Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions apps/apollo-vertex/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Slack integration for the Invoice Processing demo (Socket Mode).
# Copy this file to `.env` and fill in the real values from the "Invoice Agent"
# Slack app in the "VS Demos" workspace. NEVER commit `.env` (it is gitignored).

# Bot User OAuth Token — starts with xoxb-
SLACK_BOT_TOKEN=xoxb-your-token-here

# App-Level Token (Socket Mode, scope connections:write) — starts with xapp-
SLACK_APP_TOKEN=xapp-your-token-here

# Signing Secret — 32-char hex. Not strictly required for Socket Mode,
# included for completeness.
SLACK_SIGNING_SECRET=your-32-char-hex-signing-secret

# Channel ID for #ap-exceptions — starts with C
SLACK_CHANNEL_ID=C0XXXXXXXXX

# Port for the Slack listener's small local HTTP endpoint (escalation trigger).
# Kept off 3000 so it never collides with the Next.js dev server.
LISTENER_PORT=3010
7 changes: 7 additions & 0 deletions apps/apollo-vertex/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ public/r/
app/theme.generated.css
.vercel
.env*.local

# Slack demo runtime state (regenerated by the listener / reset script)
data/demo-state.json

# Isolated Slack listener deps (installed with npm, not pnpm)
slack/node_modules
slack/package-lock.json
5 changes: 4 additions & 1 deletion apps/apollo-vertex/.oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@
".dependency-cruiser.js",
"scripts/**",
"next-env.d.ts",
"types/**/*.d.ts"
"types/**/*.d.ts",
"slack/**",
"templates/invoice-review/**",
"app/api/demo-*/**"
],
"overrides": [
{
Expand Down
3 changes: 3 additions & 0 deletions apps/apollo-vertex/app/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export default {
"data-querying": "Data Querying",
localization: "Localization",
mcp: "MCP Server",
"invoice-review": {
display: "hidden",
},
auth_callback: {
display: "hidden",
},
Expand Down
34 changes: 34 additions & 0 deletions apps/apollo-vertex/app/api/demo-reply/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { type NextRequest, NextResponse } from "next/server";

// Server-side proxy: the Comms reply input POSTs here, and we forward to the
// Slack listener, which posts the reply into the card's thread (as the
// reviewer) and records it in the shared store.
export const dynamic = "force-dynamic";

const LISTENER_PORT = process.env.LISTENER_PORT || "3010";

export async function POST(request: NextRequest) {
let body = "{}";
try {
body = JSON.stringify(await request.json());
} catch {
body = "{}";
}
Comment on lines +10 to +16
try {
const res = await fetch(`http://localhost:${LISTENER_PORT}/reply`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
});
const data = await res.json();
return NextResponse.json(data, { status: res.ok ? 200 : res.status });
} catch {
return NextResponse.json(
{
ok: false,
error: `Slack listener not reachable on :${LISTENER_PORT}. Start it with: cd slack && npm start`,
},
{ status: 502 },
);
}
}
18 changes: 18 additions & 0 deletions apps/apollo-vertex/app/api/demo-state/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import { NextResponse } from "next/server";

// Reads the shared demo store that the Slack listener writes to. The invoice
// review UI polls this every couple seconds to reflect Slack-driven actions.
export const dynamic = "force-dynamic";

export async function GET() {
try {
const file = path.join(process.cwd(), "data", "demo-state.json");
const raw = await readFile(file, "utf8");
return NextResponse.json(JSON.parse(raw));
} catch {
// Store not created yet (listener never ran) — return an empty overlay.
return NextResponse.json({ invoices: {}, updated_at: null });
}
}
37 changes: 37 additions & 0 deletions apps/apollo-vertex/app/api/demo-trigger/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { type NextRequest, NextResponse } from "next/server";

// Server-side proxy: the prototype's "Escalate to manager" flag action POSTs
// here, and we forward to the standalone Slack listener's local HTTP endpoint.
// Keeps the listener port server-side (no CORS, no client hardcoding).
export const dynamic = "force-dynamic";

const LISTENER_PORT = process.env.LISTENER_PORT || "3010";

export async function POST(request: NextRequest) {
let body = "{}";
try {
body = JSON.stringify(await request.json());
} catch {
body = "{}"; // no body is fine — listener falls back to demo defaults
}
Comment on lines +10 to +16
try {
const res = await fetch(
`http://localhost:${LISTENER_PORT}/trigger-escalation`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body,
},
);
const data = await res.json();
return NextResponse.json(data, { status: res.ok ? 200 : 502 });
} catch {
return NextResponse.json(
{
ok: false,
error: `Slack listener not reachable on :${LISTENER_PORT}. Start it with: cd slack && npm start`,
},
{ status: 502 },
);
}
}
10 changes: 10 additions & 0 deletions apps/apollo-vertex/app/invoice-review/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use client";
import { InvoiceReviewTemplate } from "@/templates/invoice-review/InvoiceReviewTemplate";
Comment on lines +1 to +2

export default function InvoiceReviewPage() {
return (
<div className="fixed inset-0 z-50 bg-background">
<InvoiceReviewTemplate />
</div>
);
}
4 changes: 4 additions & 0 deletions apps/apollo-vertex/lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export const SUPPORTED_LOCALES = Object.keys(
const DEFAULT_LOCALE: SupportedLocale = "en";

export const configurei18n = async () => {
if (i18n.isInitialized) {
document.documentElement.lang = i18n.language;
return;
}
await i18n
.use({
type: "backend",
Expand Down
1 change: 1 addition & 0 deletions apps/apollo-vertex/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"in_baseline": "In baseline",
"info": "Info",
"invoice": "Invoice",
"invoices": "Invoices",
"japanese": "Japanese",
"json_viewer_description": "JSON data viewer",
"korean": "Korean",
Expand Down
2 changes: 2 additions & 0 deletions apps/apollo-vertex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"main": "index.js",
"scripts": {
"dev": "pnpm generate:theme && next --turbopack",
"demo": "slack/node_modules/.bin/concurrently --names APP,SLACK --prefix-colors cyan,magenta \"pnpm dev\" \"cd slack && npm start\"",
"demo:reset": "cd slack && npm run reset-demo",
"build": "pnpm generate:theme && pnpm registry:build && next build",
"start": "next start",
"generate:theme": "node --experimental-strip-types scripts/generate-theme-css.ts",
Expand Down
Binary file added apps/apollo-vertex/public/peter-vachon.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions apps/apollo-vertex/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@
"chart-3": "oklch(0.8300 0.1550 75.2000)",
"chart-4": "oklch(0.7200 0.1800 320.8000)",
"chart-5": "oklch(0.6800 0.1500 245.5000)",
"insight-50": "oklch(0.96 0.03 277)",
"insight-100": "oklch(0.92 0.05 277)",
"insight-200": "oklch(0.86 0.09 277)",
"insight-300": "oklch(0.78 0.14 277)",
"insight-400": "oklch(0.70 0.19 277)",
"insight-500": "oklch(0.62 0.22 277)",
"insight-600": "oklch(0.56 0.20 277)",
"insight-700": "oklch(0.48 0.17 277)",
"insight-800": "oklch(0.38 0.13 278)",
"insight-900": "oklch(0.30 0.10 278)",
"sidebar": "oklch(0.9723 0.0074 260.7300)",
"sidebar-foreground": "oklch(0.2394 0.0455 252.4450)",
"sidebar-primary": "oklch(0.64 0.115 208)",
Expand Down Expand Up @@ -258,6 +268,16 @@
"chart-3": "oklch(0.8300 0.1550 75.2000)",
"chart-4": "oklch(0.7200 0.1800 320.8000)",
"chart-5": "oklch(0.6800 0.1500 245.5000)",
"insight-50": "oklch(0.96 0.03 277)",
"insight-100": "oklch(0.92 0.05 277)",
"insight-200": "oklch(0.86 0.09 277)",
"insight-300": "oklch(0.78 0.14 277)",
"insight-400": "oklch(0.70 0.19 277)",
"insight-500": "oklch(0.62 0.22 277)",
"insight-600": "oklch(0.56 0.20 277)",
"insight-700": "oklch(0.48 0.17 277)",
"insight-800": "oklch(0.38 0.13 278)",
"insight-900": "oklch(0.30 0.10 278)",
"sidebar": "oklch(0.1620 0.0310 257.7000)",
"sidebar-foreground": "oklch(0.9525 0.0110 225.9830)",
"sidebar-primary": "oklch(0.69 0.112 207)",
Expand Down
2 changes: 1 addition & 1 deletion apps/apollo-vertex/registry/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as React from "react";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
Expand Down
29 changes: 29 additions & 0 deletions apps/apollo-vertex/registry/shell/shell-profile-extras.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client";

import { createContext, type ReactNode, useContext } from "react";

/**
* Optional slot for injecting extra items into the shell's user-profile menu.
* Apps wrap their tree in `ShellProfileExtrasProvider` and pass menu items
* (e.g. `DropdownMenuItem`s); they render below the built-in theme/language
* entries. Empty by default, so the shell is unaffected when unused.
*/
const ShellProfileExtrasContext = createContext<ReactNode>(null);

export function ShellProfileExtrasProvider({
items,
children,
}: {
items: ReactNode;
children: ReactNode;
}) {
return (
<ShellProfileExtrasContext.Provider value={items}>
{children}
</ShellProfileExtrasContext.Provider>
);
}

export function useShellProfileExtras(): ReactNode {
return useContext(ShellProfileExtrasContext);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { cn } from "@/lib/utils";
import { useAuth } from "./shell-auth-provider";
import { LANGUAGE_CHANGED_EVENT, LOCALE_OPTIONS } from "./shell-constants";
import type { LanguageChangedEvent } from "./shell-constants";
import { useShellProfileExtras } from "./shell-profile-extras";
import { Text } from "./shell-text";
import { useTheme } from "./shell-theme-provider";

Expand All @@ -20,6 +21,7 @@ export const UserProfileMenuItems = () => {
const { logout } = useAuth();
const { setTheme } = useTheme();
const language = i18n.language;
const extras = useShellProfileExtras();

function setLanguage(code: SupportedLocale) {
document.dispatchEvent(
Expand Down Expand Up @@ -69,6 +71,7 @@ export const UserProfileMenuItems = () => {
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
{extras}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={logout}>
<LogOut className="w-4 h-4" />
Expand Down
Loading