@agent-native/dispatch 0.14.4 → 0.14.6
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/components/task-queue-health.d.ts.map +1 -1
- package/dist/components/task-queue-health.js +9 -1
- package/dist/components/task-queue-health.js.map +1 -1
- package/dist/routes/pages/apps.d.ts.map +1 -1
- package/dist/routes/pages/apps.js +4 -42
- package/dist/routes/pages/apps.js.map +1 -1
- package/dist/routes/pages/automations.d.ts.map +1 -1
- package/dist/routes/pages/automations.js +0 -1
- package/dist/routes/pages/automations.js.map +1 -1
- package/dist/routes/pages/metrics.d.ts.map +1 -1
- package/dist/routes/pages/metrics.js +1 -1
- package/dist/routes/pages/metrics.js.map +1 -1
- package/package.json +2 -2
- package/src/components/task-queue-health.tsx +9 -1
- package/src/routes/pages/apps.tsx +1 -178
- package/src/routes/pages/automations.tsx +0 -1
- package/src/routes/pages/chat.spec.tsx +6 -0
- package/src/routes/pages/metrics.tsx +0 -1
- package/src/styles/dispatch.css +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-queue-health.d.ts","sourceRoot":"","sources":["../../src/components/task-queue-health.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-queue-health.d.ts","sourceRoot":"","sources":["../../src/components/task-queue-health.tsx"],"names":[],"mappings":"AAgEA,wBAAgB,eAAe,oDA6G9B"}
|
|
@@ -17,7 +17,15 @@ function useTaskQueueStats() {
|
|
|
17
17
|
queryKey: [...TASK_QUEUE_QUERY_KEY, version],
|
|
18
18
|
queryFn: getDispatchTaskQueueStats,
|
|
19
19
|
placeholderData: (prev) => prev,
|
|
20
|
-
|
|
20
|
+
// Queue work can finish outside the action transport, so retain a bounded
|
|
21
|
+
// fallback only here. Idle workspaces back off aggressively; active queues
|
|
22
|
+
// keep the existing monitoring cadence.
|
|
23
|
+
refetchInterval: (query) => {
|
|
24
|
+
const stats = query.state.data;
|
|
25
|
+
return stats && (stats.pending > 0 || stats.processing > 0)
|
|
26
|
+
? 15_000
|
|
27
|
+
: 300_000;
|
|
28
|
+
},
|
|
21
29
|
staleTime: 5_000,
|
|
22
30
|
});
|
|
23
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-queue-health.js","sourceRoot":"","sources":["../../src/components/task-queue-health.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useChangeVersions, useT } from \"@agent-native/core/client\";\nimport { IconAlertTriangle, IconPlayerPlay } from \"@tabler/icons-react\";\nimport { useQuery } from \"@tanstack/react-query\";\n\nimport {\n formatQueueAgeSeconds,\n getDispatchTaskQueueStats,\n type TaskQueueStats,\n ZERO_TASK_QUEUE_STATS,\n} from \"../lib/task-queue\";\nimport { cn } from \"../lib/utils\";\nimport { Skeleton } from \"./ui/skeleton\";\n\nconst TASK_QUEUE_QUERY_KEY = [\"dispatch-task-queue\"] as const;\n\nfunction formatNumber(value: number): string {\n return new Intl.NumberFormat(undefined, {\n notation: value >= 10_000 ? \"compact\" : \"standard\",\n }).format(value);\n}\n\nfunction useTaskQueueStats() {\n const version = useChangeVersions([\"action\", \"screen-refresh\"]);\n return useQuery<TaskQueueStats>({\n queryKey: [...TASK_QUEUE_QUERY_KEY, version],\n queryFn: getDispatchTaskQueueStats,\n placeholderData: (prev) => prev,\n refetchInterval: 15_000,\n staleTime: 5_000,\n });\n}\n\nfunction QueueCell({\n label,\n value,\n danger,\n}: {\n label: string;\n value: number;\n danger?: boolean;\n}) {\n return (\n <div className=\"rounded-md border bg-background px-2 py-2\">\n <div\n className={cn(\n \"text-lg font-semibold text-foreground\",\n danger && \"text-destructive\",\n )}\n >\n {formatNumber(value)}\n </div>\n <div className=\"truncate text-[11px] text-muted-foreground\">{label}</div>\n </div>\n );\n}\n\nexport function TaskQueueHealth() {\n const t = useT();\n const query = useTaskQueueStats();\n const taskQueue = query.data ?? ZERO_TASK_QUEUE_STATS;\n const hasFailure = taskQueue.failed_last_hour > 0;\n const hasBacklog =\n taskQueue.pending > 5 || taskQueue.oldest_pending_age_seconds > 300;\n const oldestAge = formatQueueAgeSeconds(taskQueue.oldest_pending_age_seconds);\n const oldestAgeLabel =\n oldestAge === \"none\" ? t(\"dispatch.pages.queueAgeNone\") : oldestAge;\n\n return (\n <section className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex min-w-0 items-center justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <IconPlayerPlay\n size={16}\n className=\"shrink-0 text-muted-foreground\"\n />\n <div className=\"min-w-0\">\n <h2 className=\"truncate text-sm font-semibold text-foreground\">\n {t(\"dispatch.pages.deliveryQueue\")}\n </h2>\n <p className=\"mt-0.5 truncate text-xs text-muted-foreground\">\n {hasFailure\n ? t(\"dispatch.pages.failedLastHour\", {\n count: taskQueue.failed_last_hour,\n })\n : t(\"dispatch.pages.processingCount\", {\n count: taskQueue.processing,\n })}\n </p>\n </div>\n </div>\n </div>\n\n {query.isLoading && !query.data ? (\n <div className=\"mt-3 grid grid-cols-2 gap-2 sm:grid-cols-4\">\n {Array.from({ length: 4 }).map((_, index) => (\n <Skeleton key={index} className=\"h-14 rounded-md\" />\n ))}\n </div>\n ) : (\n <>\n <div className=\"mt-3 grid grid-cols-2 gap-2 sm:grid-cols-4\">\n <QueueCell\n label={t(\"dispatch.pages.queued\")}\n value={taskQueue.pending}\n />\n <QueueCell\n label={t(\"dispatch.pages.active\")}\n value={taskQueue.processing}\n />\n <QueueCell\n label={t(\"dispatch.pages.done1h\")}\n value={taskQueue.completed_last_hour}\n />\n <QueueCell\n label={t(\"dispatch.pages.failed1h\")}\n value={taskQueue.failed_last_hour}\n danger={hasFailure}\n />\n </div>\n\n <div\n className={cn(\n \"mt-3 rounded-md border px-3 py-2 text-xs\",\n hasFailure\n ? \"border-destructive/30 bg-destructive/5 text-destructive\"\n : hasBacklog\n ? \"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n : \"bg-muted/20 text-muted-foreground\",\n )}\n >\n {t(\"dispatch.pages.oldestQueued\", { age: oldestAgeLabel })}\n </div>\n\n {hasFailure ? (\n <div className=\"mt-3 flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive\">\n <IconAlertTriangle size={14} className=\"mt-0.5 shrink-0\" />\n <span>{t(\"dispatch.pages.queueFailureHint\")}</span>\n </div>\n ) : null}\n\n {taskQueue.recent_failures.length > 0 ? (\n <div className=\"mt-3 divide-y rounded-md border\">\n {taskQueue.recent_failures.slice(0, 3).map((failure) => (\n <div key={failure.id} className=\"px-3 py-2\">\n <div className=\"flex items-center justify-between gap-3 text-xs\">\n <span className=\"font-medium text-foreground\">\n {failure.platform || t(\"dispatch.pages.unknownPlatform\")}\n </span>\n <span className=\"text-muted-foreground\">\n {t(\"dispatch.pages.attemptsCount\", {\n count: failure.attempts,\n })}\n </span>\n </div>\n <div className=\"mt-1 truncate text-xs text-muted-foreground\">\n {failure.error || t(\"dispatch.pages.noErrorMessage\")}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </>\n )}\n </section>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"task-queue-health.js","sourceRoot":"","sources":["../../src/components/task-queue-health.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useChangeVersions, useT } from \"@agent-native/core/client\";\nimport { IconAlertTriangle, IconPlayerPlay } from \"@tabler/icons-react\";\nimport { useQuery } from \"@tanstack/react-query\";\n\nimport {\n formatQueueAgeSeconds,\n getDispatchTaskQueueStats,\n type TaskQueueStats,\n ZERO_TASK_QUEUE_STATS,\n} from \"../lib/task-queue\";\nimport { cn } from \"../lib/utils\";\nimport { Skeleton } from \"./ui/skeleton\";\n\nconst TASK_QUEUE_QUERY_KEY = [\"dispatch-task-queue\"] as const;\n\nfunction formatNumber(value: number): string {\n return new Intl.NumberFormat(undefined, {\n notation: value >= 10_000 ? \"compact\" : \"standard\",\n }).format(value);\n}\n\nfunction useTaskQueueStats() {\n const version = useChangeVersions([\"action\", \"screen-refresh\"]);\n return useQuery<TaskQueueStats>({\n queryKey: [...TASK_QUEUE_QUERY_KEY, version],\n queryFn: getDispatchTaskQueueStats,\n placeholderData: (prev) => prev,\n // Queue work can finish outside the action transport, so retain a bounded\n // fallback only here. Idle workspaces back off aggressively; active queues\n // keep the existing monitoring cadence.\n refetchInterval: (query) => {\n const stats = query.state.data;\n return stats && (stats.pending > 0 || stats.processing > 0)\n ? 15_000\n : 300_000;\n },\n staleTime: 5_000,\n });\n}\n\nfunction QueueCell({\n label,\n value,\n danger,\n}: {\n label: string;\n value: number;\n danger?: boolean;\n}) {\n return (\n <div className=\"rounded-md border bg-background px-2 py-2\">\n <div\n className={cn(\n \"text-lg font-semibold text-foreground\",\n danger && \"text-destructive\",\n )}\n >\n {formatNumber(value)}\n </div>\n <div className=\"truncate text-[11px] text-muted-foreground\">{label}</div>\n </div>\n );\n}\n\nexport function TaskQueueHealth() {\n const t = useT();\n const query = useTaskQueueStats();\n const taskQueue = query.data ?? ZERO_TASK_QUEUE_STATS;\n const hasFailure = taskQueue.failed_last_hour > 0;\n const hasBacklog =\n taskQueue.pending > 5 || taskQueue.oldest_pending_age_seconds > 300;\n const oldestAge = formatQueueAgeSeconds(taskQueue.oldest_pending_age_seconds);\n const oldestAgeLabel =\n oldestAge === \"none\" ? t(\"dispatch.pages.queueAgeNone\") : oldestAge;\n\n return (\n <section className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex min-w-0 items-center justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <IconPlayerPlay\n size={16}\n className=\"shrink-0 text-muted-foreground\"\n />\n <div className=\"min-w-0\">\n <h2 className=\"truncate text-sm font-semibold text-foreground\">\n {t(\"dispatch.pages.deliveryQueue\")}\n </h2>\n <p className=\"mt-0.5 truncate text-xs text-muted-foreground\">\n {hasFailure\n ? t(\"dispatch.pages.failedLastHour\", {\n count: taskQueue.failed_last_hour,\n })\n : t(\"dispatch.pages.processingCount\", {\n count: taskQueue.processing,\n })}\n </p>\n </div>\n </div>\n </div>\n\n {query.isLoading && !query.data ? (\n <div className=\"mt-3 grid grid-cols-2 gap-2 sm:grid-cols-4\">\n {Array.from({ length: 4 }).map((_, index) => (\n <Skeleton key={index} className=\"h-14 rounded-md\" />\n ))}\n </div>\n ) : (\n <>\n <div className=\"mt-3 grid grid-cols-2 gap-2 sm:grid-cols-4\">\n <QueueCell\n label={t(\"dispatch.pages.queued\")}\n value={taskQueue.pending}\n />\n <QueueCell\n label={t(\"dispatch.pages.active\")}\n value={taskQueue.processing}\n />\n <QueueCell\n label={t(\"dispatch.pages.done1h\")}\n value={taskQueue.completed_last_hour}\n />\n <QueueCell\n label={t(\"dispatch.pages.failed1h\")}\n value={taskQueue.failed_last_hour}\n danger={hasFailure}\n />\n </div>\n\n <div\n className={cn(\n \"mt-3 rounded-md border px-3 py-2 text-xs\",\n hasFailure\n ? \"border-destructive/30 bg-destructive/5 text-destructive\"\n : hasBacklog\n ? \"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\"\n : \"bg-muted/20 text-muted-foreground\",\n )}\n >\n {t(\"dispatch.pages.oldestQueued\", { age: oldestAgeLabel })}\n </div>\n\n {hasFailure ? (\n <div className=\"mt-3 flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs text-destructive\">\n <IconAlertTriangle size={14} className=\"mt-0.5 shrink-0\" />\n <span>{t(\"dispatch.pages.queueFailureHint\")}</span>\n </div>\n ) : null}\n\n {taskQueue.recent_failures.length > 0 ? (\n <div className=\"mt-3 divide-y rounded-md border\">\n {taskQueue.recent_failures.slice(0, 3).map((failure) => (\n <div key={failure.id} className=\"px-3 py-2\">\n <div className=\"flex items-center justify-between gap-3 text-xs\">\n <span className=\"font-medium text-foreground\">\n {failure.platform || t(\"dispatch.pages.unknownPlatform\")}\n </span>\n <span className=\"text-muted-foreground\">\n {t(\"dispatch.pages.attemptsCount\", {\n count: failure.attempts,\n })}\n </span>\n </div>\n <div className=\"mt-1 truncate text-xs text-muted-foreground\">\n {failure.error || t(\"dispatch.pages.noErrorMessage\")}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </>\n )}\n </section>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/apps.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/apps.tsx"],"names":[],"mappings":"AAuBA,wBAAgB,IAAI;;IAEnB;AAQD,MAAM,CAAC,OAAO,UAAU,SAAS,oDAqJhC"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { IconApps,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useActionQuery, useT } from "@agent-native/core/client";
|
|
3
|
+
import { IconApps, IconChevronDown, IconEyeOff, IconPlus, } from "@tabler/icons-react";
|
|
4
4
|
import { useState } from "react";
|
|
5
|
-
import { toast } from "sonner";
|
|
6
5
|
import { ActionQueryError } from "../../components/action-query-error.js";
|
|
7
6
|
import { CreateAppPopover } from "../../components/create-app-popover.js";
|
|
8
7
|
import { DispatchShell } from "../../components/dispatch-shell.js";
|
|
@@ -14,36 +13,20 @@ import { cn } from "../../lib/utils.js";
|
|
|
14
13
|
export function meta() {
|
|
15
14
|
return [{ title: "Apps — Dispatch" }];
|
|
16
15
|
}
|
|
17
|
-
const TEMPLATE_ICONS = {
|
|
18
|
-
Mail: IconMail,
|
|
19
|
-
CalendarMonth: IconCalendarMonth,
|
|
20
|
-
FileText: IconFileText,
|
|
21
|
-
Presentation: IconPresentation,
|
|
22
|
-
ScreenShare: IconScreenShare,
|
|
23
|
-
Brain: IconBrain,
|
|
24
|
-
Photo: IconPhoto,
|
|
25
|
-
ChartBar: IconChartBar,
|
|
26
|
-
ClipboardList: IconClipboardList,
|
|
27
|
-
Brush: IconBrush,
|
|
28
|
-
};
|
|
29
16
|
export default function AppsRoute() {
|
|
30
17
|
const t = useT();
|
|
31
18
|
const [showHidden, setShowHidden] = useState(false);
|
|
32
|
-
const [templatesOpen, setTemplatesOpen] = useState(false);
|
|
33
19
|
const appsQuery = useActionQuery("list-workspace-apps", {
|
|
34
20
|
includeAgentCards: false,
|
|
35
21
|
includeArchived: true,
|
|
36
22
|
});
|
|
37
23
|
const { data: apps = [], isLoading: appsLoading } = appsQuery;
|
|
38
24
|
const { data: workspace } = useActionQuery("get-workspace-info", {}, { staleTime: 60_000 });
|
|
39
|
-
const templatesQuery = useActionQuery("list-available-workspace-templates", {}, { refetchInterval: 5_000 });
|
|
40
|
-
const { data: templates = [], isLoading: templatesLoading } = templatesQuery;
|
|
41
25
|
const ws = workspace;
|
|
42
26
|
const workspaceLabel = ws?.displayName ?? ws?.name ?? null;
|
|
43
27
|
const allApps = apps.filter((app) => !app.isDispatch);
|
|
44
28
|
const visibleApps = allApps.filter((app) => !app.archived);
|
|
45
29
|
const archivedApps = allApps.filter((app) => app.archived);
|
|
46
|
-
const typedTemplates = templates;
|
|
47
30
|
const showAppSkeletons = appsLoading && allApps.length === 0;
|
|
48
31
|
return (_jsx(DispatchShell, { title: t("dispatch.nav.apps"), description: workspaceLabel
|
|
49
32
|
? t("dispatch.pages.appsDescriptionWithWorkspace", {
|
|
@@ -59,13 +42,7 @@ export default function AppsRoute() {
|
|
|
59
42
|
? ` · ${t("dispatch.pages.hiddenCount", {
|
|
60
43
|
count: archivedApps.length,
|
|
61
44
|
})}`
|
|
62
|
-
: ""] })] })] }), visibleApps.length > 0 ? (_jsx(CreateAppPopover, { align: "end", trigger: _jsxs(Button, { size: "sm", children: [_jsx(IconPlus, { size: 15, className: "mr-1.5" }), t("dispatch.pages.createApp")] }) })) : null] }), appsQuery.isError ? (_jsx(ActionQueryError, { error: appsQuery.error, onRetry: () => void appsQuery.refetch() })) : showAppSkeletons ? (_jsx(AppsSkeletonGrid, {})) : visibleApps.length > 0 ? (_jsx("div", { className: "grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3", children: visibleApps.map((app) => (_jsx(WorkspaceAppCard, { app: app, className: "h-full" }, app.id))) })) : (_jsx(EmptyAppsState, {}))] }),
|
|
63
|
-
? t("dispatch.pages.checkingTemplates")
|
|
64
|
-
: t("dispatch.pages.templatesAvailable", {
|
|
65
|
-
count: typedTemplates.length,
|
|
66
|
-
}) })] })] }), _jsx(CollapsibleTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "gap-1.5", children: [templatesOpen
|
|
67
|
-
? t("dispatch.pages.hide")
|
|
68
|
-
: t("dispatch.pages.show"), _jsx(IconChevronDown, { size: 14, className: cn("transition-transform", templatesOpen && "rotate-180") })] }) })] }), _jsx(CollapsibleContent, { children: templatesLoading && typedTemplates.length === 0 ? (_jsx(AppsSkeletonGrid, {})) : (_jsx("div", { className: "grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3", children: typedTemplates.map((template) => (_jsx(AddTemplateCard, { template: template }, template.name))) })) })] }) })) : null, archivedApps.length > 0 ? (_jsx(Collapsible, { open: showHidden, onOpenChange: setShowHidden, children: _jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t pt-4", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(IconEyeOff, { size: 16, className: "shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "text-sm font-semibold text-foreground", children: t("dispatch.pages.hiddenApps") }), _jsx("p", { className: "text-xs text-muted-foreground", children: t("dispatch.pages.hiddenAppCount", {
|
|
45
|
+
: ""] })] })] }), visibleApps.length > 0 ? (_jsx(CreateAppPopover, { align: "end", trigger: _jsxs(Button, { size: "sm", children: [_jsx(IconPlus, { size: 15, className: "mr-1.5" }), t("dispatch.pages.createApp")] }) })) : null] }), appsQuery.isError ? (_jsx(ActionQueryError, { error: appsQuery.error, onRetry: () => void appsQuery.refetch() })) : showAppSkeletons ? (_jsx(AppsSkeletonGrid, {})) : visibleApps.length > 0 ? (_jsx("div", { className: "grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3", children: visibleApps.map((app) => (_jsx(WorkspaceAppCard, { app: app, className: "h-full" }, app.id))) })) : (_jsx(EmptyAppsState, {}))] }), archivedApps.length > 0 ? (_jsx(Collapsible, { open: showHidden, onOpenChange: setShowHidden, children: _jsxs("section", { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t pt-4", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(IconEyeOff, { size: 16, className: "shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "text-sm font-semibold text-foreground", children: t("dispatch.pages.hiddenApps") }), _jsx("p", { className: "text-xs text-muted-foreground", children: t("dispatch.pages.hiddenAppCount", {
|
|
69
46
|
count: archivedApps.length,
|
|
70
47
|
}) })] })] }), _jsx(CollapsibleTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "gap-1.5", children: [showHidden
|
|
71
48
|
? t("dispatch.pages.hide")
|
|
@@ -78,19 +55,4 @@ function EmptyAppsState() {
|
|
|
78
55
|
const t = useT();
|
|
79
56
|
return (_jsxs("div", { className: "rounded-lg border border-dashed bg-card px-4 py-10 text-center", children: [_jsx("div", { className: "mx-auto flex size-10 items-center justify-center rounded-lg bg-muted text-muted-foreground", children: _jsx(IconApps, { size: 18 }) }), _jsx("h3", { className: "mt-3 text-sm font-semibold text-foreground", children: t("dispatch.pages.noWorkspaceApps") }), _jsx("p", { className: "mx-auto mt-1 max-w-sm text-sm text-muted-foreground", children: t("dispatch.pages.noWorkspaceAppsDescription") }), _jsx("div", { className: "mt-4", children: _jsx(CreateAppPopover, { trigger: _jsxs(Button, { size: "sm", children: [_jsx(IconPlus, { size: 15, className: "mr-1.5" }), t("dispatch.pages.createApp")] }) }) })] }));
|
|
80
57
|
}
|
|
81
|
-
function AddTemplateCard({ template }) {
|
|
82
|
-
const Icon = TEMPLATE_ICONS[template.icon] ?? IconSparkles;
|
|
83
|
-
const scaffold = useActionMutation("scaffold-workspace-app", {
|
|
84
|
-
onSuccess: (result) => {
|
|
85
|
-
toast.success(`Scaffolded apps/${result?.appId || template.name}. The gateway will pick it up shortly.`);
|
|
86
|
-
},
|
|
87
|
-
onError: (err) => {
|
|
88
|
-
toast.error(`Could not scaffold ${template.label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
return (_jsxs("div", { className: "group relative flex h-full min-h-36 items-stretch gap-3 rounded-lg border bg-card p-4 transition hover:border-foreground/30", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center self-start rounded-md", style: {
|
|
92
|
-
backgroundColor: `rgb(${template.colorRgb} / 0.12)`,
|
|
93
|
-
color: template.color,
|
|
94
|
-
}, children: _jsx(Icon, { size: 18 }) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("div", { className: "flex min-w-0 items-center gap-2", children: _jsx("h3", { className: "truncate text-sm font-semibold text-foreground", children: template.label }) }), _jsx("p", { className: "mt-1 line-clamp-2 text-xs leading-relaxed text-muted-foreground", children: template.hint }), _jsx("div", { className: "mt-auto pt-3", children: _jsx(Button, { size: "sm", variant: "outline", disabled: scaffold.isPending, onClick: () => scaffold.mutate({ template: template.name }), children: scaffold.isPending ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { size: 14, className: "mr-1.5 animate-spin" }), "Adding..."] })) : (_jsxs(_Fragment, { children: [_jsx(IconPlus, { size: 14, className: "mr-1.5" }), "Add to workspace"] })) }) })] })] }));
|
|
95
|
-
}
|
|
96
58
|
//# sourceMappingURL=apps.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.js","sourceRoot":"","sources":["../../../src/routes/pages/apps.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n useActionMutation,\n useActionQuery,\n useT,\n} from \"@agent-native/core/client\";\nimport {\n IconApps,\n IconBrain,\n IconBrush,\n IconCalendarMonth,\n IconChartBar,\n IconChevronDown,\n IconClipboardList,\n IconEyeOff,\n IconFileText,\n IconLoader2,\n IconMail,\n IconPhoto,\n IconPlus,\n IconPresentation,\n IconScreenShare,\n IconSparkles,\n IconStack3,\n} from \"@tabler/icons-react\";\nimport { useState } from \"react\";\nimport { toast } from \"sonner\";\n\nimport { ActionQueryError } from \"../../components/action-query-error\";\nimport { CreateAppPopover } from \"../../components/create-app-popover\";\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 { Skeleton } from \"../../components/ui/skeleton\";\nimport { WorkspaceAppCard } from \"../../components/workspace-app-card\";\nimport { cn } from \"../../lib/utils\";\nimport type { WorkspaceAppSummary } from \"../../lib/workspace-apps\";\n\nexport function meta() {\n return [{ title: \"Apps — Dispatch\" }];\n}\n\ninterface WorkspaceInfo {\n name: string | null;\n displayName: string | null;\n appCount: number;\n}\n\ninterface AvailableTemplate {\n name: string;\n label: string;\n hint: string;\n icon: string;\n color: string;\n colorRgb: string;\n core: boolean;\n}\n\nconst TEMPLATE_ICONS: Record<string, typeof IconMail> = {\n Mail: IconMail,\n CalendarMonth: IconCalendarMonth,\n FileText: IconFileText,\n Presentation: IconPresentation,\n ScreenShare: IconScreenShare,\n Brain: IconBrain,\n Photo: IconPhoto,\n ChartBar: IconChartBar,\n ClipboardList: IconClipboardList,\n Brush: IconBrush,\n};\n\nexport default function AppsRoute() {\n const t = useT();\n const [showHidden, setShowHidden] = useState(false);\n const [templatesOpen, setTemplatesOpen] = useState(false);\n const appsQuery = useActionQuery(\"list-workspace-apps\", {\n includeAgentCards: false,\n includeArchived: true,\n });\n const { data: apps = [], isLoading: appsLoading } = appsQuery;\n const { data: workspace } = useActionQuery(\n \"get-workspace-info\",\n {},\n { staleTime: 60_000 },\n );\n const templatesQuery = useActionQuery(\n \"list-available-workspace-templates\",\n {},\n { refetchInterval: 5_000 },\n );\n const { data: templates = [], isLoading: templatesLoading } = templatesQuery;\n\n const ws = workspace as WorkspaceInfo | undefined;\n const workspaceLabel = ws?.displayName ?? ws?.name ?? null;\n const allApps = (apps as WorkspaceAppSummary[]).filter(\n (app) => !app.isDispatch,\n );\n const visibleApps = allApps.filter((app) => !app.archived);\n const archivedApps = allApps.filter((app) => app.archived);\n const typedTemplates = templates as AvailableTemplate[];\n const showAppSkeletons = appsLoading && allApps.length === 0;\n\n return (\n <DispatchShell\n title={t(\"dispatch.nav.apps\")}\n description={\n workspaceLabel\n ? t(\"dispatch.pages.appsDescriptionWithWorkspace\", {\n workspace: workspaceLabel,\n })\n : t(\"dispatch.pages.appsDescription\")\n }\n >\n <div className=\"space-y-8\">\n <section className=\"space-y-3\">\n <div className=\"flex flex-wrap items-end justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-2\">\n <IconApps\n size={16}\n className=\"mt-0.5 shrink-0 text-muted-foreground\"\n />\n <div className=\"min-w-0\">\n <h2 className=\"truncate text-sm font-semibold text-foreground\">\n {workspaceLabel\n ? t(\"dispatch.pages.appsInWorkspace\", {\n workspace: workspaceLabel,\n })\n : t(\"dispatch.pages.workspaceApps\")}\n </h2>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n {t(\"dispatch.pages.activeCount\", {\n count: visibleApps.length,\n })}\n {archivedApps.length > 0\n ? ` · ${t(\"dispatch.pages.hiddenCount\", {\n count: archivedApps.length,\n })}`\n : \"\"}\n </p>\n </div>\n </div>\n {visibleApps.length > 0 ? (\n <CreateAppPopover\n align=\"end\"\n trigger={\n <Button size=\"sm\">\n <IconPlus size={15} className=\"mr-1.5\" />\n {t(\"dispatch.pages.createApp\")}\n </Button>\n }\n />\n ) : null}\n </div>\n\n {appsQuery.isError ? (\n <ActionQueryError\n error={appsQuery.error}\n onRetry={() => void appsQuery.refetch()}\n />\n ) : showAppSkeletons ? (\n <AppsSkeletonGrid />\n ) : visibleApps.length > 0 ? (\n <div className=\"grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3\">\n {visibleApps.map((app) => (\n <WorkspaceAppCard key={app.id} app={app} className=\"h-full\" />\n ))}\n </div>\n ) : (\n <EmptyAppsState />\n )}\n </section>\n\n {templatesQuery.isError ? (\n <ActionQueryError\n error={templatesQuery.error}\n onRetry={() => void templatesQuery.refetch()}\n />\n ) : typedTemplates.length > 0 || templatesLoading ? (\n <Collapsible open={templatesOpen} onOpenChange={setTemplatesOpen}>\n <section className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-3 border-t pt-4\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <IconStack3\n size={16}\n className=\"shrink-0 text-muted-foreground\"\n />\n <div className=\"min-w-0\">\n <h2 className=\"text-sm font-semibold text-foreground\">\n {t(\"dispatch.pages.templates\")}\n </h2>\n <p className=\"text-xs text-muted-foreground\">\n {templatesLoading\n ? t(\"dispatch.pages.checkingTemplates\")\n : t(\"dispatch.pages.templatesAvailable\", {\n count: typedTemplates.length,\n })}\n </p>\n </div>\n </div>\n <CollapsibleTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-1.5\"\n >\n {templatesOpen\n ? t(\"dispatch.pages.hide\")\n : t(\"dispatch.pages.show\")}\n <IconChevronDown\n size={14}\n className={cn(\n \"transition-transform\",\n templatesOpen && \"rotate-180\",\n )}\n />\n </Button>\n </CollapsibleTrigger>\n </div>\n <CollapsibleContent>\n {templatesLoading && typedTemplates.length === 0 ? (\n <AppsSkeletonGrid />\n ) : (\n <div className=\"grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3\">\n {typedTemplates.map((template) => (\n <AddTemplateCard\n key={template.name}\n template={template}\n />\n ))}\n </div>\n )}\n </CollapsibleContent>\n </section>\n </Collapsible>\n ) : null}\n\n {archivedApps.length > 0 ? (\n <Collapsible open={showHidden} onOpenChange={setShowHidden}>\n <section className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-3 border-t pt-4\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <IconEyeOff\n size={16}\n className=\"shrink-0 text-muted-foreground\"\n />\n <div className=\"min-w-0\">\n <h2 className=\"text-sm font-semibold text-foreground\">\n {t(\"dispatch.pages.hiddenApps\")}\n </h2>\n <p className=\"text-xs text-muted-foreground\">\n {t(\"dispatch.pages.hiddenAppCount\", {\n count: archivedApps.length,\n })}\n </p>\n </div>\n </div>\n <CollapsibleTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-1.5\"\n >\n {showHidden\n ? t(\"dispatch.pages.hide\")\n : t(\"dispatch.pages.show\")}\n <IconChevronDown\n size={14}\n className={cn(\n \"transition-transform\",\n showHidden && \"rotate-180\",\n )}\n />\n </Button>\n </CollapsibleTrigger>\n </div>\n <CollapsibleContent>\n <div className=\"grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3\">\n {archivedApps.map((app) => (\n <WorkspaceAppCard\n key={app.id}\n app={app}\n className=\"h-full\"\n />\n ))}\n </div>\n </CollapsibleContent>\n </section>\n </Collapsible>\n ) : null}\n </div>\n </DispatchShell>\n );\n}\n\nfunction AppsSkeletonGrid() {\n return (\n <div className=\"grid gap-3 md:grid-cols-2 xl:grid-cols-3\">\n {Array.from({ length: 3 }).map((_, index) => (\n <div key={index} className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1 space-y-3\">\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-24\" />\n <div className=\"space-y-2 pt-1\">\n <Skeleton className=\"h-3 w-full\" />\n <Skeleton className=\"h-3 w-2/3\" />\n </div>\n </div>\n <Skeleton className=\"h-7 w-7 rounded-md\" />\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction EmptyAppsState() {\n const t = useT();\n return (\n <div className=\"rounded-lg border border-dashed bg-card px-4 py-10 text-center\">\n <div className=\"mx-auto flex size-10 items-center justify-center rounded-lg bg-muted text-muted-foreground\">\n <IconApps size={18} />\n </div>\n <h3 className=\"mt-3 text-sm font-semibold text-foreground\">\n {t(\"dispatch.pages.noWorkspaceApps\")}\n </h3>\n <p className=\"mx-auto mt-1 max-w-sm text-sm text-muted-foreground\">\n {t(\"dispatch.pages.noWorkspaceAppsDescription\")}\n </p>\n <div className=\"mt-4\">\n <CreateAppPopover\n trigger={\n <Button size=\"sm\">\n <IconPlus size={15} className=\"mr-1.5\" />\n {t(\"dispatch.pages.createApp\")}\n </Button>\n }\n />\n </div>\n </div>\n );\n}\n\nfunction AddTemplateCard({ template }: { template: AvailableTemplate }) {\n const Icon = TEMPLATE_ICONS[template.icon] ?? IconSparkles;\n const scaffold = useActionMutation(\"scaffold-workspace-app\", {\n onSuccess: (result: any) => {\n toast.success(\n `Scaffolded apps/${result?.appId || template.name}. The gateway will pick it up shortly.`,\n );\n },\n onError: (err) => {\n toast.error(\n `Could not scaffold ${template.label}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n },\n });\n\n return (\n <div className=\"group relative flex h-full min-h-36 items-stretch gap-3 rounded-lg border bg-card p-4 transition hover:border-foreground/30\">\n <div\n className=\"flex h-9 w-9 shrink-0 items-center justify-center self-start rounded-md\"\n style={{\n backgroundColor: `rgb(${template.colorRgb} / 0.12)`,\n color: template.color,\n }}\n >\n <Icon size={18} />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <h3 className=\"truncate text-sm font-semibold text-foreground\">\n {template.label}\n </h3>\n </div>\n <p className=\"mt-1 line-clamp-2 text-xs leading-relaxed text-muted-foreground\">\n {template.hint}\n </p>\n <div className=\"mt-auto pt-3\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={scaffold.isPending}\n onClick={() => scaffold.mutate({ template: template.name })}\n >\n {scaffold.isPending ? (\n <>\n <IconLoader2 size={14} className=\"mr-1.5 animate-spin\" />\n Adding...\n </>\n ) : (\n <>\n <IconPlus size={14} className=\"mr-1.5\" />\n Add to workspace\n </>\n )}\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"apps.js","sourceRoot":"","sources":["../../../src/routes/pages/apps.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useActionQuery, useT } from \"@agent-native/core/client\";\nimport {\n IconApps,\n IconChevronDown,\n IconEyeOff,\n IconPlus,\n} from \"@tabler/icons-react\";\nimport { useState } from \"react\";\n\nimport { ActionQueryError } from \"../../components/action-query-error\";\nimport { CreateAppPopover } from \"../../components/create-app-popover\";\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 { Skeleton } from \"../../components/ui/skeleton\";\nimport { WorkspaceAppCard } from \"../../components/workspace-app-card\";\nimport { cn } from \"../../lib/utils\";\nimport type { WorkspaceAppSummary } from \"../../lib/workspace-apps\";\n\nexport function meta() {\n return [{ title: \"Apps — Dispatch\" }];\n}\n\ninterface WorkspaceInfo {\n name: string | null;\n displayName: string | null;\n appCount: number;\n}\n\nexport default function AppsRoute() {\n const t = useT();\n const [showHidden, setShowHidden] = useState(false);\n const appsQuery = useActionQuery(\"list-workspace-apps\", {\n includeAgentCards: false,\n includeArchived: true,\n });\n const { data: apps = [], isLoading: appsLoading } = appsQuery;\n const { data: workspace } = useActionQuery(\n \"get-workspace-info\",\n {},\n { staleTime: 60_000 },\n );\n const ws = workspace as WorkspaceInfo | undefined;\n const workspaceLabel = ws?.displayName ?? ws?.name ?? null;\n const allApps = (apps as WorkspaceAppSummary[]).filter(\n (app) => !app.isDispatch,\n );\n const visibleApps = allApps.filter((app) => !app.archived);\n const archivedApps = allApps.filter((app) => app.archived);\n const showAppSkeletons = appsLoading && allApps.length === 0;\n\n return (\n <DispatchShell\n title={t(\"dispatch.nav.apps\")}\n description={\n workspaceLabel\n ? t(\"dispatch.pages.appsDescriptionWithWorkspace\", {\n workspace: workspaceLabel,\n })\n : t(\"dispatch.pages.appsDescription\")\n }\n >\n <div className=\"space-y-8\">\n <section className=\"space-y-3\">\n <div className=\"flex flex-wrap items-end justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-2\">\n <IconApps\n size={16}\n className=\"mt-0.5 shrink-0 text-muted-foreground\"\n />\n <div className=\"min-w-0\">\n <h2 className=\"truncate text-sm font-semibold text-foreground\">\n {workspaceLabel\n ? t(\"dispatch.pages.appsInWorkspace\", {\n workspace: workspaceLabel,\n })\n : t(\"dispatch.pages.workspaceApps\")}\n </h2>\n <p className=\"mt-0.5 text-xs text-muted-foreground\">\n {t(\"dispatch.pages.activeCount\", {\n count: visibleApps.length,\n })}\n {archivedApps.length > 0\n ? ` · ${t(\"dispatch.pages.hiddenCount\", {\n count: archivedApps.length,\n })}`\n : \"\"}\n </p>\n </div>\n </div>\n {visibleApps.length > 0 ? (\n <CreateAppPopover\n align=\"end\"\n trigger={\n <Button size=\"sm\">\n <IconPlus size={15} className=\"mr-1.5\" />\n {t(\"dispatch.pages.createApp\")}\n </Button>\n }\n />\n ) : null}\n </div>\n\n {appsQuery.isError ? (\n <ActionQueryError\n error={appsQuery.error}\n onRetry={() => void appsQuery.refetch()}\n />\n ) : showAppSkeletons ? (\n <AppsSkeletonGrid />\n ) : visibleApps.length > 0 ? (\n <div className=\"grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3\">\n {visibleApps.map((app) => (\n <WorkspaceAppCard key={app.id} app={app} className=\"h-full\" />\n ))}\n </div>\n ) : (\n <EmptyAppsState />\n )}\n </section>\n\n {archivedApps.length > 0 ? (\n <Collapsible open={showHidden} onOpenChange={setShowHidden}>\n <section className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-3 border-t pt-4\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <IconEyeOff\n size={16}\n className=\"shrink-0 text-muted-foreground\"\n />\n <div className=\"min-w-0\">\n <h2 className=\"text-sm font-semibold text-foreground\">\n {t(\"dispatch.pages.hiddenApps\")}\n </h2>\n <p className=\"text-xs text-muted-foreground\">\n {t(\"dispatch.pages.hiddenAppCount\", {\n count: archivedApps.length,\n })}\n </p>\n </div>\n </div>\n <CollapsibleTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-1.5\"\n >\n {showHidden\n ? t(\"dispatch.pages.hide\")\n : t(\"dispatch.pages.show\")}\n <IconChevronDown\n size={14}\n className={cn(\n \"transition-transform\",\n showHidden && \"rotate-180\",\n )}\n />\n </Button>\n </CollapsibleTrigger>\n </div>\n <CollapsibleContent>\n <div className=\"grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3\">\n {archivedApps.map((app) => (\n <WorkspaceAppCard\n key={app.id}\n app={app}\n className=\"h-full\"\n />\n ))}\n </div>\n </CollapsibleContent>\n </section>\n </Collapsible>\n ) : null}\n </div>\n </DispatchShell>\n );\n}\n\nfunction AppsSkeletonGrid() {\n return (\n <div className=\"grid gap-3 md:grid-cols-2 xl:grid-cols-3\">\n {Array.from({ length: 3 }).map((_, index) => (\n <div key={index} className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1 space-y-3\">\n <Skeleton className=\"h-4 w-32\" />\n <Skeleton className=\"h-3 w-24\" />\n <div className=\"space-y-2 pt-1\">\n <Skeleton className=\"h-3 w-full\" />\n <Skeleton className=\"h-3 w-2/3\" />\n </div>\n </div>\n <Skeleton className=\"h-7 w-7 rounded-md\" />\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction EmptyAppsState() {\n const t = useT();\n return (\n <div className=\"rounded-lg border border-dashed bg-card px-4 py-10 text-center\">\n <div className=\"mx-auto flex size-10 items-center justify-center rounded-lg bg-muted text-muted-foreground\">\n <IconApps size={18} />\n </div>\n <h3 className=\"mt-3 text-sm font-semibold text-foreground\">\n {t(\"dispatch.pages.noWorkspaceApps\")}\n </h3>\n <p className=\"mx-auto mt-1 max-w-sm text-sm text-muted-foreground\">\n {t(\"dispatch.pages.noWorkspaceAppsDescription\")}\n </p>\n <div className=\"mt-4\">\n <CreateAppPopover\n trigger={\n <Button size=\"sm\">\n <IconPlus size={15} className=\"mr-1.5\" />\n {t(\"dispatch.pages.createApp\")}\n </Button>\n }\n />\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automations.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/automations.tsx"],"names":[],"mappings":"AAuCA,wBAAgB,IAAI;;IAEnB;
|
|
1
|
+
{"version":3,"file":"automations.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/automations.tsx"],"names":[],"mappings":"AAuCA,wBAAgB,IAAI;;IAEnB;AAmID,MAAM,CAAC,OAAO,UAAU,gBAAgB,oDA0GvC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automations.js","sourceRoot":"","sources":["../../../src/routes/pages/automations.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n PromptComposer,\n sendToAgentChat,\n useChangeVersions,\n} from \"@agent-native/core/client\";\nimport { IconPlus, IconSettingsAutomation } from \"@tabler/icons-react\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { useMemo, useState } from \"react\";\nimport { toast } from \"sonner\";\n\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport { Badge } from \"../../components/ui/badge\";\nimport { Button } from \"../../components/ui/button\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../../components/ui/popover\";\nimport { Skeleton } from \"../../components/ui/skeleton\";\nimport { Switch } from \"../../components/ui/switch\";\nimport {\n automationIdentity,\n automationLastRun,\n automationNextRun,\n automationStatus,\n automationTarget,\n sortAutomations,\n type AutomationStatusTone,\n} from \"../../lib/automation-display\";\nimport {\n listDispatchAutomations,\n setDispatchAutomationEnabled,\n type DispatchAutomationItem,\n type SetDispatchAutomationEnabledInput,\n} from \"../../lib/automations\";\nimport { cn } from \"../../lib/utils\";\n\nconst AUTOMATIONS_QUERY_KEY = [\"dispatch-automations\"] as const;\n\nexport function meta() {\n return [{ title: \"Automations — Dispatch\" }];\n}\n\nfunction StatusDot({ tone }: { tone: AutomationStatusTone }) {\n return (\n <span\n className={cn(\n \"size-2 shrink-0 rounded-full\",\n tone === \"success\" && \"bg-emerald-500\",\n tone === \"warning\" && \"bg-amber-500\",\n tone === \"danger\" && \"bg-destructive\",\n tone === \"muted\" && \"bg-muted-foreground/35\",\n tone === \"default\" && \"bg-primary\",\n )}\n />\n );\n}\n\nfunction useAutomations() {\n const version = useChangeVersions([\"action\", \"screen-refresh\"]);\n return useQuery<DispatchAutomationItem[]>({\n queryKey: [...AUTOMATIONS_QUERY_KEY, version],\n queryFn: listDispatchAutomations,\n placeholderData: (prev) => prev,\n refetchInterval: 15_000,\n staleTime: 5_000,\n });\n}\n\nfunction useToggleAutomation() {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: setDispatchAutomationEnabled,\n onMutate: async (input: SetDispatchAutomationEnabledInput) => {\n await queryClient.cancelQueries({ queryKey: AUTOMATIONS_QUERY_KEY });\n const snapshots = queryClient.getQueriesData<DispatchAutomationItem[]>({\n queryKey: AUTOMATIONS_QUERY_KEY,\n });\n\n queryClient.setQueriesData<DispatchAutomationItem[]>(\n { queryKey: AUTOMATIONS_QUERY_KEY },\n (rows) =>\n rows?.map((item) =>\n automationIdentity(item) === automationIdentity(input)\n ? { ...item, enabled: input.enabled }\n : item,\n ),\n );\n\n return { snapshots };\n },\n onError: (err, _input, context) => {\n for (const [queryKey, data] of context?.snapshots ?? []) {\n queryClient.setQueryData(queryKey, data);\n }\n toast.error(\n `Could not update automation: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n },\n onSuccess: (updated) => {\n queryClient.setQueriesData<DispatchAutomationItem[]>(\n { queryKey: AUTOMATIONS_QUERY_KEY },\n (rows) =>\n rows?.map((item) =>\n automationIdentity(item) === automationIdentity(updated)\n ? updated\n : item,\n ),\n );\n },\n onSettled: () => {\n void queryClient.invalidateQueries({ queryKey: AUTOMATIONS_QUERY_KEY });\n },\n });\n}\n\nfunction CreateAutomationButton() {\n const [open, setOpen] = useState(false);\n const [scope, setScope] = useState<\"personal\" | \"organization\">(\"personal\");\n\n function handleSubmit(text: string) {\n const trimmed = text.trim();\n if (!trimmed) return;\n window.dispatchEvent(\n new CustomEvent(\"agent-panel:set-mode\", {\n detail: { mode: \"chat\" },\n }),\n );\n sendToAgentChat({\n message: trimmed,\n context: `The user wants to create a new automation. Scope: ${scope}. Use manage-automations with action=define to create it. Ask clarifying questions if needed about what event to trigger on, conditions, and what actions to take.`,\n submit: true,\n newTab: true,\n });\n setOpen(false);\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button size=\"sm\">\n <IconPlus size={14} />\n New automation\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-[min(100vw-2rem,24rem)] p-3\">\n <p className=\"pb-2 text-sm font-semibold text-foreground\">\n New automation\n </p>\n <PromptComposer\n autoFocus\n placeholder=\"Describe what you want to automate...\"\n draftScope=\"dispatch-automations:create\"\n onSubmit={handleSubmit}\n />\n <select\n value={scope}\n onChange={(event) =>\n setScope(event.target.value as \"personal\" | \"organization\")\n }\n className=\"mt-2 w-full cursor-pointer rounded-md border border-input bg-background px-3 py-1.5 text-xs text-foreground\"\n >\n <option value=\"personal\">Personal</option>\n <option value=\"organization\">Organization</option>\n </select>\n </PopoverContent>\n </Popover>\n );\n}\n\nexport default function AutomationsRoute() {\n const automationsQuery = useAutomations();\n const toggleAutomation = useToggleAutomation();\n const automations = automationsQuery.data ?? [];\n const ordered = useMemo(() => sortAutomations(automations), [automations]);\n const enabledCount = automations.filter((item) => item.enabled).length;\n const errorCount = automations.filter(\n (item) => item.enabled && item.lastStatus === \"error\",\n ).length;\n const pendingToggleIdentity = toggleAutomation.isPending\n ? toggleAutomation.variables\n ? automationIdentity(toggleAutomation.variables)\n : null\n : null;\n\n return (\n <DispatchShell\n title=\"Automations\"\n description=\"See scheduled and event-triggered jobs, pause them, or ask the agent to create one.\"\n >\n <section className=\"flex flex-col gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-2 text-sm text-muted-foreground\">\n <IconSettingsAutomation size={16} className=\"shrink-0\" />\n <span>\n {enabledCount} enabled\n {errorCount > 0 ? ` · ${errorCount} errors` : \"\"}\n </span>\n </div>\n <CreateAutomationButton />\n </div>\n\n <div className=\"divide-y rounded-lg border bg-card\">\n {automationsQuery.isLoading && ordered.length === 0 ? (\n Array.from({ length: 4 }).map((_, index) => (\n <div key={index} className=\"px-4 py-3\">\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"mt-2 h-3 w-28\" />\n </div>\n ))\n ) : ordered.length > 0 ? (\n ordered.map((item) => {\n const status = automationStatus(item);\n const canUpdate = item.canUpdate !== false;\n const isToggling =\n pendingToggleIdentity === automationIdentity(item);\n return (\n <div\n key={item.id}\n className=\"grid grid-cols-[minmax(0,1fr)_auto] gap-3 px-4 py-3\"\n >\n <div className=\"min-w-0\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <StatusDot tone={status.tone} />\n <span className=\"truncate text-sm font-medium text-foreground\">\n {item.name}\n </span>\n </div>\n <div className=\"mt-1 truncate text-xs text-muted-foreground\">\n {automationTarget(item)}\n </div>\n <div className=\"mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-muted-foreground\">\n <span>Last {automationLastRun(item)}</span>\n <span>Next {automationNextRun(item)}</span>\n </div>\n {item.lastError ? (\n <div className=\"mt-1 truncate text-xs text-destructive\">\n {item.lastError}\n </div>\n ) : null}\n </div>\n <div className=\"flex flex-col items-end gap-2\">\n <Badge\n variant={\n status.tone === \"danger\" ? \"destructive\" : \"outline\"\n }\n className=\"h-5\"\n >\n {status.label}\n </Badge>\n <Switch\n checked={!!item.enabled}\n disabled={!canUpdate || isToggling}\n aria-label={`${item.enabled ? \"Disable\" : \"Enable\"} automation ${item.name}`}\n onCheckedChange={(checked) =>\n toggleAutomation.mutate({\n owner: item.owner,\n path: item.path,\n enabled: checked,\n })\n }\n />\n </div>\n </div>\n );\n })\n ) : (\n <div className=\"px-4 py-10 text-center text-sm text-muted-foreground\">\n No automations yet. Create one here, or ask Dispatch to set up a\n scheduled or event-triggered job.\n </div>\n )}\n </div>\n </section>\n </DispatchShell>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"automations.js","sourceRoot":"","sources":["../../../src/routes/pages/automations.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n PromptComposer,\n sendToAgentChat,\n useChangeVersions,\n} from \"@agent-native/core/client\";\nimport { IconPlus, IconSettingsAutomation } from \"@tabler/icons-react\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { useMemo, useState } from \"react\";\nimport { toast } from \"sonner\";\n\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport { Badge } from \"../../components/ui/badge\";\nimport { Button } from \"../../components/ui/button\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"../../components/ui/popover\";\nimport { Skeleton } from \"../../components/ui/skeleton\";\nimport { Switch } from \"../../components/ui/switch\";\nimport {\n automationIdentity,\n automationLastRun,\n automationNextRun,\n automationStatus,\n automationTarget,\n sortAutomations,\n type AutomationStatusTone,\n} from \"../../lib/automation-display\";\nimport {\n listDispatchAutomations,\n setDispatchAutomationEnabled,\n type DispatchAutomationItem,\n type SetDispatchAutomationEnabledInput,\n} from \"../../lib/automations\";\nimport { cn } from \"../../lib/utils\";\n\nconst AUTOMATIONS_QUERY_KEY = [\"dispatch-automations\"] as const;\n\nexport function meta() {\n return [{ title: \"Automations — Dispatch\" }];\n}\n\nfunction StatusDot({ tone }: { tone: AutomationStatusTone }) {\n return (\n <span\n className={cn(\n \"size-2 shrink-0 rounded-full\",\n tone === \"success\" && \"bg-emerald-500\",\n tone === \"warning\" && \"bg-amber-500\",\n tone === \"danger\" && \"bg-destructive\",\n tone === \"muted\" && \"bg-muted-foreground/35\",\n tone === \"default\" && \"bg-primary\",\n )}\n />\n );\n}\n\nfunction useAutomations() {\n const version = useChangeVersions([\"action\", \"screen-refresh\"]);\n return useQuery<DispatchAutomationItem[]>({\n queryKey: [...AUTOMATIONS_QUERY_KEY, version],\n queryFn: listDispatchAutomations,\n placeholderData: (prev) => prev,\n staleTime: 5_000,\n });\n}\n\nfunction useToggleAutomation() {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: setDispatchAutomationEnabled,\n onMutate: async (input: SetDispatchAutomationEnabledInput) => {\n await queryClient.cancelQueries({ queryKey: AUTOMATIONS_QUERY_KEY });\n const snapshots = queryClient.getQueriesData<DispatchAutomationItem[]>({\n queryKey: AUTOMATIONS_QUERY_KEY,\n });\n\n queryClient.setQueriesData<DispatchAutomationItem[]>(\n { queryKey: AUTOMATIONS_QUERY_KEY },\n (rows) =>\n rows?.map((item) =>\n automationIdentity(item) === automationIdentity(input)\n ? { ...item, enabled: input.enabled }\n : item,\n ),\n );\n\n return { snapshots };\n },\n onError: (err, _input, context) => {\n for (const [queryKey, data] of context?.snapshots ?? []) {\n queryClient.setQueryData(queryKey, data);\n }\n toast.error(\n `Could not update automation: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n },\n onSuccess: (updated) => {\n queryClient.setQueriesData<DispatchAutomationItem[]>(\n { queryKey: AUTOMATIONS_QUERY_KEY },\n (rows) =>\n rows?.map((item) =>\n automationIdentity(item) === automationIdentity(updated)\n ? updated\n : item,\n ),\n );\n },\n onSettled: () => {\n void queryClient.invalidateQueries({ queryKey: AUTOMATIONS_QUERY_KEY });\n },\n });\n}\n\nfunction CreateAutomationButton() {\n const [open, setOpen] = useState(false);\n const [scope, setScope] = useState<\"personal\" | \"organization\">(\"personal\");\n\n function handleSubmit(text: string) {\n const trimmed = text.trim();\n if (!trimmed) return;\n window.dispatchEvent(\n new CustomEvent(\"agent-panel:set-mode\", {\n detail: { mode: \"chat\" },\n }),\n );\n sendToAgentChat({\n message: trimmed,\n context: `The user wants to create a new automation. Scope: ${scope}. Use manage-automations with action=define to create it. Ask clarifying questions if needed about what event to trigger on, conditions, and what actions to take.`,\n submit: true,\n newTab: true,\n });\n setOpen(false);\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button size=\"sm\">\n <IconPlus size={14} />\n New automation\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-[min(100vw-2rem,24rem)] p-3\">\n <p className=\"pb-2 text-sm font-semibold text-foreground\">\n New automation\n </p>\n <PromptComposer\n autoFocus\n placeholder=\"Describe what you want to automate...\"\n draftScope=\"dispatch-automations:create\"\n onSubmit={handleSubmit}\n />\n <select\n value={scope}\n onChange={(event) =>\n setScope(event.target.value as \"personal\" | \"organization\")\n }\n className=\"mt-2 w-full cursor-pointer rounded-md border border-input bg-background px-3 py-1.5 text-xs text-foreground\"\n >\n <option value=\"personal\">Personal</option>\n <option value=\"organization\">Organization</option>\n </select>\n </PopoverContent>\n </Popover>\n );\n}\n\nexport default function AutomationsRoute() {\n const automationsQuery = useAutomations();\n const toggleAutomation = useToggleAutomation();\n const automations = automationsQuery.data ?? [];\n const ordered = useMemo(() => sortAutomations(automations), [automations]);\n const enabledCount = automations.filter((item) => item.enabled).length;\n const errorCount = automations.filter(\n (item) => item.enabled && item.lastStatus === \"error\",\n ).length;\n const pendingToggleIdentity = toggleAutomation.isPending\n ? toggleAutomation.variables\n ? automationIdentity(toggleAutomation.variables)\n : null\n : null;\n\n return (\n <DispatchShell\n title=\"Automations\"\n description=\"See scheduled and event-triggered jobs, pause them, or ask the agent to create one.\"\n >\n <section className=\"flex flex-col gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-2 text-sm text-muted-foreground\">\n <IconSettingsAutomation size={16} className=\"shrink-0\" />\n <span>\n {enabledCount} enabled\n {errorCount > 0 ? ` · ${errorCount} errors` : \"\"}\n </span>\n </div>\n <CreateAutomationButton />\n </div>\n\n <div className=\"divide-y rounded-lg border bg-card\">\n {automationsQuery.isLoading && ordered.length === 0 ? (\n Array.from({ length: 4 }).map((_, index) => (\n <div key={index} className=\"px-4 py-3\">\n <Skeleton className=\"h-4 w-40\" />\n <Skeleton className=\"mt-2 h-3 w-28\" />\n </div>\n ))\n ) : ordered.length > 0 ? (\n ordered.map((item) => {\n const status = automationStatus(item);\n const canUpdate = item.canUpdate !== false;\n const isToggling =\n pendingToggleIdentity === automationIdentity(item);\n return (\n <div\n key={item.id}\n className=\"grid grid-cols-[minmax(0,1fr)_auto] gap-3 px-4 py-3\"\n >\n <div className=\"min-w-0\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <StatusDot tone={status.tone} />\n <span className=\"truncate text-sm font-medium text-foreground\">\n {item.name}\n </span>\n </div>\n <div className=\"mt-1 truncate text-xs text-muted-foreground\">\n {automationTarget(item)}\n </div>\n <div className=\"mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-muted-foreground\">\n <span>Last {automationLastRun(item)}</span>\n <span>Next {automationNextRun(item)}</span>\n </div>\n {item.lastError ? (\n <div className=\"mt-1 truncate text-xs text-destructive\">\n {item.lastError}\n </div>\n ) : null}\n </div>\n <div className=\"flex flex-col items-end gap-2\">\n <Badge\n variant={\n status.tone === \"danger\" ? \"destructive\" : \"outline\"\n }\n className=\"h-5\"\n >\n {status.label}\n </Badge>\n <Switch\n checked={!!item.enabled}\n disabled={!canUpdate || isToggling}\n aria-label={`${item.enabled ? \"Disable\" : \"Enable\"} automation ${item.name}`}\n onCheckedChange={(checked) =>\n toggleAutomation.mutate({\n owner: item.owner,\n path: item.path,\n enabled: checked,\n })\n }\n />\n </div>\n </div>\n );\n })\n ) : (\n <div className=\"px-4 py-10 text-center text-sm text-muted-foreground\">\n No automations yet. Create one here, or ask Dispatch to set up a\n scheduled or event-triggered job.\n </div>\n )}\n </div>\n </section>\n </DispatchShell>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/metrics.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,IAAI;;IAEnB;AAymBD,MAAM,CAAC,OAAO,UAAU,YAAY,
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/metrics.tsx"],"names":[],"mappings":"AAoBA,wBAAgB,IAAI;;IAEnB;AAymBD,MAAM,CAAC,OAAO,UAAU,YAAY,oDAwInC"}
|
|
@@ -151,7 +151,7 @@ function RecentTable({ rows, billing, }) {
|
|
|
151
151
|
export default function MetricsRoute() {
|
|
152
152
|
const t = useT();
|
|
153
153
|
const [sinceDays, setSinceDays] = useState(30);
|
|
154
|
-
const { data, isLoading, error } = useActionQuery("list-dispatch-usage-metrics", { sinceDays }
|
|
154
|
+
const { data, isLoading, error } = useActionQuery("list-dispatch-usage-metrics", { sinceDays });
|
|
155
155
|
const metrics = data;
|
|
156
156
|
const billing = metrics?.billing ?? USD_BILLING;
|
|
157
157
|
const totalTokens = useMemo(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/routes/pages/metrics.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useActionQuery, useT } from \"@agent-native/core/client\";\nimport {\n IconActivity,\n IconAlertTriangle,\n IconApps,\n IconChartBar,\n IconClockHour4,\n IconCoin,\n IconMessages,\n IconUsersGroup,\n} from \"@tabler/icons-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport { Alert, AlertDescription, AlertTitle } from \"../../components/ui/alert\";\nimport { Badge } from \"../../components/ui/badge\";\nimport { Button } from \"../../components/ui/button\";\nimport { Skeleton } from \"../../components/ui/skeleton\";\nimport { cn } from \"../../lib/utils\";\n\nexport function meta() {\n return [{ title: \"Metrics — Dispatch\" }];\n}\n\ninterface UsageMetricBucket {\n key: string;\n label: string;\n costCents: number;\n calls: number;\n chatCalls: number;\n inputTokens: number;\n outputTokens: number;\n activeUsers: number;\n lastActiveAt: number | null;\n}\n\ninterface UserUsageMetric extends UsageMetricBucket {\n ownerEmail: string;\n chatThreads: number;\n chatMessages: number;\n lastChatAt: number | null;\n topApp: string | null;\n role: string | null;\n}\n\ninterface AppAccessMetric {\n id: string;\n name: string;\n path: string;\n status?: \"ready\" | \"pending\";\n statusLabel?: string;\n isDispatch: boolean;\n accessLabel: string;\n accessUsers: number;\n usersWithUsage: number;\n usageCalls: number;\n chatCalls: number;\n costCents: number;\n lastActiveAt: number | null;\n}\n\ninterface DailyUsageMetric {\n date: string;\n costCents: number;\n calls: number;\n chatCalls: number;\n activeUsers: number;\n}\n\ninterface RecentUsageMetric {\n id: number;\n createdAt: number;\n ownerEmail: string;\n app: string;\n label: string;\n model: string;\n inputTokens: number;\n outputTokens: number;\n costCents: number;\n}\n\ninterface UsageBillingMode {\n unit: \"usd\" | \"builder-credits\";\n label: string;\n shortLabel: string;\n source: \"estimated-provider-cost\" | \"builder-agent-credits\";\n hardCostMarginMultiplier?: number;\n creditsPerUsd?: number;\n}\n\ninterface DispatchUsageMetrics {\n billing?: UsageBillingMode;\n sinceDays: number;\n access: {\n viewerEmail: string;\n orgId: string | null;\n role: string | null;\n scope: \"organization\" | \"solo\";\n totalUsers: number;\n };\n totals: {\n costCents: number;\n calls: number;\n chatCalls: number;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n activeUsers: number;\n chatThreads: number;\n chatMessages: number;\n workspaceApps: number;\n };\n byApp: UsageMetricBucket[];\n byUser: UserUsageMetric[];\n byLabel: UsageMetricBucket[];\n byModel: UsageMetricBucket[];\n daily: DailyUsageMetric[];\n appAccess: AppAccessMetric[];\n recent: RecentUsageMetric[];\n}\n\nconst RANGES = [7, 30, 90] as const;\n\nconst USD_BILLING: UsageBillingMode = {\n unit: \"usd\",\n label: \"Estimated spend\",\n shortLabel: \"Cost\",\n source: \"estimated-provider-cost\",\n};\n\nfunction displayAmountFromCostCents(\n cents: number,\n billing: UsageBillingMode,\n): number {\n if (billing.unit !== \"builder-credits\") return cents;\n const margin = billing.hardCostMarginMultiplier ?? 1.25;\n const creditsPerUsd = billing.creditsPerUsd ?? 20;\n const credits = (cents / 100) * margin * creditsPerUsd;\n return credits <= 0 ? 0 : Math.ceil(credits * 1000) / 1000;\n}\n\nfunction formatCredits(credits: number): string {\n if (!Number.isFinite(credits) || credits === 0) return \"0 credits\";\n const maximumFractionDigits = credits < 1 ? 3 : credits < 10 ? 2 : 1;\n const value = credits.toLocaleString(undefined, {\n maximumFractionDigits,\n });\n return `${value} ${credits === 1 ? \"credit\" : \"credits\"}`;\n}\n\nfunction formatSpend(cents: number, billing: UsageBillingMode): string {\n if (billing.unit === \"builder-credits\") {\n return formatCredits(displayAmountFromCostCents(cents, billing));\n }\n if (!Number.isFinite(cents) || cents === 0) return \"$0.00\";\n if (Math.abs(cents) < 1) return `${cents.toFixed(3)}¢`;\n if (Math.abs(cents) < 100) return `${cents.toFixed(2)}¢`;\n return (cents / 100).toLocaleString(undefined, {\n style: \"currency\",\n currency: \"USD\",\n maximumFractionDigits: 2,\n });\n}\n\nfunction formatNumber(value: number): string {\n return new Intl.NumberFormat(undefined, {\n notation: value >= 10_000 ? \"compact\" : \"standard\",\n maximumFractionDigits: value >= 10_000 ? 1 : 0,\n }).format(value);\n}\n\nfunction formatTokens(value: number): string {\n return new Intl.NumberFormat(undefined, {\n notation: \"compact\",\n maximumFractionDigits: 1,\n }).format(value);\n}\n\nfunction timeAgo(timestamp: number | null): string {\n if (!timestamp) return \"No activity\";\n const diff = Date.now() - timestamp;\n if (diff < 60_000) return \"just now\";\n if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;\n if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;\n return `${Math.floor(diff / 86_400_000)}d ago`;\n}\n\nfunction displayApp(value: string | null | undefined): string {\n const trimmed = value?.trim();\n if (!trimmed || trimmed === \"unattributed\") return \"Unattributed\";\n return trimmed;\n}\n\nfunction maxSpend(\n rows: Array<{ costCents: number }>,\n billing: UsageBillingMode,\n): number {\n return rows.reduce(\n (max, row) =>\n Math.max(max, displayAmountFromCostCents(row.costCents, billing)),\n 0,\n );\n}\n\nfunction barWidth(value: number, max: number): string {\n if (max <= 0 || value <= 0) return \"0%\";\n return `${Math.max(4, Math.round((value / max) * 100))}%`;\n}\n\nfunction RangeSelector({\n value,\n onChange,\n}: {\n value: number;\n onChange: (value: number) => void;\n}) {\n return (\n <div className=\"flex rounded-md border bg-card p-0.5\">\n {RANGES.map((range) => (\n <Button\n key={range}\n type=\"button\"\n variant={value === range ? \"secondary\" : \"ghost\"}\n size=\"sm\"\n className=\"h-7 px-3 text-xs\"\n onClick={() => onChange(range)}\n >\n {range}d\n </Button>\n ))}\n </div>\n );\n}\n\nfunction MetricCard({\n label,\n value,\n detail,\n icon,\n}: {\n label: string;\n value: string;\n detail: string;\n icon: ReactNode;\n}) {\n return (\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {label}\n </span>\n <span className=\"text-muted-foreground\">{icon}</span>\n </div>\n <div className=\"text-2xl font-semibold tabular-nums text-foreground\">\n {value}\n </div>\n <div className=\"mt-1 truncate text-xs text-muted-foreground\">\n {detail}\n </div>\n </div>\n );\n}\n\nfunction Panel({\n title,\n icon,\n children,\n action,\n}: {\n title: string;\n icon: ReactNode;\n children: ReactNode;\n action?: ReactNode;\n}) {\n return (\n <section className=\"rounded-lg border bg-card\">\n <div className=\"flex items-center justify-between gap-3 border-b px-4 py-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"text-muted-foreground\">{icon}</span>\n <h2 className=\"truncate text-sm font-semibold text-foreground\">\n {title}\n </h2>\n </div>\n {action}\n </div>\n <div className=\"p-4\">{children}</div>\n </section>\n );\n}\n\nfunction LoadingMetrics() {\n return (\n <div className=\"space-y-4\">\n <div className=\"grid gap-3 md:grid-cols-2 xl:grid-cols-5\">\n {Array.from({ length: 5 }).map((_, index) => (\n <div key={index} className=\"rounded-lg border bg-card p-4\">\n <Skeleton className=\"mb-4 h-4 w-24\" />\n <Skeleton className=\"h-7 w-20\" />\n <Skeleton className=\"mt-3 h-3 w-28\" />\n </div>\n ))}\n </div>\n <Skeleton className=\"h-80 rounded-lg\" />\n </div>\n );\n}\n\nfunction AppSpendRows({\n rows,\n billing,\n}: {\n rows: UsageMetricBucket[];\n billing: UsageBillingMode;\n}) {\n const max = maxSpend(rows, billing);\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No LLM usage recorded for this window.\n </div>\n );\n }\n return (\n <div className=\"space-y-3\">\n {rows.map((row) => (\n <div key={row.key} className=\"space-y-1.5\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium text-foreground\">\n {displayApp(row.key)}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatNumber(row.chatCalls)} chats ·{\" \"}\n {formatNumber(row.activeUsers)} users\n </div>\n </div>\n <div className=\"shrink-0 text-right\">\n <div className=\"font-medium tabular-nums text-foreground\">\n {formatSpend(row.costCents, billing)}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatNumber(row.calls)} calls\n </div>\n </div>\n </div>\n <div className=\"h-2 overflow-hidden rounded-full bg-muted\">\n <div\n className=\"h-full rounded-full bg-foreground\"\n style={{\n width: barWidth(\n displayAmountFromCostCents(row.costCents, billing),\n max,\n ),\n }}\n />\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction DailyActivity({ rows }: { rows: DailyUsageMetric[] }) {\n const max = Math.max(\n 1,\n rows.reduce((value, row) => Math.max(value, row.calls), 0),\n );\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No activity in this window.\n </div>\n );\n }\n return (\n <div className=\"flex h-44 items-end gap-1\">\n {rows.map((row) => (\n <div\n key={row.date}\n className=\"group flex min-w-0 flex-1 flex-col items-center gap-2\"\n >\n <div className=\"relative flex h-36 w-full items-end rounded-sm bg-muted/60\">\n <div\n className=\"w-full rounded-sm bg-foreground transition group-hover:bg-primary\"\n style={{ height: `${Math.max(4, (row.calls / max) * 100)}%` }}\n />\n </div>\n <div className=\"hidden text-[10px] text-muted-foreground sm:block\">\n {row.date.slice(5)}\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AppAccessTable({\n rows,\n billing,\n}: {\n rows: AppAccessMetric[];\n billing: UsageBillingMode;\n}) {\n const visibleRows = rows.filter((row) => !row.isDispatch);\n if (visibleRows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No workspace apps discovered yet.\n </div>\n );\n }\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[720px] text-left text-xs\">\n <thead>\n <tr className=\"border-b text-muted-foreground\">\n <th className=\"px-2 py-2 font-medium\">App</th>\n <th className=\"px-2 py-2 font-medium\">Access</th>\n <th className=\"px-2 py-2 text-right font-medium\">Users</th>\n <th className=\"px-2 py-2 text-right font-medium\">Chats</th>\n <th className=\"px-2 py-2 text-right font-medium\">\n {billing.shortLabel}\n </th>\n <th className=\"px-2 py-2 text-right font-medium\">Last activity</th>\n </tr>\n </thead>\n <tbody>\n {visibleRows.map((row) => (\n <tr key={row.id} className=\"border-b last:border-0\">\n <td className=\"px-2 py-3\">\n <div className=\"font-medium text-foreground\">{row.name}</div>\n <div className=\"font-mono text-[11px] text-muted-foreground\">\n {row.path}\n </div>\n </td>\n <td className=\"px-2 py-3\">\n <Badge\n variant=\"outline\"\n className={cn(\n row.status === \"pending\" &&\n \"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\",\n )}\n >\n {row.status === \"pending\"\n ? row.statusLabel || \"Builder branch\"\n : row.accessLabel}\n </Badge>\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatNumber(row.usersWithUsage)} /{\" \"}\n {formatNumber(row.accessUsers)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatNumber(row.chatCalls)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatSpend(row.costCents, billing)}\n </td>\n <td className=\"px-2 py-3 text-right text-muted-foreground\">\n {timeAgo(row.lastActiveAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction UserTable({\n rows,\n billing,\n}: {\n rows: UserUsageMetric[];\n billing: UsageBillingMode;\n}) {\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No users have triggered LLM usage in this window.\n </div>\n );\n }\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[760px] text-left text-xs\">\n <thead>\n <tr className=\"border-b text-muted-foreground\">\n <th className=\"px-2 py-2 font-medium\">User</th>\n <th className=\"px-2 py-2 font-medium\">Role</th>\n <th className=\"px-2 py-2 font-medium\">Top app</th>\n <th className=\"px-2 py-2 text-right font-medium\">Chats</th>\n <th className=\"px-2 py-2 text-right font-medium\">Threads</th>\n <th className=\"px-2 py-2 text-right font-medium\">Tokens</th>\n <th className=\"px-2 py-2 text-right font-medium\">\n {billing.shortLabel}\n </th>\n </tr>\n </thead>\n <tbody>\n {rows.slice(0, 12).map((row) => (\n <tr key={row.ownerEmail} className=\"border-b last:border-0\">\n <td className=\"max-w-64 px-2 py-3\">\n <div className=\"truncate font-medium text-foreground\">\n {row.ownerEmail}\n </div>\n <div className=\"text-muted-foreground\">\n {timeAgo(row.lastActiveAt ?? row.lastChatAt)}\n </div>\n </td>\n <td className=\"px-2 py-3\">\n <Badge variant=\"secondary\">{row.role ?? \"user\"}</Badge>\n </td>\n <td className=\"px-2 py-3 text-muted-foreground\">\n {displayApp(row.topApp)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatNumber(row.chatCalls)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatNumber(row.chatThreads)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatTokens(row.inputTokens + row.outputTokens)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatSpend(row.costCents, billing)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction CompactBreakdown({\n rows,\n empty,\n billing,\n}: {\n rows: UsageMetricBucket[];\n empty: string;\n billing: UsageBillingMode;\n}) {\n const max = maxSpend(rows, billing);\n if (rows.length === 0) {\n return <div className=\"text-sm text-muted-foreground\">{empty}</div>;\n }\n return (\n <div className=\"space-y-3\">\n {rows.slice(0, 6).map((row) => (\n <div key={row.key} className=\"space-y-1\">\n <div className=\"flex items-center justify-between gap-3 text-xs\">\n <span className=\"truncate font-medium text-foreground\">\n {row.label}\n </span>\n <span className=\"shrink-0 tabular-nums text-muted-foreground\">\n {formatSpend(row.costCents, billing)}\n </span>\n </div>\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\">\n <div\n className=\"h-full rounded-full bg-muted-foreground\"\n style={{\n width: barWidth(\n displayAmountFromCostCents(row.costCents, billing),\n max,\n ),\n }}\n />\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction RecentTable({\n rows,\n billing,\n}: {\n rows: RecentUsageMetric[];\n billing: UsageBillingMode;\n}) {\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No recent LLM calls.\n </div>\n );\n }\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[760px] text-left text-xs\">\n <thead>\n <tr className=\"border-b text-muted-foreground\">\n <th className=\"px-2 py-2 font-medium\">When</th>\n <th className=\"px-2 py-2 font-medium\">User</th>\n <th className=\"px-2 py-2 font-medium\">App</th>\n <th className=\"px-2 py-2 font-medium\">Label</th>\n <th className=\"px-2 py-2 font-medium\">Model</th>\n <th className=\"px-2 py-2 text-right font-medium\">\n {billing.shortLabel}\n </th>\n </tr>\n </thead>\n <tbody>\n {rows.slice(0, 10).map((row) => (\n <tr key={row.id} className=\"border-b last:border-0\">\n <td className=\"px-2 py-3 text-muted-foreground\">\n {timeAgo(row.createdAt)}\n </td>\n <td className=\"max-w-56 px-2 py-3\">\n <div className=\"truncate text-foreground\">{row.ownerEmail}</div>\n </td>\n <td className=\"px-2 py-3 text-muted-foreground\">\n {displayApp(row.app)}\n </td>\n <td className=\"px-2 py-3\">\n <Badge variant=\"outline\">{row.label}</Badge>\n </td>\n <td className=\"max-w-48 px-2 py-3\">\n <div className=\"truncate text-muted-foreground\">\n {row.model}\n </div>\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatSpend(row.costCents, billing)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nexport default function MetricsRoute() {\n const t = useT();\n const [sinceDays, setSinceDays] = useState(30);\n const { data, isLoading, error } = useActionQuery(\n \"list-dispatch-usage-metrics\",\n { sinceDays },\n { refetchInterval: 30_000 },\n );\n const metrics = data as DispatchUsageMetrics | undefined;\n const billing = metrics?.billing ?? USD_BILLING;\n const totalTokens = useMemo(() => {\n if (!metrics) return 0;\n return (\n metrics.totals.inputTokens +\n metrics.totals.outputTokens +\n metrics.totals.cacheReadTokens +\n metrics.totals.cacheWriteTokens\n );\n }, [metrics]);\n\n return (\n <DispatchShell\n title={t(\"dispatch.nav.metrics\")}\n description={\n billing.unit === \"builder-credits\"\n ? t(\"dispatch.pages.metricsDescriptionBuilder\")\n : t(\"dispatch.pages.metricsDescriptionLlm\")\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"text-sm text-muted-foreground\">\n {metrics?.access.scope === \"organization\"\n ? `${metrics.access.totalUsers} workspace users`\n : `${metrics?.access.totalUsers ?? 0} signed-in users`}\n </div>\n <RangeSelector value={sinceDays} onChange={setSinceDays} />\n </div>\n\n {error ? (\n <Alert variant=\"destructive\">\n <IconAlertTriangle className=\"h-4 w-4\" />\n <AlertTitle>{t(\"dispatch.pages.metricsUnavailable\")}</AlertTitle>\n <AlertDescription>\n {error instanceof Error\n ? error.message\n : t(\"dispatch.pages.unableToLoadUsage\")}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {isLoading && !metrics ? <LoadingMetrics /> : null}\n\n {metrics ? (\n <>\n <div className=\"grid gap-3 md:grid-cols-2 xl:grid-cols-5\">\n <MetricCard\n label={billing.label}\n value={formatSpend(metrics.totals.costCents, billing)}\n detail={`${formatTokens(totalTokens)} total tokens`}\n icon={<IconCoin size={17} />}\n />\n <MetricCard\n label={t(\"dispatch.pages.llmCalls\")}\n value={formatNumber(metrics.totals.calls)}\n detail={`${formatNumber(metrics.totals.chatCalls)} chat turns`}\n icon={<IconActivity size={17} />}\n />\n <MetricCard\n label={t(\"dispatch.pages.activeUsers\")}\n value={formatNumber(metrics.totals.activeUsers)}\n detail={`${formatNumber(metrics.access.totalUsers)} users with access`}\n icon={<IconUsersGroup size={17} />}\n />\n <MetricCard\n label={t(\"dispatch.pages.workspaceAppsStat\")}\n value={formatNumber(metrics.totals.workspaceApps)}\n detail={`${formatNumber(metrics.byApp.length)} with usage`}\n icon={<IconApps size={17} />}\n />\n <MetricCard\n label={t(\"dispatch.pages.chatThreads\")}\n value={formatNumber(metrics.totals.chatThreads)}\n detail={`${formatNumber(metrics.totals.chatMessages)} messages`}\n icon={<IconMessages size={17} />}\n />\n </div>\n\n <div className=\"grid gap-4 xl:grid-cols-[minmax(0,1.35fr)_minmax(320px,0.65fr)]\">\n <Panel\n title={\n billing.unit === \"builder-credits\"\n ? \"Credit Spend By App\"\n : \"Spend By App\"\n }\n icon={<IconChartBar size={16} />}\n >\n <AppSpendRows rows={metrics.byApp} billing={billing} />\n </Panel>\n <Panel title=\"Daily Activity\" icon={<IconClockHour4 size={16} />}>\n <DailyActivity rows={metrics.daily} />\n </Panel>\n </div>\n\n <Panel title=\"Access By App\" icon={<IconApps size={16} />}>\n <AppAccessTable rows={metrics.appAccess} billing={billing} />\n </Panel>\n\n <Panel title=\"Users\" icon={<IconUsersGroup size={16} />}>\n <UserTable rows={metrics.byUser} billing={billing} />\n </Panel>\n\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Panel title=\"Models\" icon={<IconChartBar size={16} />}>\n <CompactBreakdown\n rows={metrics.byModel}\n empty=\"No model usage in this window.\"\n billing={billing}\n />\n </Panel>\n <Panel title=\"Work Types\" icon={<IconActivity size={16} />}>\n <CompactBreakdown\n rows={metrics.byLabel}\n empty=\"No labeled usage in this window.\"\n billing={billing}\n />\n </Panel>\n </div>\n\n <Panel title=\"Recent LLM Calls\" icon={<IconActivity size={16} />}>\n <RecentTable rows={metrics.recent} billing={billing} />\n </Panel>\n </>\n ) : null}\n </div>\n </DispatchShell>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/routes/pages/metrics.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useActionQuery, useT } from \"@agent-native/core/client\";\nimport {\n IconActivity,\n IconAlertTriangle,\n IconApps,\n IconChartBar,\n IconClockHour4,\n IconCoin,\n IconMessages,\n IconUsersGroup,\n} from \"@tabler/icons-react\";\nimport { useMemo, useState, type ReactNode } from \"react\";\n\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport { Alert, AlertDescription, AlertTitle } from \"../../components/ui/alert\";\nimport { Badge } from \"../../components/ui/badge\";\nimport { Button } from \"../../components/ui/button\";\nimport { Skeleton } from \"../../components/ui/skeleton\";\nimport { cn } from \"../../lib/utils\";\n\nexport function meta() {\n return [{ title: \"Metrics — Dispatch\" }];\n}\n\ninterface UsageMetricBucket {\n key: string;\n label: string;\n costCents: number;\n calls: number;\n chatCalls: number;\n inputTokens: number;\n outputTokens: number;\n activeUsers: number;\n lastActiveAt: number | null;\n}\n\ninterface UserUsageMetric extends UsageMetricBucket {\n ownerEmail: string;\n chatThreads: number;\n chatMessages: number;\n lastChatAt: number | null;\n topApp: string | null;\n role: string | null;\n}\n\ninterface AppAccessMetric {\n id: string;\n name: string;\n path: string;\n status?: \"ready\" | \"pending\";\n statusLabel?: string;\n isDispatch: boolean;\n accessLabel: string;\n accessUsers: number;\n usersWithUsage: number;\n usageCalls: number;\n chatCalls: number;\n costCents: number;\n lastActiveAt: number | null;\n}\n\ninterface DailyUsageMetric {\n date: string;\n costCents: number;\n calls: number;\n chatCalls: number;\n activeUsers: number;\n}\n\ninterface RecentUsageMetric {\n id: number;\n createdAt: number;\n ownerEmail: string;\n app: string;\n label: string;\n model: string;\n inputTokens: number;\n outputTokens: number;\n costCents: number;\n}\n\ninterface UsageBillingMode {\n unit: \"usd\" | \"builder-credits\";\n label: string;\n shortLabel: string;\n source: \"estimated-provider-cost\" | \"builder-agent-credits\";\n hardCostMarginMultiplier?: number;\n creditsPerUsd?: number;\n}\n\ninterface DispatchUsageMetrics {\n billing?: UsageBillingMode;\n sinceDays: number;\n access: {\n viewerEmail: string;\n orgId: string | null;\n role: string | null;\n scope: \"organization\" | \"solo\";\n totalUsers: number;\n };\n totals: {\n costCents: number;\n calls: number;\n chatCalls: number;\n inputTokens: number;\n outputTokens: number;\n cacheReadTokens: number;\n cacheWriteTokens: number;\n activeUsers: number;\n chatThreads: number;\n chatMessages: number;\n workspaceApps: number;\n };\n byApp: UsageMetricBucket[];\n byUser: UserUsageMetric[];\n byLabel: UsageMetricBucket[];\n byModel: UsageMetricBucket[];\n daily: DailyUsageMetric[];\n appAccess: AppAccessMetric[];\n recent: RecentUsageMetric[];\n}\n\nconst RANGES = [7, 30, 90] as const;\n\nconst USD_BILLING: UsageBillingMode = {\n unit: \"usd\",\n label: \"Estimated spend\",\n shortLabel: \"Cost\",\n source: \"estimated-provider-cost\",\n};\n\nfunction displayAmountFromCostCents(\n cents: number,\n billing: UsageBillingMode,\n): number {\n if (billing.unit !== \"builder-credits\") return cents;\n const margin = billing.hardCostMarginMultiplier ?? 1.25;\n const creditsPerUsd = billing.creditsPerUsd ?? 20;\n const credits = (cents / 100) * margin * creditsPerUsd;\n return credits <= 0 ? 0 : Math.ceil(credits * 1000) / 1000;\n}\n\nfunction formatCredits(credits: number): string {\n if (!Number.isFinite(credits) || credits === 0) return \"0 credits\";\n const maximumFractionDigits = credits < 1 ? 3 : credits < 10 ? 2 : 1;\n const value = credits.toLocaleString(undefined, {\n maximumFractionDigits,\n });\n return `${value} ${credits === 1 ? \"credit\" : \"credits\"}`;\n}\n\nfunction formatSpend(cents: number, billing: UsageBillingMode): string {\n if (billing.unit === \"builder-credits\") {\n return formatCredits(displayAmountFromCostCents(cents, billing));\n }\n if (!Number.isFinite(cents) || cents === 0) return \"$0.00\";\n if (Math.abs(cents) < 1) return `${cents.toFixed(3)}¢`;\n if (Math.abs(cents) < 100) return `${cents.toFixed(2)}¢`;\n return (cents / 100).toLocaleString(undefined, {\n style: \"currency\",\n currency: \"USD\",\n maximumFractionDigits: 2,\n });\n}\n\nfunction formatNumber(value: number): string {\n return new Intl.NumberFormat(undefined, {\n notation: value >= 10_000 ? \"compact\" : \"standard\",\n maximumFractionDigits: value >= 10_000 ? 1 : 0,\n }).format(value);\n}\n\nfunction formatTokens(value: number): string {\n return new Intl.NumberFormat(undefined, {\n notation: \"compact\",\n maximumFractionDigits: 1,\n }).format(value);\n}\n\nfunction timeAgo(timestamp: number | null): string {\n if (!timestamp) return \"No activity\";\n const diff = Date.now() - timestamp;\n if (diff < 60_000) return \"just now\";\n if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;\n if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;\n return `${Math.floor(diff / 86_400_000)}d ago`;\n}\n\nfunction displayApp(value: string | null | undefined): string {\n const trimmed = value?.trim();\n if (!trimmed || trimmed === \"unattributed\") return \"Unattributed\";\n return trimmed;\n}\n\nfunction maxSpend(\n rows: Array<{ costCents: number }>,\n billing: UsageBillingMode,\n): number {\n return rows.reduce(\n (max, row) =>\n Math.max(max, displayAmountFromCostCents(row.costCents, billing)),\n 0,\n );\n}\n\nfunction barWidth(value: number, max: number): string {\n if (max <= 0 || value <= 0) return \"0%\";\n return `${Math.max(4, Math.round((value / max) * 100))}%`;\n}\n\nfunction RangeSelector({\n value,\n onChange,\n}: {\n value: number;\n onChange: (value: number) => void;\n}) {\n return (\n <div className=\"flex rounded-md border bg-card p-0.5\">\n {RANGES.map((range) => (\n <Button\n key={range}\n type=\"button\"\n variant={value === range ? \"secondary\" : \"ghost\"}\n size=\"sm\"\n className=\"h-7 px-3 text-xs\"\n onClick={() => onChange(range)}\n >\n {range}d\n </Button>\n ))}\n </div>\n );\n}\n\nfunction MetricCard({\n label,\n value,\n detail,\n icon,\n}: {\n label: string;\n value: string;\n detail: string;\n icon: ReactNode;\n}) {\n return (\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {label}\n </span>\n <span className=\"text-muted-foreground\">{icon}</span>\n </div>\n <div className=\"text-2xl font-semibold tabular-nums text-foreground\">\n {value}\n </div>\n <div className=\"mt-1 truncate text-xs text-muted-foreground\">\n {detail}\n </div>\n </div>\n );\n}\n\nfunction Panel({\n title,\n icon,\n children,\n action,\n}: {\n title: string;\n icon: ReactNode;\n children: ReactNode;\n action?: ReactNode;\n}) {\n return (\n <section className=\"rounded-lg border bg-card\">\n <div className=\"flex items-center justify-between gap-3 border-b px-4 py-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"text-muted-foreground\">{icon}</span>\n <h2 className=\"truncate text-sm font-semibold text-foreground\">\n {title}\n </h2>\n </div>\n {action}\n </div>\n <div className=\"p-4\">{children}</div>\n </section>\n );\n}\n\nfunction LoadingMetrics() {\n return (\n <div className=\"space-y-4\">\n <div className=\"grid gap-3 md:grid-cols-2 xl:grid-cols-5\">\n {Array.from({ length: 5 }).map((_, index) => (\n <div key={index} className=\"rounded-lg border bg-card p-4\">\n <Skeleton className=\"mb-4 h-4 w-24\" />\n <Skeleton className=\"h-7 w-20\" />\n <Skeleton className=\"mt-3 h-3 w-28\" />\n </div>\n ))}\n </div>\n <Skeleton className=\"h-80 rounded-lg\" />\n </div>\n );\n}\n\nfunction AppSpendRows({\n rows,\n billing,\n}: {\n rows: UsageMetricBucket[];\n billing: UsageBillingMode;\n}) {\n const max = maxSpend(rows, billing);\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No LLM usage recorded for this window.\n </div>\n );\n }\n return (\n <div className=\"space-y-3\">\n {rows.map((row) => (\n <div key={row.key} className=\"space-y-1.5\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium text-foreground\">\n {displayApp(row.key)}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatNumber(row.chatCalls)} chats ·{\" \"}\n {formatNumber(row.activeUsers)} users\n </div>\n </div>\n <div className=\"shrink-0 text-right\">\n <div className=\"font-medium tabular-nums text-foreground\">\n {formatSpend(row.costCents, billing)}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatNumber(row.calls)} calls\n </div>\n </div>\n </div>\n <div className=\"h-2 overflow-hidden rounded-full bg-muted\">\n <div\n className=\"h-full rounded-full bg-foreground\"\n style={{\n width: barWidth(\n displayAmountFromCostCents(row.costCents, billing),\n max,\n ),\n }}\n />\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction DailyActivity({ rows }: { rows: DailyUsageMetric[] }) {\n const max = Math.max(\n 1,\n rows.reduce((value, row) => Math.max(value, row.calls), 0),\n );\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No activity in this window.\n </div>\n );\n }\n return (\n <div className=\"flex h-44 items-end gap-1\">\n {rows.map((row) => (\n <div\n key={row.date}\n className=\"group flex min-w-0 flex-1 flex-col items-center gap-2\"\n >\n <div className=\"relative flex h-36 w-full items-end rounded-sm bg-muted/60\">\n <div\n className=\"w-full rounded-sm bg-foreground transition group-hover:bg-primary\"\n style={{ height: `${Math.max(4, (row.calls / max) * 100)}%` }}\n />\n </div>\n <div className=\"hidden text-[10px] text-muted-foreground sm:block\">\n {row.date.slice(5)}\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction AppAccessTable({\n rows,\n billing,\n}: {\n rows: AppAccessMetric[];\n billing: UsageBillingMode;\n}) {\n const visibleRows = rows.filter((row) => !row.isDispatch);\n if (visibleRows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No workspace apps discovered yet.\n </div>\n );\n }\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[720px] text-left text-xs\">\n <thead>\n <tr className=\"border-b text-muted-foreground\">\n <th className=\"px-2 py-2 font-medium\">App</th>\n <th className=\"px-2 py-2 font-medium\">Access</th>\n <th className=\"px-2 py-2 text-right font-medium\">Users</th>\n <th className=\"px-2 py-2 text-right font-medium\">Chats</th>\n <th className=\"px-2 py-2 text-right font-medium\">\n {billing.shortLabel}\n </th>\n <th className=\"px-2 py-2 text-right font-medium\">Last activity</th>\n </tr>\n </thead>\n <tbody>\n {visibleRows.map((row) => (\n <tr key={row.id} className=\"border-b last:border-0\">\n <td className=\"px-2 py-3\">\n <div className=\"font-medium text-foreground\">{row.name}</div>\n <div className=\"font-mono text-[11px] text-muted-foreground\">\n {row.path}\n </div>\n </td>\n <td className=\"px-2 py-3\">\n <Badge\n variant=\"outline\"\n className={cn(\n row.status === \"pending\" &&\n \"border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300\",\n )}\n >\n {row.status === \"pending\"\n ? row.statusLabel || \"Builder branch\"\n : row.accessLabel}\n </Badge>\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatNumber(row.usersWithUsage)} /{\" \"}\n {formatNumber(row.accessUsers)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatNumber(row.chatCalls)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatSpend(row.costCents, billing)}\n </td>\n <td className=\"px-2 py-3 text-right text-muted-foreground\">\n {timeAgo(row.lastActiveAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction UserTable({\n rows,\n billing,\n}: {\n rows: UserUsageMetric[];\n billing: UsageBillingMode;\n}) {\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No users have triggered LLM usage in this window.\n </div>\n );\n }\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[760px] text-left text-xs\">\n <thead>\n <tr className=\"border-b text-muted-foreground\">\n <th className=\"px-2 py-2 font-medium\">User</th>\n <th className=\"px-2 py-2 font-medium\">Role</th>\n <th className=\"px-2 py-2 font-medium\">Top app</th>\n <th className=\"px-2 py-2 text-right font-medium\">Chats</th>\n <th className=\"px-2 py-2 text-right font-medium\">Threads</th>\n <th className=\"px-2 py-2 text-right font-medium\">Tokens</th>\n <th className=\"px-2 py-2 text-right font-medium\">\n {billing.shortLabel}\n </th>\n </tr>\n </thead>\n <tbody>\n {rows.slice(0, 12).map((row) => (\n <tr key={row.ownerEmail} className=\"border-b last:border-0\">\n <td className=\"max-w-64 px-2 py-3\">\n <div className=\"truncate font-medium text-foreground\">\n {row.ownerEmail}\n </div>\n <div className=\"text-muted-foreground\">\n {timeAgo(row.lastActiveAt ?? row.lastChatAt)}\n </div>\n </td>\n <td className=\"px-2 py-3\">\n <Badge variant=\"secondary\">{row.role ?? \"user\"}</Badge>\n </td>\n <td className=\"px-2 py-3 text-muted-foreground\">\n {displayApp(row.topApp)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatNumber(row.chatCalls)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatNumber(row.chatThreads)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatTokens(row.inputTokens + row.outputTokens)}\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatSpend(row.costCents, billing)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction CompactBreakdown({\n rows,\n empty,\n billing,\n}: {\n rows: UsageMetricBucket[];\n empty: string;\n billing: UsageBillingMode;\n}) {\n const max = maxSpend(rows, billing);\n if (rows.length === 0) {\n return <div className=\"text-sm text-muted-foreground\">{empty}</div>;\n }\n return (\n <div className=\"space-y-3\">\n {rows.slice(0, 6).map((row) => (\n <div key={row.key} className=\"space-y-1\">\n <div className=\"flex items-center justify-between gap-3 text-xs\">\n <span className=\"truncate font-medium text-foreground\">\n {row.label}\n </span>\n <span className=\"shrink-0 tabular-nums text-muted-foreground\">\n {formatSpend(row.costCents, billing)}\n </span>\n </div>\n <div className=\"h-1.5 overflow-hidden rounded-full bg-muted\">\n <div\n className=\"h-full rounded-full bg-muted-foreground\"\n style={{\n width: barWidth(\n displayAmountFromCostCents(row.costCents, billing),\n max,\n ),\n }}\n />\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction RecentTable({\n rows,\n billing,\n}: {\n rows: RecentUsageMetric[];\n billing: UsageBillingMode;\n}) {\n if (rows.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n No recent LLM calls.\n </div>\n );\n }\n return (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[760px] text-left text-xs\">\n <thead>\n <tr className=\"border-b text-muted-foreground\">\n <th className=\"px-2 py-2 font-medium\">When</th>\n <th className=\"px-2 py-2 font-medium\">User</th>\n <th className=\"px-2 py-2 font-medium\">App</th>\n <th className=\"px-2 py-2 font-medium\">Label</th>\n <th className=\"px-2 py-2 font-medium\">Model</th>\n <th className=\"px-2 py-2 text-right font-medium\">\n {billing.shortLabel}\n </th>\n </tr>\n </thead>\n <tbody>\n {rows.slice(0, 10).map((row) => (\n <tr key={row.id} className=\"border-b last:border-0\">\n <td className=\"px-2 py-3 text-muted-foreground\">\n {timeAgo(row.createdAt)}\n </td>\n <td className=\"max-w-56 px-2 py-3\">\n <div className=\"truncate text-foreground\">{row.ownerEmail}</div>\n </td>\n <td className=\"px-2 py-3 text-muted-foreground\">\n {displayApp(row.app)}\n </td>\n <td className=\"px-2 py-3\">\n <Badge variant=\"outline\">{row.label}</Badge>\n </td>\n <td className=\"max-w-48 px-2 py-3\">\n <div className=\"truncate text-muted-foreground\">\n {row.model}\n </div>\n </td>\n <td className=\"px-2 py-3 text-right tabular-nums\">\n {formatSpend(row.costCents, billing)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nexport default function MetricsRoute() {\n const t = useT();\n const [sinceDays, setSinceDays] = useState(30);\n const { data, isLoading, error } = useActionQuery(\n \"list-dispatch-usage-metrics\",\n { sinceDays },\n );\n const metrics = data as DispatchUsageMetrics | undefined;\n const billing = metrics?.billing ?? USD_BILLING;\n const totalTokens = useMemo(() => {\n if (!metrics) return 0;\n return (\n metrics.totals.inputTokens +\n metrics.totals.outputTokens +\n metrics.totals.cacheReadTokens +\n metrics.totals.cacheWriteTokens\n );\n }, [metrics]);\n\n return (\n <DispatchShell\n title={t(\"dispatch.nav.metrics\")}\n description={\n billing.unit === \"builder-credits\"\n ? t(\"dispatch.pages.metricsDescriptionBuilder\")\n : t(\"dispatch.pages.metricsDescriptionLlm\")\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"text-sm text-muted-foreground\">\n {metrics?.access.scope === \"organization\"\n ? `${metrics.access.totalUsers} workspace users`\n : `${metrics?.access.totalUsers ?? 0} signed-in users`}\n </div>\n <RangeSelector value={sinceDays} onChange={setSinceDays} />\n </div>\n\n {error ? (\n <Alert variant=\"destructive\">\n <IconAlertTriangle className=\"h-4 w-4\" />\n <AlertTitle>{t(\"dispatch.pages.metricsUnavailable\")}</AlertTitle>\n <AlertDescription>\n {error instanceof Error\n ? error.message\n : t(\"dispatch.pages.unableToLoadUsage\")}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {isLoading && !metrics ? <LoadingMetrics /> : null}\n\n {metrics ? (\n <>\n <div className=\"grid gap-3 md:grid-cols-2 xl:grid-cols-5\">\n <MetricCard\n label={billing.label}\n value={formatSpend(metrics.totals.costCents, billing)}\n detail={`${formatTokens(totalTokens)} total tokens`}\n icon={<IconCoin size={17} />}\n />\n <MetricCard\n label={t(\"dispatch.pages.llmCalls\")}\n value={formatNumber(metrics.totals.calls)}\n detail={`${formatNumber(metrics.totals.chatCalls)} chat turns`}\n icon={<IconActivity size={17} />}\n />\n <MetricCard\n label={t(\"dispatch.pages.activeUsers\")}\n value={formatNumber(metrics.totals.activeUsers)}\n detail={`${formatNumber(metrics.access.totalUsers)} users with access`}\n icon={<IconUsersGroup size={17} />}\n />\n <MetricCard\n label={t(\"dispatch.pages.workspaceAppsStat\")}\n value={formatNumber(metrics.totals.workspaceApps)}\n detail={`${formatNumber(metrics.byApp.length)} with usage`}\n icon={<IconApps size={17} />}\n />\n <MetricCard\n label={t(\"dispatch.pages.chatThreads\")}\n value={formatNumber(metrics.totals.chatThreads)}\n detail={`${formatNumber(metrics.totals.chatMessages)} messages`}\n icon={<IconMessages size={17} />}\n />\n </div>\n\n <div className=\"grid gap-4 xl:grid-cols-[minmax(0,1.35fr)_minmax(320px,0.65fr)]\">\n <Panel\n title={\n billing.unit === \"builder-credits\"\n ? \"Credit Spend By App\"\n : \"Spend By App\"\n }\n icon={<IconChartBar size={16} />}\n >\n <AppSpendRows rows={metrics.byApp} billing={billing} />\n </Panel>\n <Panel title=\"Daily Activity\" icon={<IconClockHour4 size={16} />}>\n <DailyActivity rows={metrics.daily} />\n </Panel>\n </div>\n\n <Panel title=\"Access By App\" icon={<IconApps size={16} />}>\n <AppAccessTable rows={metrics.appAccess} billing={billing} />\n </Panel>\n\n <Panel title=\"Users\" icon={<IconUsersGroup size={16} />}>\n <UserTable rows={metrics.byUser} billing={billing} />\n </Panel>\n\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Panel title=\"Models\" icon={<IconChartBar size={16} />}>\n <CompactBreakdown\n rows={metrics.byModel}\n empty=\"No model usage in this window.\"\n billing={billing}\n />\n </Panel>\n <Panel title=\"Work Types\" icon={<IconActivity size={16} />}>\n <CompactBreakdown\n rows={metrics.byLabel}\n empty=\"No labeled usage in this window.\"\n billing={billing}\n />\n </Panel>\n </div>\n\n <Panel title=\"Recent LLM Calls\" icon={<IconActivity size={16} />}>\n <RecentTable rows={metrics.recent} billing={billing} />\n </Panel>\n </>\n ) : null}\n </div>\n </DispatchShell>\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.6",
|
|
4
4
|
"description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
94
94
|
"vite": "8.1.0",
|
|
95
95
|
"vitest": "^4.1.5",
|
|
96
|
-
"@agent-native/core": "0.98.
|
|
96
|
+
"@agent-native/core": "0.98.4"
|
|
97
97
|
},
|
|
98
98
|
"peerDependencies": {
|
|
99
99
|
"@agent-native/core": ">=0.8.0",
|
|
@@ -25,7 +25,15 @@ function useTaskQueueStats() {
|
|
|
25
25
|
queryKey: [...TASK_QUEUE_QUERY_KEY, version],
|
|
26
26
|
queryFn: getDispatchTaskQueueStats,
|
|
27
27
|
placeholderData: (prev) => prev,
|
|
28
|
-
|
|
28
|
+
// Queue work can finish outside the action transport, so retain a bounded
|
|
29
|
+
// fallback only here. Idle workspaces back off aggressively; active queues
|
|
30
|
+
// keep the existing monitoring cadence.
|
|
31
|
+
refetchInterval: (query) => {
|
|
32
|
+
const stats = query.state.data;
|
|
33
|
+
return stats && (stats.pending > 0 || stats.processing > 0)
|
|
34
|
+
? 15_000
|
|
35
|
+
: 300_000;
|
|
36
|
+
},
|
|
29
37
|
staleTime: 5_000,
|
|
30
38
|
});
|
|
31
39
|
}
|
|
@@ -1,29 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useActionMutation,
|
|
3
|
-
useActionQuery,
|
|
4
|
-
useT,
|
|
5
|
-
} from "@agent-native/core/client";
|
|
1
|
+
import { useActionQuery, useT } from "@agent-native/core/client";
|
|
6
2
|
import {
|
|
7
3
|
IconApps,
|
|
8
|
-
IconBrain,
|
|
9
|
-
IconBrush,
|
|
10
|
-
IconCalendarMonth,
|
|
11
|
-
IconChartBar,
|
|
12
4
|
IconChevronDown,
|
|
13
|
-
IconClipboardList,
|
|
14
5
|
IconEyeOff,
|
|
15
|
-
IconFileText,
|
|
16
|
-
IconLoader2,
|
|
17
|
-
IconMail,
|
|
18
|
-
IconPhoto,
|
|
19
6
|
IconPlus,
|
|
20
|
-
IconPresentation,
|
|
21
|
-
IconScreenShare,
|
|
22
|
-
IconSparkles,
|
|
23
|
-
IconStack3,
|
|
24
7
|
} from "@tabler/icons-react";
|
|
25
8
|
import { useState } from "react";
|
|
26
|
-
import { toast } from "sonner";
|
|
27
9
|
|
|
28
10
|
import { ActionQueryError } from "../../components/action-query-error";
|
|
29
11
|
import { CreateAppPopover } from "../../components/create-app-popover";
|
|
@@ -49,33 +31,9 @@ interface WorkspaceInfo {
|
|
|
49
31
|
appCount: number;
|
|
50
32
|
}
|
|
51
33
|
|
|
52
|
-
interface AvailableTemplate {
|
|
53
|
-
name: string;
|
|
54
|
-
label: string;
|
|
55
|
-
hint: string;
|
|
56
|
-
icon: string;
|
|
57
|
-
color: string;
|
|
58
|
-
colorRgb: string;
|
|
59
|
-
core: boolean;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const TEMPLATE_ICONS: Record<string, typeof IconMail> = {
|
|
63
|
-
Mail: IconMail,
|
|
64
|
-
CalendarMonth: IconCalendarMonth,
|
|
65
|
-
FileText: IconFileText,
|
|
66
|
-
Presentation: IconPresentation,
|
|
67
|
-
ScreenShare: IconScreenShare,
|
|
68
|
-
Brain: IconBrain,
|
|
69
|
-
Photo: IconPhoto,
|
|
70
|
-
ChartBar: IconChartBar,
|
|
71
|
-
ClipboardList: IconClipboardList,
|
|
72
|
-
Brush: IconBrush,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
34
|
export default function AppsRoute() {
|
|
76
35
|
const t = useT();
|
|
77
36
|
const [showHidden, setShowHidden] = useState(false);
|
|
78
|
-
const [templatesOpen, setTemplatesOpen] = useState(false);
|
|
79
37
|
const appsQuery = useActionQuery("list-workspace-apps", {
|
|
80
38
|
includeAgentCards: false,
|
|
81
39
|
includeArchived: true,
|
|
@@ -86,13 +44,6 @@ export default function AppsRoute() {
|
|
|
86
44
|
{},
|
|
87
45
|
{ staleTime: 60_000 },
|
|
88
46
|
);
|
|
89
|
-
const templatesQuery = useActionQuery(
|
|
90
|
-
"list-available-workspace-templates",
|
|
91
|
-
{},
|
|
92
|
-
{ refetchInterval: 5_000 },
|
|
93
|
-
);
|
|
94
|
-
const { data: templates = [], isLoading: templatesLoading } = templatesQuery;
|
|
95
|
-
|
|
96
47
|
const ws = workspace as WorkspaceInfo | undefined;
|
|
97
48
|
const workspaceLabel = ws?.displayName ?? ws?.name ?? null;
|
|
98
49
|
const allApps = (apps as WorkspaceAppSummary[]).filter(
|
|
@@ -100,7 +51,6 @@ export default function AppsRoute() {
|
|
|
100
51
|
);
|
|
101
52
|
const visibleApps = allApps.filter((app) => !app.archived);
|
|
102
53
|
const archivedApps = allApps.filter((app) => app.archived);
|
|
103
|
-
const typedTemplates = templates as AvailableTemplate[];
|
|
104
54
|
const showAppSkeletons = appsLoading && allApps.length === 0;
|
|
105
55
|
|
|
106
56
|
return (
|
|
@@ -173,71 +123,6 @@ export default function AppsRoute() {
|
|
|
173
123
|
)}
|
|
174
124
|
</section>
|
|
175
125
|
|
|
176
|
-
{templatesQuery.isError ? (
|
|
177
|
-
<ActionQueryError
|
|
178
|
-
error={templatesQuery.error}
|
|
179
|
-
onRetry={() => void templatesQuery.refetch()}
|
|
180
|
-
/>
|
|
181
|
-
) : typedTemplates.length > 0 || templatesLoading ? (
|
|
182
|
-
<Collapsible open={templatesOpen} onOpenChange={setTemplatesOpen}>
|
|
183
|
-
<section className="space-y-3">
|
|
184
|
-
<div className="flex flex-wrap items-center justify-between gap-3 border-t pt-4">
|
|
185
|
-
<div className="flex min-w-0 items-center gap-2">
|
|
186
|
-
<IconStack3
|
|
187
|
-
size={16}
|
|
188
|
-
className="shrink-0 text-muted-foreground"
|
|
189
|
-
/>
|
|
190
|
-
<div className="min-w-0">
|
|
191
|
-
<h2 className="text-sm font-semibold text-foreground">
|
|
192
|
-
{t("dispatch.pages.templates")}
|
|
193
|
-
</h2>
|
|
194
|
-
<p className="text-xs text-muted-foreground">
|
|
195
|
-
{templatesLoading
|
|
196
|
-
? t("dispatch.pages.checkingTemplates")
|
|
197
|
-
: t("dispatch.pages.templatesAvailable", {
|
|
198
|
-
count: typedTemplates.length,
|
|
199
|
-
})}
|
|
200
|
-
</p>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
<CollapsibleTrigger asChild>
|
|
204
|
-
<Button
|
|
205
|
-
type="button"
|
|
206
|
-
variant="outline"
|
|
207
|
-
size="sm"
|
|
208
|
-
className="gap-1.5"
|
|
209
|
-
>
|
|
210
|
-
{templatesOpen
|
|
211
|
-
? t("dispatch.pages.hide")
|
|
212
|
-
: t("dispatch.pages.show")}
|
|
213
|
-
<IconChevronDown
|
|
214
|
-
size={14}
|
|
215
|
-
className={cn(
|
|
216
|
-
"transition-transform",
|
|
217
|
-
templatesOpen && "rotate-180",
|
|
218
|
-
)}
|
|
219
|
-
/>
|
|
220
|
-
</Button>
|
|
221
|
-
</CollapsibleTrigger>
|
|
222
|
-
</div>
|
|
223
|
-
<CollapsibleContent>
|
|
224
|
-
{templatesLoading && typedTemplates.length === 0 ? (
|
|
225
|
-
<AppsSkeletonGrid />
|
|
226
|
-
) : (
|
|
227
|
-
<div className="grid auto-rows-fr gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
228
|
-
{typedTemplates.map((template) => (
|
|
229
|
-
<AddTemplateCard
|
|
230
|
-
key={template.name}
|
|
231
|
-
template={template}
|
|
232
|
-
/>
|
|
233
|
-
))}
|
|
234
|
-
</div>
|
|
235
|
-
)}
|
|
236
|
-
</CollapsibleContent>
|
|
237
|
-
</section>
|
|
238
|
-
</Collapsible>
|
|
239
|
-
) : null}
|
|
240
|
-
|
|
241
126
|
{archivedApps.length > 0 ? (
|
|
242
127
|
<Collapsible open={showHidden} onOpenChange={setShowHidden}>
|
|
243
128
|
<section className="space-y-3">
|
|
@@ -345,65 +230,3 @@ function EmptyAppsState() {
|
|
|
345
230
|
</div>
|
|
346
231
|
);
|
|
347
232
|
}
|
|
348
|
-
|
|
349
|
-
function AddTemplateCard({ template }: { template: AvailableTemplate }) {
|
|
350
|
-
const Icon = TEMPLATE_ICONS[template.icon] ?? IconSparkles;
|
|
351
|
-
const scaffold = useActionMutation("scaffold-workspace-app", {
|
|
352
|
-
onSuccess: (result: any) => {
|
|
353
|
-
toast.success(
|
|
354
|
-
`Scaffolded apps/${result?.appId || template.name}. The gateway will pick it up shortly.`,
|
|
355
|
-
);
|
|
356
|
-
},
|
|
357
|
-
onError: (err) => {
|
|
358
|
-
toast.error(
|
|
359
|
-
`Could not scaffold ${template.label}: ${
|
|
360
|
-
err instanceof Error ? err.message : String(err)
|
|
361
|
-
}`,
|
|
362
|
-
);
|
|
363
|
-
},
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
return (
|
|
367
|
-
<div className="group relative flex h-full min-h-36 items-stretch gap-3 rounded-lg border bg-card p-4 transition hover:border-foreground/30">
|
|
368
|
-
<div
|
|
369
|
-
className="flex h-9 w-9 shrink-0 items-center justify-center self-start rounded-md"
|
|
370
|
-
style={{
|
|
371
|
-
backgroundColor: `rgb(${template.colorRgb} / 0.12)`,
|
|
372
|
-
color: template.color,
|
|
373
|
-
}}
|
|
374
|
-
>
|
|
375
|
-
<Icon size={18} />
|
|
376
|
-
</div>
|
|
377
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
378
|
-
<div className="flex min-w-0 items-center gap-2">
|
|
379
|
-
<h3 className="truncate text-sm font-semibold text-foreground">
|
|
380
|
-
{template.label}
|
|
381
|
-
</h3>
|
|
382
|
-
</div>
|
|
383
|
-
<p className="mt-1 line-clamp-2 text-xs leading-relaxed text-muted-foreground">
|
|
384
|
-
{template.hint}
|
|
385
|
-
</p>
|
|
386
|
-
<div className="mt-auto pt-3">
|
|
387
|
-
<Button
|
|
388
|
-
size="sm"
|
|
389
|
-
variant="outline"
|
|
390
|
-
disabled={scaffold.isPending}
|
|
391
|
-
onClick={() => scaffold.mutate({ template: template.name })}
|
|
392
|
-
>
|
|
393
|
-
{scaffold.isPending ? (
|
|
394
|
-
<>
|
|
395
|
-
<IconLoader2 size={14} className="mr-1.5 animate-spin" />
|
|
396
|
-
Adding...
|
|
397
|
-
</>
|
|
398
|
-
) : (
|
|
399
|
-
<>
|
|
400
|
-
<IconPlus size={14} className="mr-1.5" />
|
|
401
|
-
Add to workspace
|
|
402
|
-
</>
|
|
403
|
-
)}
|
|
404
|
-
</Button>
|
|
405
|
-
</div>
|
|
406
|
-
</div>
|
|
407
|
-
</div>
|
|
408
|
-
);
|
|
409
|
-
}
|
|
@@ -29,6 +29,11 @@ describe("Dispatch ChatRoute", () => {
|
|
|
29
29
|
let root: Root;
|
|
30
30
|
|
|
31
31
|
beforeEach(() => {
|
|
32
|
+
// ChatRoute intentionally clears navigation handoff state on a zero-delay
|
|
33
|
+
// timer. Keep that timer deterministic so a busy workspace test run cannot
|
|
34
|
+
// advance to the post-handoff hero render before this spec inspects the
|
|
35
|
+
// transition frame.
|
|
36
|
+
vi.useFakeTimers();
|
|
32
37
|
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
33
38
|
clientState.surfaceProps = null;
|
|
34
39
|
container = document.createElement("div");
|
|
@@ -39,6 +44,7 @@ describe("Dispatch ChatRoute", () => {
|
|
|
39
44
|
afterEach(() => {
|
|
40
45
|
act(() => root.unmount());
|
|
41
46
|
container.remove();
|
|
47
|
+
vi.useRealTimers();
|
|
42
48
|
vi.unstubAllGlobals();
|
|
43
49
|
});
|
|
44
50
|
|
|
@@ -643,7 +643,6 @@ export default function MetricsRoute() {
|
|
|
643
643
|
const { data, isLoading, error } = useActionQuery(
|
|
644
644
|
"list-dispatch-usage-metrics",
|
|
645
645
|
{ sinceDays },
|
|
646
|
-
{ refetchInterval: 30_000 },
|
|
647
646
|
);
|
|
648
647
|
const metrics = data as DispatchUsageMetrics | undefined;
|
|
649
648
|
const billing = metrics?.billing ?? USD_BILLING;
|
package/src/styles/dispatch.css
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
.dispatch-chat-panel [data-agent-empty-state="centered"] .dispatch-chat-intro {
|
|
21
21
|
display: grid;
|
|
22
22
|
width: 100%;
|
|
23
|
-
max-width: min(
|
|
23
|
+
max-width: min(570px, 90%);
|
|
24
24
|
gap: 0.5rem;
|
|
25
25
|
margin: 0 auto 1rem;
|
|
26
26
|
text-align: center;
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
|
|
51
51
|
.dispatch-chat-panel [data-agent-empty-state="centered"] .agent-composer-area {
|
|
52
52
|
width: 100%;
|
|
53
|
-
max-width: min(
|
|
53
|
+
max-width: min(570px, 90%);
|
|
54
54
|
padding: 0;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
.dispatch-chat-panel [data-agent-empty-state="centered"] .agent-composer-root {
|
|
58
|
-
min-height:
|
|
58
|
+
min-height: 7.875rem;
|
|
59
59
|
border-color: hsl(var(--border));
|
|
60
60
|
border-radius: 1rem;
|
|
61
61
|
background: hsl(var(--card));
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
.dispatch-chat-panel
|
|
73
73
|
[data-agent-empty-state="centered"]
|
|
74
74
|
.agent-composer-prosemirror {
|
|
75
|
-
min-height:
|
|
75
|
+
min-height: 5.0625rem;
|
|
76
76
|
font-size: 1rem;
|
|
77
77
|
line-height: 1.625rem;
|
|
78
78
|
}
|
|
@@ -103,6 +103,6 @@
|
|
|
103
103
|
.dispatch-chat-panel
|
|
104
104
|
[data-agent-empty-state="centered"]
|
|
105
105
|
.agent-composer-root {
|
|
106
|
-
min-height:
|
|
106
|
+
min-height: 6.75rem;
|
|
107
107
|
}
|
|
108
108
|
}
|