@agent-native/dispatch 0.23.5 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/list-connected-agents.d.ts +1 -0
- package/dist/actions/list-connected-agents.js +5 -1
- package/dist/actions/list-connected-agents.js.map +1 -1
- package/dist/actions/list-dispatch-usage-metrics.d.ts +2 -0
- package/dist/actions/list-dispatch-usage-metrics.js +11 -1
- package/dist/actions/list-dispatch-usage-metrics.js.map +1 -1
- package/dist/actions/provider-api-register.d.ts +12 -12
- package/dist/actions/remix-workspace-template.js +4 -3
- package/dist/actions/remix-workspace-template.js.map +1 -1
- package/dist/actions/scaffold-workspace-app.js +3 -2
- package/dist/actions/scaffold-workspace-app.js.map +1 -1
- package/dist/actions/start-workspace-app-creation.js +3 -2
- package/dist/actions/start-workspace-app-creation.js.map +1 -1
- package/dist/actions/view-screen.js +11 -1
- package/dist/actions/view-screen.js.map +1 -1
- package/dist/components/admin-navigation.d.ts.map +1 -1
- package/dist/components/admin-navigation.js +21 -5
- package/dist/components/admin-navigation.js.map +1 -1
- package/dist/components/app-icon.js +1 -1
- package/dist/components/app-icon.js.map +1 -1
- package/dist/components/app-list-row.d.ts +1 -1
- package/dist/components/app-list-row.d.ts.map +1 -1
- package/dist/components/app-list-row.js +1 -1
- package/dist/components/app-list-row.js.map +1 -1
- package/dist/components/app-open-actions.d.ts +1 -17
- package/dist/components/app-open-actions.d.ts.map +1 -1
- package/dist/components/app-open-actions.js +1 -20
- package/dist/components/app-open-actions.js.map +1 -1
- package/dist/components/app-row-settings.d.ts +6 -0
- package/dist/components/app-row-settings.d.ts.map +1 -0
- package/dist/components/app-row-settings.js +11 -0
- package/dist/components/app-row-settings.js.map +1 -0
- package/dist/components/automation-run-history-dialog.d.ts.map +1 -1
- package/dist/components/automation-run-history-dialog.js +3 -2
- package/dist/components/automation-run-history-dialog.js.map +1 -1
- package/dist/components/connected-app-card.d.ts.map +1 -1
- package/dist/components/connected-app-card.js +3 -2
- package/dist/components/connected-app-card.js.map +1 -1
- package/dist/components/create-app-popover.js +2 -2
- package/dist/components/create-app-popover.js.map +1 -1
- package/dist/components/dispatch-control-plane.d.ts.map +1 -1
- package/dist/components/dispatch-control-plane.js +23 -7
- package/dist/components/dispatch-control-plane.js.map +1 -1
- package/dist/components/dispatch-shell.d.ts.map +1 -1
- package/dist/components/dispatch-shell.js +5 -1
- package/dist/components/dispatch-shell.js.map +1 -1
- package/dist/components/docs-link.d.ts +16 -0
- package/dist/components/docs-link.d.ts.map +1 -0
- package/dist/components/docs-link.js +34 -0
- package/dist/components/docs-link.js.map +1 -0
- package/dist/components/layout/Layout.d.ts +1 -0
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +43 -35
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/messaging-setup-panel.d.ts.map +1 -1
- package/dist/components/messaging-setup-panel.js +3 -2
- package/dist/components/messaging-setup-panel.js.map +1 -1
- package/dist/components/usage-alerts-panel.d.ts +11 -0
- package/dist/components/usage-alerts-panel.d.ts.map +1 -0
- package/dist/components/usage-alerts-panel.js +125 -0
- package/dist/components/usage-alerts-panel.js.map +1 -0
- package/dist/components/workspace-app-card.d.ts +3 -1
- package/dist/components/workspace-app-card.d.ts.map +1 -1
- package/dist/components/workspace-app-card.js +70 -29
- package/dist/components/workspace-app-card.js.map +1 -1
- package/dist/components/workspace-app-search.d.ts +10 -0
- package/dist/components/workspace-app-search.d.ts.map +1 -0
- package/dist/components/workspace-app-search.js +27 -0
- package/dist/components/workspace-app-search.js.map +1 -0
- package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -1
- package/dist/components/workspace-resource-effective-stack.js +3 -2
- package/dist/components/workspace-resource-effective-stack.js.map +1 -1
- package/dist/components/workspace-template-card.d.ts.map +1 -1
- package/dist/components/workspace-template-card.js +10 -15
- package/dist/components/workspace-template-card.js.map +1 -1
- package/dist/hooks/use-navigation-state.d.ts +2 -0
- package/dist/hooks/use-navigation-state.d.ts.map +1 -1
- package/dist/hooks/use-navigation-state.js +10 -0
- package/dist/hooks/use-navigation-state.js.map +1 -1
- package/dist/lib/embed-session-recovery.d.ts +3 -0
- package/dist/lib/embed-session-recovery.d.ts.map +1 -0
- package/dist/lib/embed-session-recovery.js +18 -0
- package/dist/lib/embed-session-recovery.js.map +1 -0
- package/dist/lib/other-apps.d.ts +2 -0
- package/dist/lib/other-apps.d.ts.map +1 -1
- package/dist/lib/other-apps.js.map +1 -1
- package/dist/lib/workspace-app-layout.d.ts +19 -0
- package/dist/lib/workspace-app-layout.d.ts.map +1 -0
- package/dist/lib/workspace-app-layout.js +75 -0
- package/dist/lib/workspace-app-layout.js.map +1 -0
- package/dist/lib/workspace-apps.d.ts +14 -0
- package/dist/lib/workspace-apps.d.ts.map +1 -1
- package/dist/lib/workspace-apps.js +66 -0
- package/dist/lib/workspace-apps.js.map +1 -1
- package/dist/lib/workspace-apps.test.d.ts +2 -0
- package/dist/lib/workspace-apps.test.d.ts.map +1 -0
- package/dist/lib/workspace-apps.test.js +33 -0
- package/dist/lib/workspace-apps.test.js.map +1 -0
- package/dist/routes/pages/$appId.d.ts.map +1 -1
- package/dist/routes/pages/$appId.js +4 -2
- package/dist/routes/pages/$appId.js.map +1 -1
- package/dist/routes/pages/agents.d.ts.map +1 -1
- package/dist/routes/pages/agents.js +2 -3
- package/dist/routes/pages/agents.js.map +1 -1
- package/dist/routes/pages/apps.$appId.d.ts.map +1 -1
- package/dist/routes/pages/apps.$appId.js +17 -13
- package/dist/routes/pages/apps.$appId.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +31 -16
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/audit.d.ts.map +1 -1
- package/dist/routes/pages/audit.js +2 -1
- package/dist/routes/pages/audit.js.map +1 -1
- package/dist/routes/pages/chat.d.ts.map +1 -1
- package/dist/routes/pages/chat.js +7 -2
- package/dist/routes/pages/chat.js.map +1 -1
- package/dist/routes/pages/dreams.d.ts.map +1 -1
- package/dist/routes/pages/dreams.js +14 -18
- package/dist/routes/pages/dreams.js.map +1 -1
- package/dist/routes/pages/integrations.d.ts.map +1 -1
- package/dist/routes/pages/integrations.js +51 -14
- package/dist/routes/pages/integrations.js.map +1 -1
- package/dist/routes/pages/metrics.d.ts.map +1 -1
- package/dist/routes/pages/metrics.js +146 -17
- package/dist/routes/pages/metrics.js.map +1 -1
- package/dist/routes/pages/operations.js +1 -1
- package/dist/routes/pages/operations.js.map +1 -1
- package/dist/routes/pages/settings.d.ts.map +1 -1
- package/dist/routes/pages/settings.js +19 -2
- package/dist/routes/pages/settings.js.map +1 -1
- package/dist/routes/pages/thread-debug.d.ts.map +1 -1
- package/dist/routes/pages/thread-debug.js +5 -8
- package/dist/routes/pages/thread-debug.js.map +1 -1
- package/dist/routes/pages/vault.d.ts.map +1 -1
- package/dist/routes/pages/vault.js +1 -1
- package/dist/routes/pages/vault.js.map +1 -1
- package/dist/routes/pages/workspace.d.ts.map +1 -1
- package/dist/routes/pages/workspace.js +13 -10
- package/dist/routes/pages/workspace.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +12 -52
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/usage-metrics-store.d.ts +17 -0
- package/dist/server/lib/usage-metrics-store.d.ts.map +1 -1
- package/dist/server/lib/usage-metrics-store.js +187 -30
- package/dist/server/lib/usage-metrics-store.js.map +1 -1
- package/dist/server/plugins/agent-chat.js +2 -0
- package/dist/server/plugins/agent-chat.js.map +1 -1
- package/dist/server/plugins/integrations.d.ts.map +1 -1
- package/dist/server/plugins/integrations.js +1 -0
- package/dist/server/plugins/integrations.js.map +1 -1
- package/dist/shared/app-roles.d.ts +9 -0
- package/dist/shared/app-roles.d.ts.map +1 -0
- package/dist/shared/app-roles.js +7 -0
- package/dist/shared/app-roles.js.map +1 -0
- package/package.json +4 -3
- package/src/actions/list-connected-agents.spec.ts +73 -0
- package/src/actions/list-connected-agents.ts +6 -2
- package/src/actions/list-dispatch-usage-metrics.ts +11 -1
- package/src/actions/remix-workspace-template.spec.ts +29 -0
- package/src/actions/remix-workspace-template.ts +13 -6
- package/src/actions/scaffold-workspace-app.ts +12 -5
- package/src/actions/start-workspace-app-creation.ts +8 -2
- package/src/actions/view-screen.ts +13 -1
- package/src/components/admin-navigation.tsx +57 -11
- package/src/components/app-icon.spec.tsx +34 -0
- package/src/components/app-icon.tsx +1 -1
- package/src/components/app-list-row.tsx +1 -2
- package/src/components/app-open-actions.tsx +5 -114
- package/src/components/app-row-settings.tsx +54 -0
- package/src/components/automation-run-history-dialog.tsx +8 -4
- package/src/components/connected-app-card.tsx +3 -2
- package/src/components/create-app-popover.spec.tsx +1 -1
- package/src/components/create-app-popover.tsx +2 -2
- package/src/components/dispatch-control-plane.spec.tsx +44 -3
- package/src/components/dispatch-control-plane.tsx +83 -27
- package/src/components/dispatch-shell.tsx +7 -1
- package/src/components/docs-link.tsx +52 -0
- package/src/components/layout/Layout.spec.tsx +13 -0
- package/src/components/layout/Layout.tsx +63 -32
- package/src/components/messaging-setup-panel.tsx +10 -3
- package/src/components/usage-alerts-panel.tsx +445 -0
- package/src/components/workspace-app-card.spec.tsx +130 -9
- package/src/components/workspace-app-card.tsx +128 -71
- package/src/components/workspace-app-search.tsx +96 -0
- package/src/components/workspace-resource-effective-stack.tsx +10 -5
- package/src/components/workspace-template-card.spec.tsx +53 -24
- package/src/components/workspace-template-card.tsx +16 -26
- package/src/hooks/use-navigation-state.ts +12 -0
- package/src/lib/embed-session-recovery.spec.ts +63 -0
- package/src/lib/embed-session-recovery.ts +26 -0
- package/src/lib/other-apps.ts +2 -0
- package/src/lib/workspace-app-layout.spec.ts +47 -0
- package/src/lib/workspace-app-layout.ts +121 -0
- package/src/lib/workspace-apps.test.ts +64 -0
- package/src/lib/workspace-apps.ts +82 -0
- package/src/routes/pages/$appId.tsx +3 -6
- package/src/routes/pages/agents.tsx +6 -3
- package/src/routes/pages/apps.$appId.tsx +16 -17
- package/src/routes/pages/apps.tsx +116 -62
- package/src/routes/pages/audit.tsx +28 -8
- package/src/routes/pages/chat.spec.tsx +10 -0
- package/src/routes/pages/chat.tsx +8 -0
- package/src/routes/pages/dreams.tsx +92 -142
- package/src/routes/pages/integrations.tsx +147 -68
- package/src/routes/pages/metrics.tsx +460 -102
- package/src/routes/pages/operations.tsx +2 -2
- package/src/routes/pages/settings.tsx +27 -10
- package/src/routes/pages/thread-debug.tsx +18 -36
- package/src/routes/pages/vault.tsx +7 -2
- package/src/routes/pages/workspace.tsx +92 -56
- package/src/server/index.ts +4 -0
- package/src/server/lib/app-creation-store.spec.ts +90 -43
- package/src/server/lib/app-creation-store.ts +15 -61
- package/src/server/lib/usage-metrics-store.spec.ts +117 -0
- package/src/server/lib/usage-metrics-store.ts +249 -30
- package/src/server/plugins/agent-chat.spec.ts +4 -0
- package/src/server/plugins/agent-chat.ts +2 -0
- package/src/server/plugins/integrations.ts +1 -0
- package/src/shared/app-roles.ts +8 -0
- package/src/styles/dispatch.css +9 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useActionMutation, useActionQuery, } from "@agent-native/core/client/hooks";
|
|
3
|
-
import {
|
|
3
|
+
import { getDefaultMcpIntegrations, McpIntegrationLogo, } from "@agent-native/core/client/resources";
|
|
4
|
+
import { IconChevronRight, IconLink, IconPlugConnected, IconSearch, } from "@tabler/icons-react";
|
|
4
5
|
import { useQueryClient } from "@tanstack/react-query";
|
|
5
6
|
import { useMemo, useState } from "react";
|
|
6
7
|
import { Link } from "react-router";
|
|
7
8
|
import { toast } from "sonner";
|
|
8
9
|
import { ActionQueryError } from "../../components/action-query-error.js";
|
|
9
10
|
import { DispatchShell } from "../../components/dispatch-shell.js";
|
|
10
|
-
import { Badge } from "../../components/ui/badge.js";
|
|
11
11
|
import { Button } from "../../components/ui/button.js";
|
|
12
12
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "../../components/ui/collapsible.js";
|
|
13
13
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../components/ui/dialog.js";
|
|
14
14
|
import { Input } from "../../components/ui/input.js";
|
|
15
15
|
import { Label } from "../../components/ui/label.js";
|
|
16
|
-
import {
|
|
16
|
+
import { Skeleton } from "../../components/ui/skeleton.js";
|
|
17
17
|
export function meta() {
|
|
18
18
|
return [{ title: "Connections — Dispatch" }];
|
|
19
19
|
}
|
|
@@ -36,9 +36,30 @@ function inferProviderFromKey(key, label) {
|
|
|
36
36
|
}
|
|
37
37
|
return "other";
|
|
38
38
|
}
|
|
39
|
+
const MCP_INTEGRATIONS_BY_ID = new Map(getDefaultMcpIntegrations().map((integration) => [
|
|
40
|
+
integration.id,
|
|
41
|
+
integration,
|
|
42
|
+
]));
|
|
43
|
+
const CREDENTIAL_PROVIDER_LOGO_IDS = {
|
|
44
|
+
google: "google-workspace",
|
|
45
|
+
bigquery: "google-workspace",
|
|
46
|
+
jira: "atlassian",
|
|
47
|
+
};
|
|
48
|
+
function credentialLogo(service) {
|
|
49
|
+
const providerId = inferProviderFromKey(service.key, service.label);
|
|
50
|
+
const integration = MCP_INTEGRATIONS_BY_ID.get(CREDENTIAL_PROVIDER_LOGO_IDS[providerId] ?? providerId);
|
|
51
|
+
if (!integration?.logoUrl) {
|
|
52
|
+
return _jsx(IconPlugConnected, { className: "size-4 text-muted-foreground" });
|
|
53
|
+
}
|
|
54
|
+
return (_jsx(McpIntegrationLogo, { name: service.label, logoUrl: integration.logoUrl, integrationId: integration.id, className: "size-full rounded-md border-0 bg-transparent", imageClassName: "size-full p-1" }));
|
|
55
|
+
}
|
|
39
56
|
function ConnectDialog({ service, open, onOpenChange, accessMode, }) {
|
|
40
57
|
const [value, setValue] = useState("");
|
|
41
58
|
const qc = useQueryClient();
|
|
59
|
+
const configuredApps = service.apps.filter((app) => app.configured);
|
|
60
|
+
const configuredAppNames = configuredApps
|
|
61
|
+
.map((app) => app.appName)
|
|
62
|
+
.join(", ");
|
|
42
63
|
const createSecret = useActionMutation("create-vault-secret", {});
|
|
43
64
|
const createGrant = useActionMutation("create-vault-grant", {});
|
|
44
65
|
const syncToApp = useActionMutation("sync-vault-to-app", {});
|
|
@@ -104,15 +125,22 @@ function ConnectDialog({ service, open, onOpenChange, accessMode, }) {
|
|
|
104
125
|
if (!next)
|
|
105
126
|
reset();
|
|
106
127
|
onOpenChange(next);
|
|
107
|
-
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Connect ", service.label] }), _jsxs(DialogDescription, { children: [
|
|
108
|
-
?
|
|
109
|
-
:
|
|
128
|
+
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Connect ", service.label] }), _jsxs(DialogDescription, { children: [configuredApps.length > 0
|
|
129
|
+
? `Configured in ${configuredAppNames}. `
|
|
130
|
+
: "No app currently reports this key as configured. ", "Saving it creates a workspace vault entry using this exact key name. Apps need the same key name to use it."] })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-xs text-muted-foreground", children: "Key" }), _jsx("div", { className: "font-mono text-sm", children: service.key })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "connector-value", children: "Value" }), _jsx(Input, { id: "connector-value", type: "password", autoComplete: "off", value: value, onChange: (e) => setValue(e.target.value), placeholder: `Paste your ${service.label} key…`, autoFocus: true })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: pending, children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: pending || !value.trim(), children: pending ? "Saving…" : "Connect" })] })] }) }));
|
|
110
131
|
}
|
|
111
132
|
function ConnectorCard({ service, accessMode, }) {
|
|
112
133
|
const [open, setOpen] = useState(false);
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
134
|
+
const configuredApps = service.apps.filter((app) => app.configured);
|
|
135
|
+
const vaultApps = service.apps.filter((app) => app.vaultGranted);
|
|
136
|
+
const isAvailable = configuredApps.length > 0 || vaultApps.length > 0;
|
|
137
|
+
const configuredAppNames = configuredApps
|
|
138
|
+
.map((app) => app.appName)
|
|
139
|
+
.join(", ");
|
|
140
|
+
const vaultAppNames = vaultApps.map((app) => app.appName).join(", ");
|
|
141
|
+
return (_jsxs(_Fragment, { children: [_jsxs("article", { className: "flex min-w-0 items-center gap-3 border-b border-border/60 py-3.5 last:border-b-0", children: [_jsx("div", { className: "flex size-8 shrink-0 items-center justify-center rounded-md bg-muted", children: credentialLogo(service) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("span", { className: "truncate text-sm font-medium text-foreground", children: service.label }), _jsx("span", { className: isAvailable
|
|
142
|
+
? "shrink-0 text-[11px] font-medium text-emerald-600 dark:text-emerald-400"
|
|
143
|
+
: "shrink-0 text-[11px] font-medium text-muted-foreground", children: vaultApps.length > 0 ? "Connected" : "App key" })] }), _jsx("p", { className: "mt-0.5 truncate font-mono text-[11px] leading-5 text-muted-foreground/80", children: service.key }), _jsxs("p", { className: "mt-0.5 truncate text-xs leading-5 text-muted-foreground", children: ["Declared by ", service.apps.length, " ", service.apps.length === 1 ? "app" : "apps", configuredApps.length > 0 ? ` · ${configuredAppNames}` : "", vaultApps.length > 0 ? ` · Vault: ${vaultAppNames}` : ""] })] }), _jsxs("button", { type: "button", onClick: () => setOpen(true), className: "inline-flex h-8 shrink-0 items-center justify-center gap-1 rounded-md border border-border bg-background px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [isAvailable ? "Manage" : "Connect", _jsx(IconChevronRight, { className: "size-3.5 rtl:-scale-x-100" })] })] }), _jsx(ConnectDialog, { service: service, open: open, onOpenChange: setOpen, accessMode: accessMode })] }));
|
|
116
144
|
}
|
|
117
145
|
function PerAppDetailRow({ app }) {
|
|
118
146
|
const total = (app.integrations ?? []).length;
|
|
@@ -122,10 +150,12 @@ function PerAppDetailRow({ app }) {
|
|
|
122
150
|
export default function ConnectionsRoute() {
|
|
123
151
|
const catalogQuery = useActionQuery("list-integrations-catalog", {});
|
|
124
152
|
const accessQuery = useActionQuery("get-vault-access-settings", {});
|
|
125
|
-
const { data: catalog
|
|
153
|
+
const { data: catalog } = catalogQuery;
|
|
126
154
|
const { data: accessSettings } = accessQuery;
|
|
155
|
+
const isLoading = catalogQuery.isLoading || accessQuery.isLoading;
|
|
127
156
|
const apps = catalog || [];
|
|
128
157
|
const accessMode = accessSettings?.mode === "manual" ? "manual" : "all-apps";
|
|
158
|
+
const [query, setQuery] = useState("");
|
|
129
159
|
const services = useMemo(() => {
|
|
130
160
|
const map = new Map();
|
|
131
161
|
for (const app of apps) {
|
|
@@ -149,11 +179,18 @@ export default function ConnectionsRoute() {
|
|
|
149
179
|
}
|
|
150
180
|
return Array.from(map.values()).sort((a, b) => a.label.localeCompare(b.label));
|
|
151
181
|
}, [apps]);
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
182
|
+
const filteredServices = useMemo(() => {
|
|
183
|
+
const normalized = query.trim().toLowerCase();
|
|
184
|
+
if (!normalized)
|
|
185
|
+
return services;
|
|
186
|
+
return services.filter((service) => service.label.toLowerCase().includes(normalized) ||
|
|
187
|
+
service.key.toLowerCase().includes(normalized));
|
|
188
|
+
}, [query, services]);
|
|
189
|
+
const available = filteredServices.filter((s) => !s.apps.some((a) => a.configured));
|
|
190
|
+
const connected = filteredServices.filter((s) => s.apps.some((a) => a.configured));
|
|
191
|
+
return (_jsxs(DispatchShell, { title: "Connections", description: "Connect workspace credentials declared by apps and manage how they are shared.", children: [catalogQuery.isError || accessQuery.isError ? (_jsx(ActionQueryError, { error: catalogQuery.error ?? accessQuery.error, onRetry: () => {
|
|
155
192
|
void catalogQuery.refetch();
|
|
156
193
|
void accessQuery.refetch();
|
|
157
|
-
} })) : null, !catalogQuery.isError && isLoading && services.length === 0 && (_jsx("div", { className: "rounded-
|
|
194
|
+
} })) : null, !catalogQuery.isError && !isLoading ? (_jsxs("div", { className: "mb-5 flex items-center gap-3", children: [_jsxs("div", { className: "relative min-w-0 flex-1", children: [_jsx(IconSearch, { className: "pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: query, onChange: (event) => setQuery(event.target.value), placeholder: "Search credentials", "aria-label": "Search credentials", className: "h-9 ps-9" })] }), _jsxs("span", { className: "shrink-0 text-xs text-muted-foreground", children: [filteredServices.length, " credential", filteredServices.length === 1 ? "" : "s"] })] })) : null, !catalogQuery.isError && isLoading && services.length === 0 && (_jsx("div", { className: "overflow-hidden rounded-lg bg-card", children: Array.from({ length: 7 }).map((_, index) => (_jsxs("div", { className: "flex items-center gap-3 border-b px-4 py-3 last:border-b-0", children: [_jsx(Skeleton, { className: "size-8 rounded-md" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Skeleton, { className: "h-4 w-40 max-w-full" }), _jsx(Skeleton, { className: "mt-1.5 h-3 w-56 max-w-full" })] }), _jsx(Skeleton, { className: "h-6 w-20 rounded-full" })] }, index))) })), !catalogQuery.isError && !isLoading && services.length === 0 && (_jsx("div", { className: "rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground", children: "No apps with declared integrations are reachable yet." })), available.length > 0 && (_jsxs("section", { children: [_jsxs("div", { className: "mb-3 flex items-baseline justify-between", children: [_jsx("h2", { className: "text-sm font-medium text-foreground", children: "Not configured in apps" }), _jsx("span", { className: "text-xs text-muted-foreground", children: available.length })] }), _jsx("div", { className: "overflow-hidden rounded-lg bg-card", children: available.map((service) => (_jsx(ConnectorCard, { service: service, accessMode: accessMode }, service.key))) })] })), connected.length > 0 && (_jsxs("section", { children: [_jsxs("div", { className: "mb-3 mt-2 flex items-baseline justify-between", children: [_jsx("h2", { className: "text-sm font-medium text-foreground", children: "Configured in apps" }), _jsx("span", { className: "text-xs text-muted-foreground", children: connected.length })] }), _jsx("div", { className: "overflow-hidden rounded-lg bg-card", children: connected.map((service) => (_jsx(ConnectorCard, { service: service, accessMode: accessMode }, service.key))) })] })), apps.length > 0 && (_jsxs(Collapsible, { className: "mt-6 rounded-2xl bg-card", children: [_jsxs(CollapsibleTrigger, { className: "group flex w-full items-center justify-between px-4 py-3 text-sm", children: [_jsxs("span", { className: "flex items-center gap-2 text-muted-foreground", children: [_jsx(IconPlugConnected, { size: 14 }), "Per-app status"] }), _jsx(IconChevronRight, { size: 14, className: "text-muted-foreground transition group-data-[state=open]:rotate-90" })] }), _jsxs(CollapsibleContent, { children: [_jsx("div", { className: "border-t", children: apps.map((app) => (_jsx(PerAppDetailRow, { app: app }, app.appId))) }), _jsxs("div", { className: "flex items-center justify-end gap-1.5 border-t px-4 py-2.5 text-xs text-muted-foreground", children: [_jsx(IconLink, { size: 12 }), _jsx(Link, { to: "/admin/vault", className: "hover:underline", children: "Open vault for advanced sharing" })] })] })] }))] }));
|
|
158
195
|
}
|
|
159
196
|
//# sourceMappingURL=integrations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useActionMutation,\n useActionQuery,\n} from \"@agent-native/core/client/hooks\";\nimport {\n IconCheck,\n IconChevronRight,\n IconCircleDashed,\n IconKey,\n IconLink,\n IconPlugConnected,\n} from \"@tabler/icons-react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useMemo, useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { toast } from \"sonner\";\n\nimport { ActionQueryError } from \"../../components/action-query-error\";\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport { Badge } from \"../../components/ui/badge\";\nimport { Button } from \"../../components/ui/button\";\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"../../components/ui/collapsible\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"../../components/ui/dialog\";\nimport { Input } from \"../../components/ui/input\";\nimport { Label } from \"../../components/ui/label\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"../../components/ui/tooltip\";\n\nexport function meta() {\n return [{ title: \"Connections — Dispatch\" }];\n}\n\ninterface AppRef {\n appId: string;\n appName: string;\n color: string;\n configured: boolean;\n vaultGranted: boolean;\n vaultSecretId?: string;\n}\n\ninterface Service {\n /** Credential key shared across apps (e.g. `OPENAI_API_KEY`). */\n key: string;\n /** Human label from the first app that declares it (`\"OpenAI\"`, `\"Stripe\"`). */\n label: string;\n /** Apps in the workspace that declare this credential. */\n apps: AppRef[];\n}\n\ninterface CatalogApp {\n appId: string;\n appName: string;\n color: string;\n url: string;\n reachable: boolean;\n integrations?: Array<{\n key: string;\n label: string;\n required: boolean;\n configured: boolean;\n vaultGranted: boolean;\n vaultSecretId?: string;\n }>;\n}\n\nfunction inferProviderFromKey(key: string, label: string): string {\n const haystack = `${key} ${label}`.toLowerCase();\n for (const provider of [\n \"google\",\n \"slack\",\n \"sendgrid\",\n \"github\",\n \"stripe\",\n \"hubspot\",\n \"jira\",\n \"bigquery\",\n \"anthropic\",\n \"openai\",\n ]) {\n if (haystack.includes(provider)) return provider;\n }\n return \"other\";\n}\n\nfunction ConnectDialog({\n service,\n open,\n onOpenChange,\n accessMode,\n}: {\n service: Service;\n open: boolean;\n onOpenChange: (next: boolean) => void;\n accessMode: \"all-apps\" | \"manual\";\n}) {\n const [value, setValue] = useState(\"\");\n const qc = useQueryClient();\n\n const createSecret = useActionMutation(\"create-vault-secret\", {});\n const createGrant = useActionMutation(\"create-vault-grant\", {});\n const syncToApp = useActionMutation(\"sync-vault-to-app\", {});\n\n function reset() {\n setValue(\"\");\n }\n\n async function handleSave() {\n const trimmed = value.trim();\n if (!trimmed) {\n toast.error(\"Enter a value to save\");\n return;\n }\n try {\n // 1. Create the secret (or get the existing one — server treats key as\n // the unique identifier). The server returns { secret: { id, ... } }.\n const created = await createSecret.mutateAsync({\n credentialKey: service.key,\n name: service.label,\n value: trimmed,\n provider: inferProviderFromKey(service.key, service.label),\n });\n const secretId =\n (created as { secret?: { id?: string } })?.secret?.id ??\n (created as { id?: string })?.id;\n if (!secretId) {\n throw new Error(\"Secret created but id missing\");\n }\n\n // 2. Manual mode needs grants; all-apps mode only needs sync.\n if (accessMode === \"manual\") {\n const targets = service.apps.filter((a) => !a.vaultGranted);\n for (const app of targets) {\n try {\n await createGrant.mutateAsync({\n secretId,\n appId: app.appId,\n });\n } catch (err) {\n console.warn(`grant to ${app.appId} failed`, err);\n }\n }\n }\n for (const app of service.apps) {\n try {\n await syncToApp.mutateAsync({ appId: app.appId });\n } catch (err) {\n console.warn(`sync to ${app.appId} failed`, err);\n }\n }\n\n qc.invalidateQueries({\n queryKey: [\"action\", \"list-integrations-catalog\"],\n });\n toast.success(`Connected ${service.label}`);\n onOpenChange(false);\n reset();\n } catch (err: any) {\n toast.error(err?.message ?? \"Failed to save credential\");\n }\n }\n\n const pending =\n createSecret.isPending || createGrant.isPending || syncToApp.isPending;\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) reset();\n onOpenChange(next);\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Connect {service.label}</DialogTitle>\n <DialogDescription>\n Used by{\" \"}\n {service.apps.length === 1\n ? service.apps[0].appName\n : `${service.apps.length} apps`}\n . Saved to the workspace vault and synced to every app that needs\n it.\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <div>\n <Label className=\"text-xs text-muted-foreground\">Key</Label>\n <div className=\"font-mono text-sm\">{service.key}</div>\n </div>\n <div>\n <Label htmlFor=\"connector-value\">Value</Label>\n <Input\n id=\"connector-value\"\n type=\"password\"\n autoComplete=\"off\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder={`Paste your ${service.label} key…`}\n autoFocus\n />\n </div>\n </div>\n <DialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={pending}\n >\n Cancel\n </Button>\n <Button onClick={handleSave} disabled={pending || !value.trim()}>\n {pending ? \"Saving…\" : \"Connect\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction ConnectorCard({\n service,\n accessMode,\n}: {\n service: Service;\n accessMode: \"all-apps\" | \"manual\";\n}) {\n const [open, setOpen] = useState(false);\n const isConnected = service.apps.some((a) => a.configured);\n const appCount = service.apps.length;\n\n return (\n <>\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"group flex flex-col items-start gap-2 rounded-2xl bg-card p-5 text-left transition-[background-color] hover:bg-card/80 cursor-pointer\"\n >\n <div className=\"flex w-full items-start justify-between gap-2\">\n <div className=\"flex h-9 w-9 items-center justify-center rounded-xl bg-muted\">\n <IconKey size={16} className=\"text-muted-foreground\" />\n </div>\n {isConnected ? (\n <Badge\n variant=\"secondary\"\n className=\"bg-green-500/10 text-green-700 dark:text-green-400 gap-1\"\n >\n <IconCheck size={12} />\n Connected\n </Badge>\n ) : (\n <Badge\n variant=\"secondary\"\n className=\"bg-amber-500/10 text-amber-700 dark:text-amber-400 gap-1\"\n >\n <IconCircleDashed size={12} />\n Connect\n </Badge>\n )}\n </div>\n <div className=\"w-full min-w-0\">\n <Tooltip>\n <TooltipTrigger asChild>\n <div className=\"text-sm font-semibold text-foreground truncate\">\n {service.label}\n </div>\n </TooltipTrigger>\n <TooltipContent>{service.label}</TooltipContent>\n </Tooltip>\n <div className=\"font-mono text-xs text-muted-foreground/80 truncate\">\n {service.key}\n </div>\n </div>\n <div className=\"text-xs text-muted-foreground\">\n Used by {appCount} {appCount === 1 ? \"app\" : \"apps\"}\n </div>\n </button>\n <ConnectDialog\n service={service}\n open={open}\n onOpenChange={setOpen}\n accessMode={accessMode}\n />\n </>\n );\n}\n\nfunction PerAppDetailRow({ app }: { app: CatalogApp }) {\n const total = (app.integrations ?? []).length;\n const ok = (app.integrations ?? []).filter((i) => i.configured).length;\n return (\n <div className=\"flex items-center justify-between border-t px-4 py-2.5 first:border-t-0\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <div\n className=\"h-5 w-5 rounded text-[10px] font-bold text-white flex items-center justify-center shrink-0\"\n style={{ backgroundColor: app.color }}\n >\n {app.appName.charAt(0).toUpperCase()}\n </div>\n <span className=\"text-sm truncate\">{app.appName}</span>\n {!app.reachable && (\n <span className=\"text-xs text-muted-foreground\">offline</span>\n )}\n </div>\n <span className=\"text-xs text-muted-foreground\">\n {total === 0 ? \"no integrations\" : `${ok}/${total}`}\n </span>\n </div>\n );\n}\n\nexport default function ConnectionsRoute() {\n const catalogQuery = useActionQuery(\"list-integrations-catalog\", {});\n const accessQuery = useActionQuery(\"get-vault-access-settings\", {});\n const { data: catalog, isLoading } = catalogQuery;\n const { data: accessSettings } = accessQuery;\n const apps = (catalog as CatalogApp[]) || [];\n const accessMode =\n (accessSettings as any)?.mode === \"manual\" ? \"manual\" : \"all-apps\";\n\n const services = useMemo<Service[]>(() => {\n const map = new Map<string, Service>();\n for (const app of apps) {\n for (const intg of app.integrations ?? []) {\n if (!map.has(intg.key)) {\n map.set(intg.key, {\n key: intg.key,\n label: intg.label,\n apps: [],\n });\n }\n map.get(intg.key)!.apps.push({\n appId: app.appId,\n appName: app.appName,\n color: app.color,\n configured: intg.configured,\n vaultGranted: intg.vaultGranted,\n vaultSecretId: intg.vaultSecretId,\n });\n }\n }\n return Array.from(map.values()).sort((a, b) =>\n a.label.localeCompare(b.label),\n );\n }, [apps]);\n\n const available = services.filter((s) => !s.apps.some((a) => a.configured));\n const connected = services.filter((s) => s.apps.some((a) => a.configured));\n\n return (\n <DispatchShell\n title=\"Connections\"\n description=\"Connect services once. Apps that need them pick up the key automatically.\"\n >\n {catalogQuery.isError || accessQuery.isError ? (\n <ActionQueryError\n error={catalogQuery.error ?? accessQuery.error}\n onRetry={() => {\n void catalogQuery.refetch();\n void accessQuery.refetch();\n }}\n />\n ) : null}\n\n {!catalogQuery.isError && isLoading && services.length === 0 && (\n <div className=\"rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground\">\n Discovering apps and credentials…\n </div>\n )}\n\n {!catalogQuery.isError && !isLoading && services.length === 0 && (\n <div className=\"rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground\">\n No apps with declared integrations are reachable yet.\n </div>\n )}\n\n {available.length > 0 && (\n <section>\n <div className=\"mb-3 flex items-baseline justify-between\">\n <h2 className=\"text-sm font-medium text-foreground\">\n Available to connect\n </h2>\n <span className=\"text-xs text-muted-foreground\">\n {available.length}\n </span>\n </div>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {available.map((service) => (\n <ConnectorCard\n key={service.key}\n service={service}\n accessMode={accessMode}\n />\n ))}\n </div>\n </section>\n )}\n\n {connected.length > 0 && (\n <section>\n <div className=\"mb-3 mt-2 flex items-baseline justify-between\">\n <h2 className=\"text-sm font-medium text-foreground\">Connected</h2>\n <span className=\"text-xs text-muted-foreground\">\n {connected.length}\n </span>\n </div>\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {connected.map((service) => (\n <ConnectorCard\n key={service.key}\n service={service}\n accessMode={accessMode}\n />\n ))}\n </div>\n </section>\n )}\n\n {apps.length > 0 && (\n <Collapsible className=\"mt-6 rounded-2xl bg-card\">\n <CollapsibleTrigger className=\"group flex w-full items-center justify-between px-4 py-3 text-sm\">\n <span className=\"flex items-center gap-2 text-muted-foreground\">\n <IconPlugConnected size={14} />\n Per-app status\n </span>\n <IconChevronRight\n size={14}\n className=\"text-muted-foreground transition group-data-[state=open]:rotate-90\"\n />\n </CollapsibleTrigger>\n <CollapsibleContent>\n <div className=\"border-t\">\n {apps.map((app) => (\n <PerAppDetailRow key={app.appId} app={app} />\n ))}\n </div>\n <div className=\"flex items-center justify-end gap-1.5 border-t px-4 py-2.5 text-xs text-muted-foreground\">\n <IconLink size={12} />\n <Link to=\"/admin/vault\" className=\"hover:underline\">\n Open vault for advanced sharing\n </Link>\n </div>\n </CollapsibleContent>\n </Collapsible>\n )}\n </DispatchShell>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../../src/routes/pages/integrations.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useActionMutation,\n useActionQuery,\n} from \"@agent-native/core/client/hooks\";\nimport {\n getDefaultMcpIntegrations,\n McpIntegrationLogo,\n} from \"@agent-native/core/client/resources\";\nimport {\n IconChevronRight,\n IconLink,\n IconPlugConnected,\n IconSearch,\n} from \"@tabler/icons-react\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useMemo, useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { toast } from \"sonner\";\n\nimport { ActionQueryError } from \"../../components/action-query-error\";\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport { Button } from \"../../components/ui/button\";\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"../../components/ui/collapsible\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"../../components/ui/dialog\";\nimport { Input } from \"../../components/ui/input\";\nimport { Label } from \"../../components/ui/label\";\nimport { Skeleton } from \"../../components/ui/skeleton\";\n\nexport function meta() {\n return [{ title: \"Connections — Dispatch\" }];\n}\n\ninterface AppRef {\n appId: string;\n appName: string;\n color: string;\n configured: boolean;\n vaultGranted: boolean;\n vaultSecretId?: string;\n}\n\ninterface Service {\n /** Credential key shared across apps (e.g. `OPENAI_API_KEY`). */\n key: string;\n /** Human label from the first app that declares it (`\"OpenAI\"`, `\"Stripe\"`). */\n label: string;\n /** Apps in the workspace that declare this credential. */\n apps: AppRef[];\n}\n\ninterface CatalogApp {\n appId: string;\n appName: string;\n color: string;\n url: string;\n reachable: boolean;\n integrations?: Array<{\n key: string;\n label: string;\n required: boolean;\n configured: boolean;\n vaultGranted: boolean;\n vaultSecretId?: string;\n }>;\n}\n\nfunction inferProviderFromKey(key: string, label: string): string {\n const haystack = `${key} ${label}`.toLowerCase();\n for (const provider of [\n \"google\",\n \"slack\",\n \"sendgrid\",\n \"github\",\n \"stripe\",\n \"hubspot\",\n \"jira\",\n \"bigquery\",\n \"anthropic\",\n \"openai\",\n ]) {\n if (haystack.includes(provider)) return provider;\n }\n return \"other\";\n}\n\nconst MCP_INTEGRATIONS_BY_ID = new Map(\n getDefaultMcpIntegrations().map((integration) => [\n integration.id,\n integration,\n ]),\n);\n\nconst CREDENTIAL_PROVIDER_LOGO_IDS: Record<string, string> = {\n google: \"google-workspace\",\n bigquery: \"google-workspace\",\n jira: \"atlassian\",\n};\n\nfunction credentialLogo(service: Service) {\n const providerId = inferProviderFromKey(service.key, service.label);\n const integration = MCP_INTEGRATIONS_BY_ID.get(\n CREDENTIAL_PROVIDER_LOGO_IDS[providerId] ?? providerId,\n );\n if (!integration?.logoUrl) {\n return <IconPlugConnected className=\"size-4 text-muted-foreground\" />;\n }\n return (\n <McpIntegrationLogo\n name={service.label}\n logoUrl={integration.logoUrl}\n integrationId={integration.id}\n className=\"size-full rounded-md border-0 bg-transparent\"\n imageClassName=\"size-full p-1\"\n />\n );\n}\n\nfunction ConnectDialog({\n service,\n open,\n onOpenChange,\n accessMode,\n}: {\n service: Service;\n open: boolean;\n onOpenChange: (next: boolean) => void;\n accessMode: \"all-apps\" | \"manual\";\n}) {\n const [value, setValue] = useState(\"\");\n const qc = useQueryClient();\n const configuredApps = service.apps.filter((app) => app.configured);\n const configuredAppNames = configuredApps\n .map((app) => app.appName)\n .join(\", \");\n\n const createSecret = useActionMutation(\"create-vault-secret\", {});\n const createGrant = useActionMutation(\"create-vault-grant\", {});\n const syncToApp = useActionMutation(\"sync-vault-to-app\", {});\n\n function reset() {\n setValue(\"\");\n }\n\n async function handleSave() {\n const trimmed = value.trim();\n if (!trimmed) {\n toast.error(\"Enter a value to save\");\n return;\n }\n try {\n // 1. Create the secret (or get the existing one — server treats key as\n // the unique identifier). The server returns { secret: { id, ... } }.\n const created = await createSecret.mutateAsync({\n credentialKey: service.key,\n name: service.label,\n value: trimmed,\n provider: inferProviderFromKey(service.key, service.label),\n });\n const secretId =\n (created as { secret?: { id?: string } })?.secret?.id ??\n (created as { id?: string })?.id;\n if (!secretId) {\n throw new Error(\"Secret created but id missing\");\n }\n\n // 2. Manual mode needs grants; all-apps mode only needs sync.\n if (accessMode === \"manual\") {\n const targets = service.apps.filter((a) => !a.vaultGranted);\n for (const app of targets) {\n try {\n await createGrant.mutateAsync({\n secretId,\n appId: app.appId,\n });\n } catch (err) {\n console.warn(`grant to ${app.appId} failed`, err);\n }\n }\n }\n for (const app of service.apps) {\n try {\n await syncToApp.mutateAsync({ appId: app.appId });\n } catch (err) {\n console.warn(`sync to ${app.appId} failed`, err);\n }\n }\n\n qc.invalidateQueries({\n queryKey: [\"action\", \"list-integrations-catalog\"],\n });\n toast.success(`Connected ${service.label}`);\n onOpenChange(false);\n reset();\n } catch (err: any) {\n toast.error(err?.message ?? \"Failed to save credential\");\n }\n }\n\n const pending =\n createSecret.isPending || createGrant.isPending || syncToApp.isPending;\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) reset();\n onOpenChange(next);\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Connect {service.label}</DialogTitle>\n <DialogDescription>\n {configuredApps.length > 0\n ? `Configured in ${configuredAppNames}. `\n : \"No app currently reports this key as configured. \"}\n Saving it creates a workspace vault entry using this exact key name.\n Apps need the same key name to use it.\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <div>\n <Label className=\"text-xs text-muted-foreground\">Key</Label>\n <div className=\"font-mono text-sm\">{service.key}</div>\n </div>\n <div>\n <Label htmlFor=\"connector-value\">Value</Label>\n <Input\n id=\"connector-value\"\n type=\"password\"\n autoComplete=\"off\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder={`Paste your ${service.label} key…`}\n autoFocus\n />\n </div>\n </div>\n <DialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={pending}\n >\n Cancel\n </Button>\n <Button onClick={handleSave} disabled={pending || !value.trim()}>\n {pending ? \"Saving…\" : \"Connect\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction ConnectorCard({\n service,\n accessMode,\n}: {\n service: Service;\n accessMode: \"all-apps\" | \"manual\";\n}) {\n const [open, setOpen] = useState(false);\n const configuredApps = service.apps.filter((app) => app.configured);\n const vaultApps = service.apps.filter((app) => app.vaultGranted);\n const isAvailable = configuredApps.length > 0 || vaultApps.length > 0;\n const configuredAppNames = configuredApps\n .map((app) => app.appName)\n .join(\", \");\n const vaultAppNames = vaultApps.map((app) => app.appName).join(\", \");\n\n return (\n <>\n <article className=\"flex min-w-0 items-center gap-3 border-b border-border/60 py-3.5 last:border-b-0\">\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-md bg-muted\">\n {credentialLogo(service)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-foreground\">\n {service.label}\n </span>\n <span\n className={\n isAvailable\n ? \"shrink-0 text-[11px] font-medium text-emerald-600 dark:text-emerald-400\"\n : \"shrink-0 text-[11px] font-medium text-muted-foreground\"\n }\n >\n {vaultApps.length > 0 ? \"Connected\" : \"App key\"}\n </span>\n </div>\n <p className=\"mt-0.5 truncate font-mono text-[11px] leading-5 text-muted-foreground/80\">\n {service.key}\n </p>\n <p className=\"mt-0.5 truncate text-xs leading-5 text-muted-foreground\">\n Declared by {service.apps.length}{\" \"}\n {service.apps.length === 1 ? \"app\" : \"apps\"}\n {configuredApps.length > 0 ? ` · ${configuredAppNames}` : \"\"}\n {vaultApps.length > 0 ? ` · Vault: ${vaultAppNames}` : \"\"}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"inline-flex h-8 shrink-0 items-center justify-center gap-1 rounded-md border border-border bg-background px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {isAvailable ? \"Manage\" : \"Connect\"}\n <IconChevronRight className=\"size-3.5 rtl:-scale-x-100\" />\n </button>\n </article>\n <ConnectDialog\n service={service}\n open={open}\n onOpenChange={setOpen}\n accessMode={accessMode}\n />\n </>\n );\n}\n\nfunction PerAppDetailRow({ app }: { app: CatalogApp }) {\n const total = (app.integrations ?? []).length;\n const ok = (app.integrations ?? []).filter((i) => i.configured).length;\n return (\n <div className=\"flex items-center justify-between border-t px-4 py-2.5 first:border-t-0\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <div\n className=\"h-5 w-5 rounded text-[10px] font-bold text-white flex items-center justify-center shrink-0\"\n style={{ backgroundColor: app.color }}\n >\n {app.appName.charAt(0).toUpperCase()}\n </div>\n <span className=\"text-sm truncate\">{app.appName}</span>\n {!app.reachable && (\n <span className=\"text-xs text-muted-foreground\">offline</span>\n )}\n </div>\n <span className=\"text-xs text-muted-foreground\">\n {total === 0 ? \"no integrations\" : `${ok}/${total}`}\n </span>\n </div>\n );\n}\n\nexport default function ConnectionsRoute() {\n const catalogQuery = useActionQuery(\"list-integrations-catalog\", {});\n const accessQuery = useActionQuery(\"get-vault-access-settings\", {});\n const { data: catalog } = catalogQuery;\n const { data: accessSettings } = accessQuery;\n const isLoading = catalogQuery.isLoading || accessQuery.isLoading;\n const apps = (catalog as CatalogApp[]) || [];\n const accessMode =\n (accessSettings as any)?.mode === \"manual\" ? \"manual\" : \"all-apps\";\n const [query, setQuery] = useState(\"\");\n\n const services = useMemo<Service[]>(() => {\n const map = new Map<string, Service>();\n for (const app of apps) {\n for (const intg of app.integrations ?? []) {\n if (!map.has(intg.key)) {\n map.set(intg.key, {\n key: intg.key,\n label: intg.label,\n apps: [],\n });\n }\n map.get(intg.key)!.apps.push({\n appId: app.appId,\n appName: app.appName,\n color: app.color,\n configured: intg.configured,\n vaultGranted: intg.vaultGranted,\n vaultSecretId: intg.vaultSecretId,\n });\n }\n }\n return Array.from(map.values()).sort((a, b) =>\n a.label.localeCompare(b.label),\n );\n }, [apps]);\n\n const filteredServices = useMemo(() => {\n const normalized = query.trim().toLowerCase();\n if (!normalized) return services;\n return services.filter(\n (service) =>\n service.label.toLowerCase().includes(normalized) ||\n service.key.toLowerCase().includes(normalized),\n );\n }, [query, services]);\n const available = filteredServices.filter(\n (s) => !s.apps.some((a) => a.configured),\n );\n const connected = filteredServices.filter((s) =>\n s.apps.some((a) => a.configured),\n );\n\n return (\n <DispatchShell\n title=\"Connections\"\n description=\"Connect workspace credentials declared by apps and manage how they are shared.\"\n >\n {catalogQuery.isError || accessQuery.isError ? (\n <ActionQueryError\n error={catalogQuery.error ?? accessQuery.error}\n onRetry={() => {\n void catalogQuery.refetch();\n void accessQuery.refetch();\n }}\n />\n ) : null}\n\n {!catalogQuery.isError && !isLoading ? (\n <div className=\"mb-5 flex items-center gap-3\">\n <div className=\"relative min-w-0 flex-1\">\n <IconSearch className=\"pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder=\"Search credentials\"\n aria-label=\"Search credentials\"\n className=\"h-9 ps-9\"\n />\n </div>\n <span className=\"shrink-0 text-xs text-muted-foreground\">\n {filteredServices.length} credential\n {filteredServices.length === 1 ? \"\" : \"s\"}\n </span>\n </div>\n ) : null}\n\n {!catalogQuery.isError && isLoading && services.length === 0 && (\n <div className=\"overflow-hidden rounded-lg bg-card\">\n {Array.from({ length: 7 }).map((_, index) => (\n <div\n key={index}\n className=\"flex items-center gap-3 border-b px-4 py-3 last:border-b-0\"\n >\n <Skeleton className=\"size-8 rounded-md\" />\n <div className=\"min-w-0 flex-1\">\n <Skeleton className=\"h-4 w-40 max-w-full\" />\n <Skeleton className=\"mt-1.5 h-3 w-56 max-w-full\" />\n </div>\n <Skeleton className=\"h-6 w-20 rounded-full\" />\n </div>\n ))}\n </div>\n )}\n\n {!catalogQuery.isError && !isLoading && services.length === 0 && (\n <div className=\"rounded-2xl border border-dashed px-6 py-12 text-center text-sm text-muted-foreground\">\n No apps with declared integrations are reachable yet.\n </div>\n )}\n\n {available.length > 0 && (\n <section>\n <div className=\"mb-3 flex items-baseline justify-between\">\n <h2 className=\"text-sm font-medium text-foreground\">\n Not configured in apps\n </h2>\n <span className=\"text-xs text-muted-foreground\">\n {available.length}\n </span>\n </div>\n <div className=\"overflow-hidden rounded-lg bg-card\">\n {available.map((service) => (\n <ConnectorCard\n key={service.key}\n service={service}\n accessMode={accessMode}\n />\n ))}\n </div>\n </section>\n )}\n\n {connected.length > 0 && (\n <section>\n <div className=\"mb-3 mt-2 flex items-baseline justify-between\">\n <h2 className=\"text-sm font-medium text-foreground\">\n Configured in apps\n </h2>\n <span className=\"text-xs text-muted-foreground\">\n {connected.length}\n </span>\n </div>\n <div className=\"overflow-hidden rounded-lg bg-card\">\n {connected.map((service) => (\n <ConnectorCard\n key={service.key}\n service={service}\n accessMode={accessMode}\n />\n ))}\n </div>\n </section>\n )}\n\n {apps.length > 0 && (\n <Collapsible className=\"mt-6 rounded-2xl bg-card\">\n <CollapsibleTrigger className=\"group flex w-full items-center justify-between px-4 py-3 text-sm\">\n <span className=\"flex items-center gap-2 text-muted-foreground\">\n <IconPlugConnected size={14} />\n Per-app status\n </span>\n <IconChevronRight\n size={14}\n className=\"text-muted-foreground transition group-data-[state=open]:rotate-90\"\n />\n </CollapsibleTrigger>\n <CollapsibleContent>\n <div className=\"border-t\">\n {apps.map((app) => (\n <PerAppDetailRow key={app.appId} app={app} />\n ))}\n </div>\n <div className=\"flex items-center justify-end gap-1.5 border-t px-4 py-2.5 text-xs text-muted-foreground\">\n <IconLink size={12} />\n <Link to=\"/admin/vault\" className=\"hover:underline\">\n Open vault for advanced sharing\n </Link>\n </div>\n </CollapsibleContent>\n </Collapsible>\n )}\n </DispatchShell>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/metrics.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/metrics.tsx"],"names":[],"mappings":"AAsCA,wBAAgB,IAAI;;IAEnB;AAg4BD,MAAM,CAAC,OAAO,UAAU,YAAY,oDAsMnC"}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { sendToAgentChat } from "@agent-native/core/client/agent-chat";
|
|
2
3
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
3
4
|
import { useT } from "@agent-native/core/client/i18n";
|
|
4
|
-
import { IconActivity, IconAlertTriangle, IconApps, IconChartBar,
|
|
5
|
+
import { IconActivity, IconAlertTriangle, IconArrowUpRight, IconApps, IconChartBar, IconCoin, IconMessages, IconUsersGroup, } from "@tabler/icons-react";
|
|
5
6
|
import { useMemo, useState } from "react";
|
|
7
|
+
import { Link, useSearchParams } from "react-router";
|
|
8
|
+
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
|
|
6
9
|
import { DispatchShell } from "../../components/dispatch-shell.js";
|
|
7
10
|
import { Alert, AlertDescription, AlertTitle } from "../../components/ui/alert.js";
|
|
8
11
|
import { Badge } from "../../components/ui/badge.js";
|
|
9
12
|
import { Button } from "../../components/ui/button.js";
|
|
13
|
+
import { ChartContainer, ChartTooltip, ChartTooltipContent, } from "../../components/ui/chart.js";
|
|
14
|
+
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "../../components/ui/select.js";
|
|
10
15
|
import { Skeleton } from "../../components/ui/skeleton.js";
|
|
16
|
+
import { UsageAlertsPanel } from "../../components/usage-alerts-panel.js";
|
|
11
17
|
import { cn } from "../../lib/utils.js";
|
|
12
18
|
export function meta() {
|
|
13
19
|
return [{ title: "Metrics — Dispatch" }];
|
|
@@ -76,6 +82,32 @@ function timeAgo(timestamp) {
|
|
|
76
82
|
return `${Math.floor(diff / 3_600_000)}h ago`;
|
|
77
83
|
return `${Math.floor(diff / 86_400_000)}d ago`;
|
|
78
84
|
}
|
|
85
|
+
function formatTrendDate(value) {
|
|
86
|
+
const date = new Date(`${value}T12:00:00`);
|
|
87
|
+
return date.toLocaleDateString(undefined, {
|
|
88
|
+
month: "short",
|
|
89
|
+
day: "numeric",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function completeTrendRows(rows) {
|
|
93
|
+
if (rows.length < 2)
|
|
94
|
+
return rows;
|
|
95
|
+
const byDate = new Map(rows.map((row) => [row.date, row]));
|
|
96
|
+
const start = new Date(`${rows[0].date}T12:00:00`);
|
|
97
|
+
const end = new Date(`${rows[rows.length - 1].date}T12:00:00`);
|
|
98
|
+
const completed = [];
|
|
99
|
+
for (const cursor = start; cursor <= end; cursor.setDate(cursor.getDate() + 1)) {
|
|
100
|
+
const date = cursor.toISOString().slice(0, 10);
|
|
101
|
+
completed.push(byDate.get(date) ?? {
|
|
102
|
+
date,
|
|
103
|
+
costCents: 0,
|
|
104
|
+
calls: 0,
|
|
105
|
+
chatCalls: 0,
|
|
106
|
+
activeUsers: 0,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return completed;
|
|
110
|
+
}
|
|
79
111
|
function displayApp(value) {
|
|
80
112
|
const trimmed = value?.trim();
|
|
81
113
|
if (!trimmed || trimmed === "unattributed")
|
|
@@ -102,6 +134,78 @@ function Panel({ title, icon, children, action, }) {
|
|
|
102
134
|
function LoadingMetrics() {
|
|
103
135
|
return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "grid gap-3 md:grid-cols-2 xl:grid-cols-5", children: Array.from({ length: 5 }).map((_, index) => (_jsxs("div", { className: "rounded-lg bg-card p-4", children: [_jsx(Skeleton, { className: "mb-4 h-4 w-24" }), _jsx(Skeleton, { className: "h-7 w-20" }), _jsx(Skeleton, { className: "mt-3 h-3 w-28" })] }, index))) }), _jsx(Skeleton, { className: "h-80 rounded-lg" })] }));
|
|
104
136
|
}
|
|
137
|
+
function ScopeSelector({ value, onChange, }) {
|
|
138
|
+
return (_jsx("div", { className: "flex rounded-md bg-card p-0.5", "aria-label": "Usage scope", children: [
|
|
139
|
+
["me", "My usage"],
|
|
140
|
+
["workspace", "Workspace"],
|
|
141
|
+
].map(([scope, label]) => (_jsx(Button, { type: "button", variant: value === scope ? "secondary" : "ghost", size: "sm", className: "h-7 px-3 text-xs", "aria-pressed": value === scope, onClick: () => onChange(scope), children: label }, scope))) }));
|
|
142
|
+
}
|
|
143
|
+
function UserSelector({ value, users, onChange, }) {
|
|
144
|
+
return (_jsxs(Select, { value: value ?? "all", onValueChange: (nextValue) => onChange(nextValue === "all" ? null : nextValue), children: [_jsx(SelectTrigger, { className: "h-8 w-[210px] text-xs", "aria-label": "Filter usage by user", children: _jsx(SelectValue, { placeholder: "All users" }) }), _jsx(SelectContent, { children: _jsxs(SelectGroup, { children: [_jsx(SelectItem, { value: "all", children: "All users" }), users.map((user) => (_jsx(SelectItem, { value: user.email, children: user.email }, user.email)))] }) })] }));
|
|
145
|
+
}
|
|
146
|
+
function UsageTrend({ rows, billing, }) {
|
|
147
|
+
const chartData = completeTrendRows(rows).map((row) => ({
|
|
148
|
+
...row,
|
|
149
|
+
spend: displayAmountFromCostCents(row.costCents, billing),
|
|
150
|
+
}));
|
|
151
|
+
return (_jsx(Panel, { title: billing.unit === "builder-credits"
|
|
152
|
+
? "Credit usage trend"
|
|
153
|
+
: "Usage trend", icon: _jsx(IconChartBar, { size: 16 }), action: _jsxs("div", { className: "hidden items-center gap-3 text-[11px] text-muted-foreground sm:flex", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "size-2 rounded-full bg-[hsl(var(--dispatch-brand-blue))]" }), billing.shortLabel] }), _jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "size-2 rounded-full bg-muted-foreground" }), "Calls"] })] }), children: chartData.length === 0 ? (_jsx("div", { className: "flex min-h-56 items-center justify-center rounded-md border border-dashed px-4 text-sm text-muted-foreground", children: "No usage in this window yet." })) : (_jsx(ChartContainer, { config: {
|
|
154
|
+
spend: {
|
|
155
|
+
label: billing.shortLabel,
|
|
156
|
+
color: "hsl(var(--dispatch-brand-blue))",
|
|
157
|
+
},
|
|
158
|
+
calls: {
|
|
159
|
+
label: "Calls",
|
|
160
|
+
color: "hsl(var(--muted-foreground))",
|
|
161
|
+
},
|
|
162
|
+
}, className: "h-[280px] w-full aspect-auto", children: _jsxs(AreaChart, { data: chartData, margin: { top: 8, right: 8, left: 12, bottom: 0 }, children: [_jsx("defs", { children: _jsxs("linearGradient", { id: "usage-trend-fill", x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: "var(--color-spend)", stopOpacity: 0.24 }), _jsx("stop", { offset: "100%", stopColor: "var(--color-spend)", stopOpacity: 0 })] }) }), _jsx(CartesianGrid, { vertical: false, stroke: "hsl(var(--border))", strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "date", stroke: "hsl(var(--muted-foreground))", fontSize: 11, axisLine: false, tickLine: false, tickMargin: 8, minTickGap: 28, tickFormatter: formatTrendDate }), _jsx(YAxis, { yAxisId: "spend", stroke: "hsl(var(--muted-foreground))", fontSize: 11, axisLine: false, tickLine: false, tickMargin: 8, width: 50, tickFormatter: (value) => billing.unit === "builder-credits"
|
|
163
|
+
? formatCredits(Number(value))
|
|
164
|
+
: formatSpend(Number(value), billing) }), _jsx(YAxis, { yAxisId: "calls", orientation: "right", hide: true }), _jsx(ChartTooltip, { cursor: false, content: _jsx(ChartTooltipContent, { labelFormatter: (value) => formatTrendDate(String(value)), formatter: (value, name) => [
|
|
165
|
+
name === "spend"
|
|
166
|
+
? billing.unit === "builder-credits"
|
|
167
|
+
? formatCredits(Number(value))
|
|
168
|
+
: formatSpend(Number(value), billing)
|
|
169
|
+
: `${formatNumber(Number(value))} calls`,
|
|
170
|
+
name === "spend" ? billing.shortLabel : "Calls",
|
|
171
|
+
] }) }), _jsx(Area, { yAxisId: "spend", dataKey: "spend", type: "monotone", stroke: "var(--color-spend)", strokeWidth: 2, fill: "url(#usage-trend-fill)", fillOpacity: 1, dot: false, activeDot: { r: 4, strokeWidth: 2 }, isAnimationActive: false }), _jsx(Area, { yAxisId: "calls", dataKey: "calls", type: "monotone", stroke: "var(--color-calls)", strokeWidth: 1.5, fill: "none", dot: false, activeDot: { r: 3, strokeWidth: 2 }, isAnimationActive: false })] }) })) }));
|
|
172
|
+
}
|
|
173
|
+
function ReviewUsageButton({ metrics, billing, }) {
|
|
174
|
+
function reviewUsage() {
|
|
175
|
+
const topApps = metrics.byApp
|
|
176
|
+
.slice(0, 5)
|
|
177
|
+
.map((row) => `${displayApp(row.key)}: ${formatSpend(row.costCents, billing)} / ${row.calls} calls`)
|
|
178
|
+
.join("; ");
|
|
179
|
+
const topLabels = metrics.byLabel
|
|
180
|
+
.slice(0, 5)
|
|
181
|
+
.map((row) => `${row.label}: ${formatSpend(row.costCents, billing)} / ${row.calls} calls`)
|
|
182
|
+
.join("; ");
|
|
183
|
+
const recentPrompts = metrics.recent
|
|
184
|
+
.slice(0, 8)
|
|
185
|
+
.map((row) => {
|
|
186
|
+
const prompt = row.prompt
|
|
187
|
+
? row.prompt.slice(0, 180)
|
|
188
|
+
: "prompt not captured";
|
|
189
|
+
return `${timeAgo(row.createdAt)} | ${displayApp(row.app)} | ${row.label} | ${prompt}`;
|
|
190
|
+
})
|
|
191
|
+
.join("\n");
|
|
192
|
+
sendToAgentChat({
|
|
193
|
+
message: "Review this LLM usage and explain where the spend is going. Identify repeated, background, or unexpectedly expensive work, cite the strongest evidence, and suggest concrete fixes. Call out missing attribution instead of guessing.",
|
|
194
|
+
context: [
|
|
195
|
+
`Dispatch usage scope: ${metrics.viewScope === "workspace" ? "workspace" : "my account"}.`,
|
|
196
|
+
`Lookback: ${metrics.sinceDays} days. Viewer: ${metrics.access.viewerEmail}.`,
|
|
197
|
+
`Total: ${formatSpend(metrics.totals.costCents, billing)}, ${metrics.totals.calls} calls, ${metrics.totals.chatCalls} chat calls, ${formatTokens(metrics.totals.inputTokens + metrics.totals.outputTokens)} input/output tokens.`,
|
|
198
|
+
`Top apps: ${topApps || "none"}.`,
|
|
199
|
+
`Top work types: ${topLabels || "none"}.`,
|
|
200
|
+
`Recent prompt evidence (bounded to the latest 8 rows):\n${recentPrompts || "none"}`,
|
|
201
|
+
].join("\n"),
|
|
202
|
+
submit: true,
|
|
203
|
+
openSidebar: true,
|
|
204
|
+
chatTarget: "local",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: reviewUsage, children: "Ask agent" }));
|
|
208
|
+
}
|
|
105
209
|
function AppSpendRows({ rows, billing, }) {
|
|
106
210
|
const max = maxSpend(rows, billing);
|
|
107
211
|
if (rows.length === 0) {
|
|
@@ -111,13 +215,6 @@ function AppSpendRows({ rows, billing, }) {
|
|
|
111
215
|
width: barWidth(displayAmountFromCostCents(row.costCents, billing), max),
|
|
112
216
|
} }) })] }, row.key))) }));
|
|
113
217
|
}
|
|
114
|
-
function DailyActivity({ rows }) {
|
|
115
|
-
const max = Math.max(1, rows.reduce((value, row) => Math.max(value, row.calls), 0));
|
|
116
|
-
if (rows.length === 0) {
|
|
117
|
-
return (_jsx("div", { className: "rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground", children: "No activity in this window." }));
|
|
118
|
-
}
|
|
119
|
-
return (_jsx("div", { className: "flex h-44 items-end gap-1", children: rows.map((row) => (_jsxs("div", { className: "group flex min-w-0 flex-1 flex-col items-center gap-2", children: [_jsx("div", { className: "relative flex h-36 w-full items-end rounded-sm bg-muted/60", children: _jsx("div", { className: "w-full rounded-sm bg-foreground transition group-hover:bg-primary", style: { height: `${Math.max(4, (row.calls / max) * 100)}%` } }) }), _jsx("div", { className: "hidden text-[10px] text-muted-foreground sm:block", children: row.date.slice(5) })] }, row.date))) }));
|
|
120
|
-
}
|
|
121
218
|
function AppAccessTable({ rows, billing, }) {
|
|
122
219
|
const visibleRows = rows.filter((row) => !row.isDispatch);
|
|
123
220
|
if (visibleRows.length === 0) {
|
|
@@ -145,14 +242,38 @@ function CompactBreakdown({ rows, empty, billing, }) {
|
|
|
145
242
|
}
|
|
146
243
|
function RecentTable({ rows, billing, }) {
|
|
147
244
|
if (rows.length === 0) {
|
|
148
|
-
return (_jsx("div", { className: "rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground", children: "No
|
|
245
|
+
return (_jsx("div", { className: "rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground", children: "No prompts or LLM calls in this window." }));
|
|
149
246
|
}
|
|
150
|
-
return (_jsx("div", { className: "
|
|
247
|
+
return (_jsx("div", { className: "divide-y", children: rows.slice(0, 10).map((row) => (_jsxs("article", { className: "-mx-1 px-1 py-3 first:pt-0 last:pb-0", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsx("p", { className: "min-w-0 whitespace-pre-wrap text-sm leading-6 text-foreground", children: row.prompt ||
|
|
248
|
+
(row.promptSource === "unavailable"
|
|
249
|
+
? "Prompt unavailable - linked thread data could not be read."
|
|
250
|
+
: "Prompt not captured for this call.") }), _jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: timeAgo(row.createdAt) })] }), _jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(Badge, { variant: "outline", children: displayApp(row.app) }), _jsx(Badge, { variant: "secondary", children: row.label }), _jsx("span", { children: row.model }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { children: formatSpend(row.costCents, billing) }), row.ownerEmail ? (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "max-w-64 truncate", children: row.ownerEmail })] })) : null, row.threadId ? (_jsxs(Link, { to: `/admin/thread-debug?threadId=${encodeURIComponent(row.threadId)}`, className: "inline-flex items-center gap-1 rounded-md border px-2 py-0.5 font-medium text-foreground transition-colors hover:bg-muted", children: ["Inspect thread", _jsx(IconArrowUpRight, { size: 12 })] })) : null] })] }, row.id))) }));
|
|
151
251
|
}
|
|
152
252
|
export default function MetricsRoute() {
|
|
153
253
|
const t = useT();
|
|
154
254
|
const [sinceDays, setSinceDays] = useState(30);
|
|
155
|
-
const
|
|
255
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
256
|
+
const scope = searchParams.get("scope") === "workspace" ? "workspace" : "me";
|
|
257
|
+
const userEmail = scope === "workspace" ? searchParams.get("user") || null : null;
|
|
258
|
+
function setScope(nextScope) {
|
|
259
|
+
const next = new URLSearchParams(searchParams);
|
|
260
|
+
if (nextScope === "me")
|
|
261
|
+
next.delete("scope");
|
|
262
|
+
else
|
|
263
|
+
next.set("scope", nextScope);
|
|
264
|
+
if (nextScope === "me")
|
|
265
|
+
next.delete("user");
|
|
266
|
+
setSearchParams(next, { replace: true });
|
|
267
|
+
}
|
|
268
|
+
function setUserEmail(nextUserEmail) {
|
|
269
|
+
const next = new URLSearchParams(searchParams);
|
|
270
|
+
if (nextUserEmail)
|
|
271
|
+
next.set("user", nextUserEmail);
|
|
272
|
+
else
|
|
273
|
+
next.delete("user");
|
|
274
|
+
setSearchParams(next, { replace: true });
|
|
275
|
+
}
|
|
276
|
+
const { data, isLoading, error } = useActionQuery("list-dispatch-usage-metrics", { sinceDays, scope, userEmail: userEmail ?? undefined });
|
|
156
277
|
const metrics = data;
|
|
157
278
|
const billing = metrics?.billing ?? USD_BILLING;
|
|
158
279
|
const totalTokens = useMemo(() => {
|
|
@@ -165,12 +286,20 @@ export default function MetricsRoute() {
|
|
|
165
286
|
}, [metrics]);
|
|
166
287
|
return (_jsx(DispatchShell, { title: t("dispatch.nav.metrics"), description: billing.unit === "builder-credits"
|
|
167
288
|
? t("dispatch.pages.metricsDescriptionBuilder")
|
|
168
|
-
: t("dispatch.pages.metricsDescriptionLlm"), children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("div", { className: "text-sm text-
|
|
169
|
-
|
|
170
|
-
|
|
289
|
+
: t("dispatch.pages.metricsDescriptionLlm"), children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: metrics?.selectedUserEmail
|
|
290
|
+
? `${metrics.selectedUserEmail}'s usage`
|
|
291
|
+
: scope === "workspace"
|
|
292
|
+
? "Workspace usage"
|
|
293
|
+
: "Your usage" }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: metrics?.selectedUserEmail
|
|
294
|
+
? "Filtered to this workspace member"
|
|
295
|
+
: scope === "workspace"
|
|
296
|
+
? `${metrics?.access.totalUsers ?? 0} users with access`
|
|
297
|
+
: metrics?.access.viewerEmail || "Signed-in account" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(ScopeSelector, { value: scope, onChange: setScope }), scope === "workspace" && metrics ? (_jsx(UserSelector, { value: metrics.selectedUserEmail ?? userEmail, users: metrics.availableUsers ?? [], onChange: setUserEmail })) : null, _jsx(RangeSelector, { value: sinceDays, onChange: setSinceDays })] })] }), error ? (_jsxs(Alert, { variant: "destructive", children: [_jsx(IconAlertTriangle, { className: "h-4 w-4" }), _jsx(AlertTitle, { children: t("dispatch.pages.metricsUnavailable") }), _jsx(AlertDescription, { children: error instanceof Error
|
|
171
298
|
? error.message
|
|
172
|
-
: t("dispatch.pages.unableToLoadUsage") })] })) : null, isLoading && !metrics ? _jsx(LoadingMetrics, {}) : null, metrics ? (_jsxs(_Fragment, { children: [
|
|
173
|
-
|
|
174
|
-
|
|
299
|
+
: t("dispatch.pages.unableToLoadUsage") })] })) : null, isLoading && !metrics ? _jsx(LoadingMetrics, {}) : null, metrics ? (_jsxs(_Fragment, { children: [_jsx(UsageTrend, { rows: metrics.daily, billing: billing }), _jsxs("div", { className: "flex items-center justify-between gap-3 border-y py-3", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "Review this usage with the agent" }), _jsx(ReviewUsageButton, { metrics: metrics, billing: billing })] }), _jsx(UsageAlertsPanel, { scope: scope === "workspace" ? "workspace" : "user", appOptions: metrics.byApp
|
|
300
|
+
.filter((row) => row.key && row.key !== "unattributed")
|
|
301
|
+
.map((row) => ({ id: row.key, label: displayApp(row.key) })) }), _jsxs("div", { className: "grid gap-3 md:grid-cols-2 xl:grid-cols-5", children: [_jsx(MetricCard, { label: billing.label, value: formatSpend(metrics.totals.costCents, billing), detail: `${formatTokens(totalTokens)} total tokens`, icon: _jsx(IconCoin, { size: 17 }) }), _jsx(MetricCard, { label: t("dispatch.pages.llmCalls"), value: formatNumber(metrics.totals.calls), detail: `${formatNumber(metrics.totals.chatCalls)} chat turns`, icon: _jsx(IconActivity, { size: 17 }) }), _jsx(MetricCard, { label: t("dispatch.pages.activeUsers"), value: formatNumber(metrics.totals.activeUsers), detail: `${formatNumber(metrics.access.totalUsers)} users with access`, icon: _jsx(IconUsersGroup, { size: 17 }) }), _jsx(MetricCard, { label: t("dispatch.pages.workspaceAppsStat"), value: formatNumber(metrics.totals.workspaceApps), detail: `${formatNumber(metrics.byApp.length)} with usage`, icon: _jsx(IconApps, { size: 17 }) }), _jsx(MetricCard, { label: t("dispatch.pages.chatThreads"), value: formatNumber(metrics.totals.chatThreads), detail: `${formatNumber(metrics.totals.chatMessages)} messages`, icon: _jsx(IconMessages, { size: 17 }) })] }), _jsx(Panel, { title: billing.unit === "builder-credits"
|
|
302
|
+
? "Credit spend by app"
|
|
303
|
+
: "Spend by app", icon: _jsx(IconApps, { size: 16 }), children: _jsx(AppSpendRows, { rows: metrics.byApp, billing: billing }) }), _jsx(Panel, { title: "Recent prompts", icon: _jsx(IconMessages, { size: 16 }), action: _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Latest ", Math.min(metrics.recent.length, 10), " of", " ", metrics.recent.length] }), children: _jsx(RecentTable, { rows: metrics.recent, billing: billing }) }), _jsx(Panel, { title: "Access By App", icon: _jsx(IconApps, { size: 16 }), children: _jsx(AppAccessTable, { rows: metrics.appAccess, billing: billing }) }), _jsx(Panel, { title: "Users", icon: _jsx(IconUsersGroup, { size: 16 }), children: _jsx(UserTable, { rows: metrics.byUser, billing: billing }) }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsx(Panel, { title: "Models", icon: _jsx(IconChartBar, { size: 16 }), children: _jsx(CompactBreakdown, { rows: metrics.byModel, empty: "No model usage in this window.", billing: billing }) }), _jsx(Panel, { title: "Work Types", icon: _jsx(IconActivity, { size: 16 }), children: _jsx(CompactBreakdown, { rows: metrics.byLabel, empty: "No labeled usage in this window.", billing: billing }) })] })] })) : null] }) }));
|
|
175
304
|
}
|
|
176
305
|
//# sourceMappingURL=metrics.js.map
|