@agent-native/core 0.77.24 → 0.78.1
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/docs/content/template-analytics.md +5 -0
- package/corpus/core/docs/content/tracking.md +45 -0
- package/corpus/core/package.json +3 -1
- package/corpus/core/src/agent/types.ts +22 -0
- package/corpus/core/src/client/AgentPanel.tsx +34 -18
- package/corpus/core/src/client/AssistantChat.tsx +40 -0
- package/corpus/core/src/client/agent-chat.ts +173 -0
- package/corpus/core/src/client/analytics.ts +141 -5
- package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
- package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
- package/corpus/core/src/client/composer/types.ts +22 -0
- package/corpus/core/src/client/index.ts +14 -0
- package/corpus/core/src/client/session-replay.ts +800 -0
- package/corpus/core/src/db/schema.ts +12 -0
- package/corpus/core/src/deploy/build.ts +75 -0
- package/corpus/core/src/private-blob/index.ts +18 -0
- package/corpus/core/src/private-blob/registry.ts +241 -0
- package/corpus/core/src/private-blob/types.ts +55 -0
- package/corpus/core/src/scripts/utils.ts +34 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
- package/corpus/core/src/server/email.ts +5 -0
- package/corpus/core/src/server/index.ts +1 -0
- package/corpus/core/src/styles/agent-native.css +24 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +43 -0
- package/corpus/templates/analytics/AGENTS.md +32 -1
- package/corpus/templates/analytics/DEVELOPING.md +9 -1
- package/corpus/templates/analytics/actions/delete-dashboard-report-subscription.ts +24 -0
- package/corpus/templates/analytics/actions/get-session-replay-events.ts +50 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +27 -0
- package/corpus/templates/analytics/actions/list-dashboard-report-subscriptions.ts +24 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +59 -0
- package/corpus/templates/analytics/actions/navigate.ts +15 -5
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
- package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +36 -0
- package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +60 -0
- package/corpus/templates/analytics/actions/view-screen.ts +50 -2
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +51 -2
- package/corpus/templates/analytics/app/components/layout/Header.tsx +4 -1
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +14 -2
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +6 -6
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +35 -6
- package/corpus/templates/analytics/app/i18n-data.ts +1119 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +437 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -12
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +43 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +136 -43
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-filters.ts +26 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +450 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +419 -0
- package/corpus/templates/analytics/app/routes/overview.tsx +13 -6
- package/corpus/templates/analytics/app/routes/sessions.$recordingId.tsx +10 -0
- package/corpus/templates/analytics/app/routes/sessions._index.tsx +10 -0
- package/corpus/templates/analytics/changelog/2026-06-25-analytics-now-opens-directly-to-ask-instead-of-the-old-overv.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-now-show-reliable-blue-placement-lines-whil.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboards-can-now-send-scheduled-daily-email-reports-from-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-browser-sessions-can-now-be-reviewed-from.md +6 -0
- package/corpus/templates/analytics/package.json +5 -2
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +131 -0
- package/corpus/templates/analytics/scripts/seed-session-replay.ts +198 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +140 -27
- package/corpus/templates/analytics/server/db/index.ts +12 -0
- package/corpus/templates/analytics/server/db/schema.ts +131 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +281 -0
- package/corpus/templates/analytics/server/handlers/sql-query.ts +8 -348
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +66 -0
- package/corpus/templates/analytics/server/jobs/session-replay-retention.ts +29 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +365 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +473 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +405 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +63 -18
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +98 -26
- package/corpus/templates/analytics/server/lib/session-replay.ts +1645 -0
- package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +47 -0
- package/corpus/templates/analytics/server/plugins/db.ts +200 -0
- package/corpus/templates/analytics/server/plugins/session-replay-retention-jobs.ts +41 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.options.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.post.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-reports/run.post.ts +61 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks/[seq].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/events.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/manifest.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
- package/corpus/templates/assets/AGENTS.md +18 -3
- package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
- package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
- package/corpus/templates/assets/actions/generate-asset.ts +30 -23
- package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
- package/corpus/templates/assets/actions/generate-image.ts +46 -30
- package/corpus/templates/assets/actions/generate-video.ts +18 -3
- package/corpus/templates/assets/actions/list-assets.ts +39 -8
- package/corpus/templates/assets/actions/list-libraries.ts +24 -2
- package/corpus/templates/assets/actions/navigate.ts +1 -1
- package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
- package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
- package/corpus/templates/assets/actions/variant-slots.ts +131 -28
- package/corpus/templates/assets/actions/view-screen.ts +9 -0
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
- package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
- package/corpus/templates/assets/app/i18n-data.ts +1815 -0
- package/corpus/templates/assets/app/lib/libraries.ts +6 -0
- package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/audit.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
- package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
- package/corpus/templates/assets/app/routes/library.tsx +1454 -175
- package/corpus/templates/assets/server/db/index.ts +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +86 -15
- package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
- package/corpus/templates/assets/server/plugins/db.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +6 -3
- package/dist/agent/types.d.ts +21 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +6 -2
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +10 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +15 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +32 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +113 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/analytics.d.ts +22 -3
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +96 -4
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +2 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +228 -56
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/MentionReference.js +3 -0
- package/dist/client/composer/extensions/MentionReference.js.map +1 -1
- package/dist/client/composer/types.d.ts +21 -0
- package/dist/client/composer/types.d.ts.map +1 -1
- package/dist/client/composer/types.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +41 -0
- package/dist/client/session-replay.d.ts.map +1 -0
- package/dist/client/session-replay.js +595 -0
- package/dist/client/session-replay.js.map +1 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/schema.d.ts +3 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +6 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/deploy/build.d.ts +8 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +66 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +8 -8
- package/dist/private-blob/index.d.ts +3 -0
- package/dist/private-blob/index.d.ts.map +1 -0
- package/dist/private-blob/index.js +2 -0
- package/dist/private-blob/index.js.map +1 -0
- package/dist/private-blob/registry.d.ts +10 -0
- package/dist/private-blob/registry.d.ts.map +1 -0
- package/dist/private-blob/registry.js +152 -0
- package/dist/private-blob/registry.js.map +1 -0
- package/dist/private-blob/types.d.ts +51 -0
- package/dist/private-blob/types.d.ts.map +1 -0
- package/dist/private-blob/types.js +2 -0
- package/dist/private-blob/types.js.map +1 -0
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/scripts/utils.d.ts +4 -4
- package/dist/scripts/utils.d.ts.map +1 -1
- package/dist/scripts/utils.js +30 -6
- package/dist/scripts/utils.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/email.d.ts +2 -0
- package/dist/server/email.d.ts.map +1 -1
- package/dist/server/email.js +3 -0
- package/dist/server/email.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +24 -0
- package/docs/content/template-analytics.md +5 -0
- package/docs/content/tracking.md +45 -0
- package/package.json +3 -1
- package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +0 -209
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readClientAppState,
|
|
3
|
+
sendToAgentChat,
|
|
4
|
+
useActionMutation,
|
|
5
|
+
useActionQuery,
|
|
6
|
+
useT,
|
|
7
|
+
} from "@agent-native/core/client";
|
|
8
|
+
import { IconMessageCircle, IconPhoto, IconTrash } from "@tabler/icons-react";
|
|
9
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
10
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
AlertDialog,
|
|
15
|
+
AlertDialogAction,
|
|
16
|
+
AlertDialogCancel,
|
|
17
|
+
AlertDialogContent,
|
|
18
|
+
AlertDialogDescription,
|
|
19
|
+
AlertDialogFooter,
|
|
20
|
+
AlertDialogHeader,
|
|
21
|
+
AlertDialogTitle,
|
|
22
|
+
} from "@/components/ui/alert-dialog";
|
|
23
|
+
import { Badge } from "@/components/ui/badge";
|
|
24
|
+
import { Button } from "@/components/ui/button";
|
|
25
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
26
|
+
import { assetPreviewSources } from "@/lib/asset-preview-sources";
|
|
27
|
+
|
|
28
|
+
import type {
|
|
29
|
+
AssetVariantState,
|
|
30
|
+
ImageLibrarySummary,
|
|
31
|
+
} from "../../../shared/api";
|
|
32
|
+
|
|
33
|
+
type LibraryListResult = {
|
|
34
|
+
libraries?: ImageLibrarySummary[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function variantStateKey(threadId: string | null) {
|
|
38
|
+
return threadId ? `asset-variants:${threadId}` : "asset-variants";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function slotTime(slot: AssetVariantState["slots"][number]): number {
|
|
42
|
+
const raw = slot.createdAt ?? slot.updatedAt ?? "";
|
|
43
|
+
const time = Date.parse(raw);
|
|
44
|
+
return Number.isNaN(time) ? 0 : time;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function stalePendingRunId(
|
|
48
|
+
slot: AssetVariantState["slots"][number],
|
|
49
|
+
): string | null {
|
|
50
|
+
if (slot.status !== "pending") return null;
|
|
51
|
+
if (!slot.runId) return null;
|
|
52
|
+
const timestamp = slotTime(slot);
|
|
53
|
+
if (!timestamp) return null;
|
|
54
|
+
return Date.now() - timestamp >= 2 * 60 * 1000 ? slot.runId : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function GenerationResults({ threadId }: { threadId: string | null }) {
|
|
58
|
+
const t = useT();
|
|
59
|
+
const queryClient = useQueryClient();
|
|
60
|
+
const [clearAllOpen, setClearAllOpen] = useState(false);
|
|
61
|
+
const stateKey = variantStateKey(threadId);
|
|
62
|
+
const stateQueryKey = useMemo(() => ["app-state", stateKey], [stateKey]);
|
|
63
|
+
const { data: variants } = useQuery({
|
|
64
|
+
queryKey: stateQueryKey,
|
|
65
|
+
queryFn: async ({ signal }) => {
|
|
66
|
+
return readClientAppState<AssetVariantState>(stateKey, { signal });
|
|
67
|
+
},
|
|
68
|
+
refetchInterval: 1000,
|
|
69
|
+
});
|
|
70
|
+
const { data: librariesData } = useActionQuery("list-libraries", {
|
|
71
|
+
compact: true,
|
|
72
|
+
} as any) as { data?: LibraryListResult };
|
|
73
|
+
const saveGenerated = useActionMutation("save-generated-image");
|
|
74
|
+
const dismissSlot = useActionMutation("dismiss-variant-slots");
|
|
75
|
+
const refreshGeneration = useActionMutation("refresh-generation-run");
|
|
76
|
+
const refreshingRunIds = useRef<Set<string>>(new Set());
|
|
77
|
+
const libraryTitle = useMemo(() => {
|
|
78
|
+
if (!variants?.libraryId) return null;
|
|
79
|
+
return (
|
|
80
|
+
librariesData?.libraries?.find((item) => item.id === variants.libraryId)
|
|
81
|
+
?.title ?? null
|
|
82
|
+
);
|
|
83
|
+
}, [librariesData?.libraries, variants?.libraryId]);
|
|
84
|
+
const slots = useMemo(
|
|
85
|
+
() =>
|
|
86
|
+
(variants?.slots ?? [])
|
|
87
|
+
.slice()
|
|
88
|
+
.sort(
|
|
89
|
+
(left, right) =>
|
|
90
|
+
slotTime(right) - slotTime(left) ||
|
|
91
|
+
right.slotId.localeCompare(left.slotId),
|
|
92
|
+
),
|
|
93
|
+
[variants?.slots],
|
|
94
|
+
);
|
|
95
|
+
const belongsToThread = Boolean(
|
|
96
|
+
variants &&
|
|
97
|
+
(threadId ? variants.threadId === threadId : !variants.threadId),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (!belongsToThread) return;
|
|
102
|
+
if (!slots.length || refreshGeneration.isPending) return;
|
|
103
|
+
const runId = slots
|
|
104
|
+
.map(stalePendingRunId)
|
|
105
|
+
.find((id): id is string =>
|
|
106
|
+
Boolean(id && !refreshingRunIds.current.has(id)),
|
|
107
|
+
);
|
|
108
|
+
if (!runId) return;
|
|
109
|
+
refreshingRunIds.current.add(runId);
|
|
110
|
+
refreshGeneration.mutate(
|
|
111
|
+
{ runId },
|
|
112
|
+
{
|
|
113
|
+
onSettled: () => {
|
|
114
|
+
window.setTimeout(() => {
|
|
115
|
+
refreshingRunIds.current.delete(runId);
|
|
116
|
+
}, 30_000);
|
|
117
|
+
void queryClient.invalidateQueries({
|
|
118
|
+
queryKey: stateQueryKey,
|
|
119
|
+
refetchType: "active",
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
}, [belongsToThread, queryClient, refreshGeneration, slots, stateQueryKey]);
|
|
125
|
+
|
|
126
|
+
if (!belongsToThread || !variants) return null;
|
|
127
|
+
if (slots.length === 0) return null;
|
|
128
|
+
|
|
129
|
+
const pendingCount = slots.filter((slot) => slot.status === "pending").length;
|
|
130
|
+
const readyCount = slots.filter((slot) => slot.status === "ready").length;
|
|
131
|
+
const failedCount = slots.filter((slot) => slot.status === "failed").length;
|
|
132
|
+
const statusSummary = [
|
|
133
|
+
pendingCount > 0
|
|
134
|
+
? t("library.generatingCount", { count: pendingCount })
|
|
135
|
+
: null,
|
|
136
|
+
readyCount > 0 ? t("library.readyCount", { count: readyCount }) : null,
|
|
137
|
+
failedCount > 0 ? t("library.failedCount", { count: failedCount }) : null,
|
|
138
|
+
]
|
|
139
|
+
.filter(Boolean)
|
|
140
|
+
.join(" / ");
|
|
141
|
+
|
|
142
|
+
function clearAllCandidates() {
|
|
143
|
+
dismissSlot.mutate(
|
|
144
|
+
{ threadId, scope: "all" },
|
|
145
|
+
{
|
|
146
|
+
onSuccess: () => {
|
|
147
|
+
setClearAllOpen(false);
|
|
148
|
+
void queryClient.invalidateQueries({
|
|
149
|
+
queryKey: stateQueryKey,
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
onError: (error) =>
|
|
153
|
+
toast.error(error.message || t("library.couldNotClearCandidates")),
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
<AlertDialog open={clearAllOpen} onOpenChange={setClearAllOpen}>
|
|
161
|
+
<AlertDialogContent>
|
|
162
|
+
<AlertDialogHeader>
|
|
163
|
+
<AlertDialogTitle>
|
|
164
|
+
{t("library.clearGeneratedCandidatesTitle")}
|
|
165
|
+
</AlertDialogTitle>
|
|
166
|
+
<AlertDialogDescription>
|
|
167
|
+
{t("library.clearGeneratedCandidatesDescription")}
|
|
168
|
+
</AlertDialogDescription>
|
|
169
|
+
</AlertDialogHeader>
|
|
170
|
+
<AlertDialogFooter>
|
|
171
|
+
<AlertDialogCancel disabled={dismissSlot.isPending}>
|
|
172
|
+
{t("library.cancel")}
|
|
173
|
+
</AlertDialogCancel>
|
|
174
|
+
<AlertDialogAction
|
|
175
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
176
|
+
disabled={dismissSlot.isPending}
|
|
177
|
+
onClick={(event) => {
|
|
178
|
+
event.preventDefault();
|
|
179
|
+
clearAllCandidates();
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
{dismissSlot.isPending ? (
|
|
183
|
+
<>
|
|
184
|
+
<Spinner className="h-4 w-4" />
|
|
185
|
+
{t("library.clearing")}
|
|
186
|
+
</>
|
|
187
|
+
) : (
|
|
188
|
+
t("library.clearCandidates")
|
|
189
|
+
)}
|
|
190
|
+
</AlertDialogAction>
|
|
191
|
+
</AlertDialogFooter>
|
|
192
|
+
</AlertDialogContent>
|
|
193
|
+
</AlertDialog>
|
|
194
|
+
|
|
195
|
+
<section className="mx-auto mb-4 w-full max-w-[760px] px-4">
|
|
196
|
+
<div className="overflow-hidden rounded-xl border border-border bg-card shadow-sm">
|
|
197
|
+
<div className="flex items-start justify-between gap-3 border-b border-border/80 px-3 py-3">
|
|
198
|
+
<div className="flex min-w-0 items-start gap-2.5">
|
|
199
|
+
<div className="flex size-7 shrink-0 items-center justify-center rounded-md border border-border bg-muted/50 text-muted-foreground">
|
|
200
|
+
{pendingCount > 0 ? (
|
|
201
|
+
<Spinner className="h-3.5 w-3.5" />
|
|
202
|
+
) : (
|
|
203
|
+
<IconPhoto className="h-4 w-4" />
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
<div className="min-w-0">
|
|
207
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
208
|
+
<h2 className="truncate text-sm font-semibold">
|
|
209
|
+
{t("library.generatedCandidatesTitle")}
|
|
210
|
+
</h2>
|
|
211
|
+
<Badge variant="secondary" className="shrink-0">
|
|
212
|
+
{statusSummary}
|
|
213
|
+
</Badge>
|
|
214
|
+
</div>
|
|
215
|
+
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
|
216
|
+
{libraryTitle || t("library.noBrandKit")} / {variants.prompt}
|
|
217
|
+
</p>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
<Button
|
|
221
|
+
variant="ghost"
|
|
222
|
+
size="sm"
|
|
223
|
+
className="h-7 shrink-0 gap-1 px-2 text-xs text-muted-foreground hover:text-destructive"
|
|
224
|
+
disabled={dismissSlot.isPending}
|
|
225
|
+
onClick={() => setClearAllOpen(true)}
|
|
226
|
+
>
|
|
227
|
+
<IconTrash className="h-3.5 w-3.5" />
|
|
228
|
+
{t("library.clear")}
|
|
229
|
+
</Button>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<div className="max-h-[min(640px,52vh)] overflow-y-auto p-3">
|
|
233
|
+
<div
|
|
234
|
+
className={
|
|
235
|
+
slots.length === 1
|
|
236
|
+
? "grid grid-cols-1 gap-3"
|
|
237
|
+
: "grid grid-cols-1 gap-3 sm:grid-cols-2"
|
|
238
|
+
}
|
|
239
|
+
>
|
|
240
|
+
{slots.map((slot, index) => (
|
|
241
|
+
<GenerationResultItem
|
|
242
|
+
key={slot.slotId}
|
|
243
|
+
slot={slot}
|
|
244
|
+
index={index}
|
|
245
|
+
prompt={variants.prompt}
|
|
246
|
+
libraryTitle={libraryTitle}
|
|
247
|
+
isSaving={saveGenerated.isPending}
|
|
248
|
+
isDismissing={dismissSlot.isPending}
|
|
249
|
+
onSave={() => {
|
|
250
|
+
if (!slot.assetId && !slot.slotId) return;
|
|
251
|
+
saveGenerated.mutate(
|
|
252
|
+
{
|
|
253
|
+
...(slot.assetId ? { assetId: slot.assetId } : {}),
|
|
254
|
+
...(slot.slotId ? { slotId: slot.slotId } : {}),
|
|
255
|
+
threadId,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
onSuccess: () => {
|
|
259
|
+
toast.success(t("library.savedGeneratedAsset"));
|
|
260
|
+
void queryClient.invalidateQueries({
|
|
261
|
+
queryKey: stateQueryKey,
|
|
262
|
+
});
|
|
263
|
+
void queryClient.invalidateQueries({
|
|
264
|
+
queryKey: ["action", "get-library"],
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
onError: (error) =>
|
|
268
|
+
toast.error(
|
|
269
|
+
error.message || t("library.couldNotSaveCandidate"),
|
|
270
|
+
),
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
}}
|
|
274
|
+
onDismiss={() => {
|
|
275
|
+
dismissSlot.mutate(
|
|
276
|
+
{ slotId: slot.slotId, threadId },
|
|
277
|
+
{
|
|
278
|
+
onSuccess: () => {
|
|
279
|
+
void queryClient.invalidateQueries({
|
|
280
|
+
queryKey: stateQueryKey,
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
onError: (error) =>
|
|
284
|
+
toast.error(
|
|
285
|
+
error.message ||
|
|
286
|
+
t("library.couldNotDismissCandidate"),
|
|
287
|
+
),
|
|
288
|
+
},
|
|
289
|
+
);
|
|
290
|
+
}}
|
|
291
|
+
/>
|
|
292
|
+
))}
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
</section>
|
|
297
|
+
</>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function GenerationResultItem({
|
|
302
|
+
slot,
|
|
303
|
+
index,
|
|
304
|
+
prompt,
|
|
305
|
+
libraryTitle,
|
|
306
|
+
isSaving,
|
|
307
|
+
isDismissing,
|
|
308
|
+
onSave,
|
|
309
|
+
onDismiss,
|
|
310
|
+
}: {
|
|
311
|
+
slot: AssetVariantState["slots"][number];
|
|
312
|
+
index: number;
|
|
313
|
+
prompt: string;
|
|
314
|
+
libraryTitle: string | null;
|
|
315
|
+
isSaving: boolean;
|
|
316
|
+
isDismissing: boolean;
|
|
317
|
+
onSave: () => void;
|
|
318
|
+
onDismiss: () => void;
|
|
319
|
+
}) {
|
|
320
|
+
const ready = slot.status === "ready" && Boolean(slot.assetId);
|
|
321
|
+
const t = useT();
|
|
322
|
+
return (
|
|
323
|
+
<article className="overflow-hidden rounded-lg border border-border/80 bg-background/70">
|
|
324
|
+
<div className="relative aspect-[16/10] overflow-hidden bg-muted/40">
|
|
325
|
+
<GenerationSlotPreview slot={slot} />
|
|
326
|
+
<div className="absolute left-2 top-2 flex items-center gap-1 rounded-full border border-border/70 bg-background/90 px-2 py-1 text-[11px] font-medium shadow-sm">
|
|
327
|
+
{slot.status === "pending" ? <Spinner className="h-3 w-3" /> : null}
|
|
328
|
+
<span>
|
|
329
|
+
{slot.status === "pending"
|
|
330
|
+
? t("library.generating")
|
|
331
|
+
: slot.status === "ready"
|
|
332
|
+
? t("library.candidateWithNumber", { number: index + 1 })
|
|
333
|
+
: t("library.failed")}
|
|
334
|
+
</span>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
<div className="space-y-2 p-2.5">
|
|
338
|
+
<div className="min-w-0 text-xs">
|
|
339
|
+
<div className="truncate font-medium">
|
|
340
|
+
{slot.status === "ready"
|
|
341
|
+
? t("library.readyToSave")
|
|
342
|
+
: slot.status === "pending"
|
|
343
|
+
? t("library.stillRendering")
|
|
344
|
+
: t("library.generationFailed")}
|
|
345
|
+
</div>
|
|
346
|
+
<div className="mt-0.5 truncate text-muted-foreground">
|
|
347
|
+
{libraryTitle || t("library.noBrandKit")}
|
|
348
|
+
{prompt ? ` / ${prompt}` : null}
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
352
|
+
<Button
|
|
353
|
+
size="sm"
|
|
354
|
+
className="h-7 px-2 text-xs"
|
|
355
|
+
disabled={!ready || isSaving}
|
|
356
|
+
onClick={onSave}
|
|
357
|
+
>
|
|
358
|
+
{isSaving ? <Spinner className="h-3 w-3" /> : t("library.save")}
|
|
359
|
+
</Button>
|
|
360
|
+
<Button
|
|
361
|
+
variant="outline"
|
|
362
|
+
size="sm"
|
|
363
|
+
className="h-7 gap-1 px-2 text-xs"
|
|
364
|
+
disabled={!slot.assetId}
|
|
365
|
+
onClick={() =>
|
|
366
|
+
sendToAgentChat({
|
|
367
|
+
message: `Refine generated asset ${slot.assetId}: `,
|
|
368
|
+
context: [
|
|
369
|
+
"## Assets candidate",
|
|
370
|
+
`Asset ID: ${slot.assetId}`,
|
|
371
|
+
`Run ID: ${slot.runId ?? "unknown"}`,
|
|
372
|
+
`Prompt: ${prompt}`,
|
|
373
|
+
libraryTitle ? `Brand kit: ${libraryTitle}` : "",
|
|
374
|
+
"Use refine-image with assetId when the user describes the change.",
|
|
375
|
+
]
|
|
376
|
+
.filter(Boolean)
|
|
377
|
+
.join("\n"),
|
|
378
|
+
submit: false,
|
|
379
|
+
openSidebar: true,
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
>
|
|
383
|
+
<IconMessageCircle className="h-3.5 w-3.5" />
|
|
384
|
+
{t("library.refine")}
|
|
385
|
+
</Button>
|
|
386
|
+
<Button
|
|
387
|
+
variant="ghost"
|
|
388
|
+
size="icon"
|
|
389
|
+
className="h-7 w-7 text-muted-foreground hover:bg-destructive/10 hover:text-destructive"
|
|
390
|
+
disabled={isDismissing}
|
|
391
|
+
onClick={onDismiss}
|
|
392
|
+
aria-label={t("library.deleteCandidate")}
|
|
393
|
+
>
|
|
394
|
+
<IconTrash className="h-3.5 w-3.5" />
|
|
395
|
+
</Button>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
</article>
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function GenerationSlotPreview({
|
|
403
|
+
slot,
|
|
404
|
+
}: {
|
|
405
|
+
slot: AssetVariantState["slots"][number];
|
|
406
|
+
}) {
|
|
407
|
+
const t = useT();
|
|
408
|
+
const sources = assetPreviewSources(slot, "thumbnail");
|
|
409
|
+
const src = sources[0];
|
|
410
|
+
if (src) {
|
|
411
|
+
return <img src={src} alt="" className="h-full w-full object-contain" />;
|
|
412
|
+
}
|
|
413
|
+
if (slot.status === "failed") {
|
|
414
|
+
return (
|
|
415
|
+
<div className="flex h-full w-full items-center justify-center p-2 text-center text-[11px] text-destructive">
|
|
416
|
+
{slot.error || t("library.failed")}
|
|
417
|
+
</div>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return (
|
|
421
|
+
<div className="flex h-full w-full items-center justify-center bg-muted">
|
|
422
|
+
<IconPhoto className="h-6 w-6 animate-pulse text-muted-foreground" />
|
|
423
|
+
</div>
|
|
424
|
+
);
|
|
425
|
+
}
|
|
@@ -35,7 +35,9 @@ function StaticTitle({ pathname }: { pathname: string }) {
|
|
|
35
35
|
|
|
36
36
|
function ResolvedTitle() {
|
|
37
37
|
const location = useLocation();
|
|
38
|
-
const libraryMatch = location.pathname.match(
|
|
38
|
+
const libraryMatch = location.pathname.match(
|
|
39
|
+
/^\/(?:library|brand-kits)\/([^/]+)/,
|
|
40
|
+
);
|
|
39
41
|
if (libraryMatch) {
|
|
40
42
|
return <LibraryTitle id={libraryMatch[1]} />;
|
|
41
43
|
}
|
|
@@ -13,6 +13,7 @@ import { IconMenu2 } from "@tabler/icons-react";
|
|
|
13
13
|
import { useState, useEffect } from "react";
|
|
14
14
|
import { useLocation, useNavigate } from "react-router";
|
|
15
15
|
|
|
16
|
+
import { GenerationResults } from "@/components/generation/GenerationResults";
|
|
16
17
|
import { useNavigationState } from "@/hooks/use-navigation-state";
|
|
17
18
|
import { ASSETS_CHAT_STORAGE_KEY } from "@/lib/chat";
|
|
18
19
|
import { cn } from "@/lib/utils";
|
|
@@ -59,6 +60,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
59
60
|
const isPicker = location.pathname === "/library";
|
|
60
61
|
const hideHeader =
|
|
61
62
|
location.pathname === "/library" ||
|
|
63
|
+
location.pathname.startsWith("/library/") ||
|
|
62
64
|
location.pathname === "/extensions" ||
|
|
63
65
|
location.pathname.startsWith("/extensions/");
|
|
64
66
|
const chromeless =
|
|
@@ -138,6 +140,9 @@ export function Layout({ children }: LayoutProps) {
|
|
|
138
140
|
t("chat.suggestionProductVideo"),
|
|
139
141
|
t("chat.suggestionReferenceStyle"),
|
|
140
142
|
]}
|
|
143
|
+
threadFooterSlot={({ threadId }) => (
|
|
144
|
+
<GenerationResults threadId={threadId} />
|
|
145
|
+
)}
|
|
141
146
|
>
|
|
142
147
|
{appFrame}
|
|
143
148
|
</AgentSidebar>
|
|
@@ -16,10 +16,9 @@ import {
|
|
|
16
16
|
IconClipboardList,
|
|
17
17
|
IconDots,
|
|
18
18
|
IconEdit,
|
|
19
|
-
IconLayoutGrid,
|
|
20
19
|
IconLayoutSidebarLeftCollapse,
|
|
21
20
|
IconLayoutSidebarLeftExpand,
|
|
22
|
-
|
|
21
|
+
IconLayoutGrid,
|
|
23
22
|
IconPhotoPlus,
|
|
24
23
|
IconPin,
|
|
25
24
|
IconPlus,
|
|
@@ -48,7 +47,6 @@ import { cn } from "@/lib/utils";
|
|
|
48
47
|
const baseNavItems = [
|
|
49
48
|
{ icon: IconPhotoPlus, labelKey: "navigation.create", href: "/" },
|
|
50
49
|
{ icon: IconLayoutGrid, labelKey: "navigation.library", href: "/library" },
|
|
51
|
-
{ icon: IconPalette, labelKey: "navigation.brandKits", href: "/brand-kits" },
|
|
52
50
|
{ icon: IconSettings, labelKey: "navigation.settings", href: "/settings" },
|
|
53
51
|
];
|
|
54
52
|
|
|
@@ -494,10 +492,10 @@ export function Sidebar() {
|
|
|
494
492
|
const isActive =
|
|
495
493
|
item.href === "/"
|
|
496
494
|
? isCreateRoute
|
|
497
|
-
: item.href === "/
|
|
498
|
-
? location.pathname === "/
|
|
499
|
-
location.pathname.startsWith("/brand-kits/") ||
|
|
495
|
+
: item.href === "/library"
|
|
496
|
+
? location.pathname === "/library" ||
|
|
500
497
|
location.pathname.startsWith("/library/") ||
|
|
498
|
+
location.pathname.startsWith("/brand-kits/") ||
|
|
501
499
|
location.pathname.startsWith("/image/") ||
|
|
502
500
|
location.pathname.startsWith("/asset/")
|
|
503
501
|
: location.pathname.startsWith(item.href);
|
|
@@ -22,6 +22,13 @@ function optionalLibraryTab(params: URLSearchParams) {
|
|
|
22
22
|
: undefined;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function isPickerRequest(params: URLSearchParams) {
|
|
26
|
+
return (
|
|
27
|
+
params.get("__an_picker") === "1" ||
|
|
28
|
+
params.get("__an_mcp_chat_bridge") === "1"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
function navigationFromPath(pathname: string, search = "") {
|
|
26
33
|
const params = new URLSearchParams(search);
|
|
27
34
|
const chat = pathname.match(/^\/chat\/([^/]+)/);
|
|
@@ -31,13 +38,15 @@ function navigationFromPath(pathname: string, search = "") {
|
|
|
31
38
|
threadId: decodePathParam(chat[1]),
|
|
32
39
|
};
|
|
33
40
|
}
|
|
34
|
-
// The "library" view is the
|
|
35
|
-
//
|
|
36
|
-
|
|
41
|
+
// The "library" view is the unified Library workspace. Keep the internal
|
|
42
|
+
// detail key stable for agent/MCP callers that already navigate by brand-kit
|
|
43
|
+
// id, while the URL is now /library/:id.
|
|
44
|
+
const library = pathname.match(/^\/(?:library|brand-kits)\/([^/]+)/);
|
|
37
45
|
if (library) {
|
|
38
46
|
return {
|
|
39
47
|
view: "library",
|
|
40
|
-
|
|
48
|
+
selection: decodePathParam(library[1]),
|
|
49
|
+
libraryId: decodePathParam(library[1]),
|
|
41
50
|
activeTab: optionalLibraryTab(params),
|
|
42
51
|
};
|
|
43
52
|
}
|
|
@@ -50,24 +59,40 @@ function navigationFromPath(pathname: string, search = "") {
|
|
|
50
59
|
view: "create",
|
|
51
60
|
};
|
|
52
61
|
}
|
|
53
|
-
// The "picker" view is the image Library browser (route /library).
|
|
54
62
|
if (pathname === "/library") {
|
|
63
|
+
if (isPickerRequest(params)) {
|
|
64
|
+
return {
|
|
65
|
+
view: "picker",
|
|
66
|
+
mediaType:
|
|
67
|
+
params.get("mediaType") === "video"
|
|
68
|
+
? "video"
|
|
69
|
+
: params.get("mediaType") === "image"
|
|
70
|
+
? "image"
|
|
71
|
+
: undefined,
|
|
72
|
+
libraryId: optionalParam(params, "libraryId"),
|
|
73
|
+
query: optionalParam(params, "q"),
|
|
74
|
+
prompt: optionalParam(params, "prompt"),
|
|
75
|
+
aspectRatio: optionalParam(params, "aspectRatio"),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const queryLibraryId = optionalParam(params, "libraryId");
|
|
79
|
+
if (queryLibraryId) {
|
|
80
|
+
return {
|
|
81
|
+
view: "library",
|
|
82
|
+
selection: queryLibraryId,
|
|
83
|
+
libraryId: queryLibraryId,
|
|
84
|
+
activeTab: optionalLibraryTab(params),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
55
87
|
return {
|
|
56
|
-
view: "
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
? "image"
|
|
62
|
-
: undefined,
|
|
63
|
-
libraryId: optionalParam(params, "libraryId"),
|
|
64
|
-
query: optionalParam(params, "q"),
|
|
65
|
-
prompt: optionalParam(params, "prompt"),
|
|
66
|
-
aspectRatio: optionalParam(params, "aspectRatio"),
|
|
88
|
+
view: "library",
|
|
89
|
+
selection: "all",
|
|
90
|
+
tab: optionalParam(params, "tab"),
|
|
91
|
+
scope: optionalParam(params, "scope"),
|
|
92
|
+
search: optionalParam(params, "q"),
|
|
67
93
|
};
|
|
68
94
|
}
|
|
69
|
-
|
|
70
|
-
if (pathname === "/brand-kits") return { view: "libraries" };
|
|
95
|
+
if (pathname === "/brand-kits") return { view: "library", selection: "all" };
|
|
71
96
|
if (pathname === "/extensions") return { view: "extensions" };
|
|
72
97
|
const extension = pathname.match(/^\/extensions\/([^/]+)/);
|
|
73
98
|
if (extension) return { view: "extensions", extensionId: extension[1] };
|
|
@@ -85,7 +110,7 @@ function pathFromCommand(command: any): string | null {
|
|
|
85
110
|
params.set("tab", command.activeTab);
|
|
86
111
|
}
|
|
87
112
|
const query = params.toString();
|
|
88
|
-
return `/
|
|
113
|
+
return `/library/${command.libraryId}${query ? `?${query}` : ""}`;
|
|
89
114
|
}
|
|
90
115
|
if (
|
|
91
116
|
(command.view === "asset" || command.view === "image") &&
|
|
@@ -100,7 +125,7 @@ function pathFromCommand(command: any): string | null {
|
|
|
100
125
|
) {
|
|
101
126
|
const tab =
|
|
102
127
|
typeof command.activeTab === "string" ? command.activeTab : "runs";
|
|
103
|
-
return `/
|
|
128
|
+
return `/library/${command.libraryId}?tab=${encodeURIComponent(tab)}`;
|
|
104
129
|
}
|
|
105
130
|
if (command.view === "audit") return "/audit";
|
|
106
131
|
if (command.view === "settings") return "/settings";
|
|
@@ -112,6 +137,7 @@ function pathFromCommand(command: any): string | null {
|
|
|
112
137
|
}
|
|
113
138
|
if (command.view === "picker") {
|
|
114
139
|
const params = new URLSearchParams();
|
|
140
|
+
params.set("__an_picker", "1");
|
|
115
141
|
if (command.mediaType === "image" || command.mediaType === "video") {
|
|
116
142
|
params.set("mediaType", command.mediaType);
|
|
117
143
|
}
|
|
@@ -130,7 +156,7 @@ function pathFromCommand(command: any): string | null {
|
|
|
130
156
|
const query = params.toString();
|
|
131
157
|
return query ? `/library?${query}` : "/library";
|
|
132
158
|
}
|
|
133
|
-
if (command.view === "libraries") return "/
|
|
159
|
+
if (command.view === "libraries") return "/library";
|
|
134
160
|
if (command.view === "extensions" && command.extensionId) {
|
|
135
161
|
return `/extensions/${command.extensionId}`;
|
|
136
162
|
}
|