@agent-native/core 0.149.1 → 0.149.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
- package/corpus/templates/analytics/app/global.css +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +37 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
- package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
- package/corpus/templates/clips/desktop/src/app.tsx +274 -137
- package/corpus/templates/clips/desktop/src/styles.css +35 -0
- package/corpus/templates/content/app/global.css +1 -0
- package/corpus/templates/crm/app/global.css +1 -1
- package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/app/global.css +1 -0
- package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
- package/corpus/templates/slides/AGENTS.md +11 -10
- package/corpus/templates/slides/actions/add-deck.ts +2 -0
- package/corpus/templates/slides/actions/add-slide.ts +15 -1
- package/corpus/templates/slides/actions/create-deck.ts +18 -6
- package/corpus/templates/slides/actions/get-deck.ts +29 -1
- package/corpus/templates/slides/actions/patch-deck.ts +122 -9
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +69 -60
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +239 -116
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +3 -3
- package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
- package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
- package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
- package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +4 -27
- package/corpus/templates/slides/app/pages/Index.tsx +240 -89
- package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/slides/shared/api.ts +2 -1
- package/corpus/templates/slides/shared/deck-title.ts +135 -0
- package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
- package/dist/agent/engine/ai-sdk-engine.js +1 -1
- package/dist/agent/production-agent.d.ts +21 -1
- package/dist/agent/production-agent.js +129 -10
- package/dist/agent/tool-approval-store.d.ts +18 -0
- package/dist/agent/tool-approval-store.js +122 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +11 -2
- package/dist/client/AgentPanel.js +31 -6
- package/dist/client/AssistantChat.d.ts +3 -2
- package/dist/client/AssistantChat.js +7 -20
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat-adapter.js +11 -0
- package/dist/client/agent-chat.d.ts +1 -1
- package/dist/client/chat/markdown-renderer.js +14 -2
- package/dist/client/chat/message-components.d.ts +6 -2
- package/dist/client/chat/message-components.js +25 -47
- package/dist/client/chat/tool-call-display.d.ts +3 -5
- package/dist/client/chat/tool-call-display.js +7 -212
- package/dist/client/chat-first/apps-rail.js +3 -3
- package/dist/client/chat-first/browser-pane.js +4 -1
- package/dist/client/chat-first/chat-history.d.ts +3 -1
- package/dist/client/chat-first/chat-history.js +2 -2
- package/dist/client/chat-first/surface-tabs.d.ts +1 -1
- package/dist/client/chat-first/surface-tabs.js +52 -41
- package/dist/client/chat-first/types.d.ts +3 -0
- package/dist/client/chat-first.d.ts +10 -0
- package/dist/client/chat-first.js +37 -2
- package/dist/client/components/ui/message-scroller.js +4 -1
- package/dist/client/i18n.js +3 -1
- package/dist/client/resources/McpConnectionSuggestion.js +9 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/use-chat-models.js +156 -80
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/integrations/webhook-handler.js +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/auth.js +262 -31
- package/dist/server/onboarding-html.js +56 -9
- package/dist/shared/reasoning-effort.d.ts +8 -8
- package/dist/shared/reasoning-effort.js +10 -10
- package/dist/shared/streaming-text-smoothing.js +41 -11
- package/dist/styles/agent-native.css +19 -32
- package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
|
@@ -26,6 +26,7 @@ import { useDesignSystems } from "@/hooks/use-design-systems";
|
|
|
26
26
|
import { useWorkspaceDefaults } from "@/hooks/use-workspace-defaults";
|
|
27
27
|
import { startDeckGeneration } from "@/lib/create-deck-generation";
|
|
28
28
|
import {
|
|
29
|
+
forgetRecentReference,
|
|
29
30
|
readRecentReferences,
|
|
30
31
|
rememberRecentReference,
|
|
31
32
|
type RecentReference,
|
|
@@ -45,11 +46,8 @@ export function FirstDeckOnboardingFlow({
|
|
|
45
46
|
const { session } = useSession();
|
|
46
47
|
const { decks, createDeck, ensureDeckPersisted, deleteDeck, reloadDecks } =
|
|
47
48
|
useDecks();
|
|
48
|
-
const { designSystems
|
|
49
|
-
const {
|
|
50
|
-
referenceDeck: workspaceReferenceDeck,
|
|
51
|
-
designSystem: workspaceDesignSystem,
|
|
52
|
-
} = useWorkspaceDefaults();
|
|
49
|
+
const { designSystems } = useDesignSystems();
|
|
50
|
+
const { designSystem: workspaceDesignSystem } = useWorkspaceDefaults();
|
|
53
51
|
const { submit: agentSubmit } = useAgentGenerating();
|
|
54
52
|
const [step, setStep] = useState<FirstDeckStep>("prompt");
|
|
55
53
|
const [prompt, setPrompt] = useState("");
|
|
@@ -63,19 +61,25 @@ export function FirstDeckOnboardingFlow({
|
|
|
63
61
|
);
|
|
64
62
|
|
|
65
63
|
const initialPrompt = searchParams.get("initialPrompt")?.trim() ?? "";
|
|
66
|
-
const workspaceReferenceDeckId =
|
|
67
|
-
workspaceReferenceDeck && !workspaceReferenceDeck.unavailable
|
|
68
|
-
? workspaceReferenceDeck.id
|
|
69
|
-
: null;
|
|
70
64
|
const workspaceDesignSystemId =
|
|
71
65
|
workspaceDesignSystem && !workspaceDesignSystem.unavailable
|
|
72
66
|
? workspaceDesignSystem.id
|
|
73
67
|
: null;
|
|
68
|
+
const lastUsedDesignSystemId =
|
|
69
|
+
recentReferences.find(
|
|
70
|
+
(reference) =>
|
|
71
|
+
reference.kind === "design-system" &&
|
|
72
|
+
designSystems.some((designSystem) => designSystem.id === reference.id),
|
|
73
|
+
)?.id ?? null;
|
|
74
|
+
const lastUsedReferenceDeckId =
|
|
75
|
+
recentReferences.find(
|
|
76
|
+
(reference) =>
|
|
77
|
+
reference.kind === "deck" &&
|
|
78
|
+
decks.some((deck) => deck.id === reference.id),
|
|
79
|
+
)?.id ?? null;
|
|
74
80
|
const initialDesignSystemId =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
defaultSystem?.id ??
|
|
78
|
-
null;
|
|
81
|
+
lastUsedDesignSystemId ?? workspaceDesignSystemId;
|
|
82
|
+
const initialReferenceDeckId = lastUsedReferenceDeckId;
|
|
79
83
|
|
|
80
84
|
useEffect(() => {
|
|
81
85
|
const result = readRecentReferences();
|
|
@@ -103,6 +107,10 @@ export function FirstDeckOnboardingFlow({
|
|
|
103
107
|
},
|
|
104
108
|
[],
|
|
105
109
|
);
|
|
110
|
+
const forgetReference = useCallback((kind: RecentReference["kind"]) => {
|
|
111
|
+
const result = forgetRecentReference(kind);
|
|
112
|
+
if (result.readable) setRecentReferences(result.items);
|
|
113
|
+
}, []);
|
|
106
114
|
|
|
107
115
|
const handlePromptSubmit = useCallback(
|
|
108
116
|
async (text: string, files: File[]) => {
|
|
@@ -137,7 +145,7 @@ export function FirstDeckOnboardingFlow({
|
|
|
137
145
|
files,
|
|
138
146
|
referenceSelection: selection,
|
|
139
147
|
selectedDesignSystemId: initialDesignSystemId,
|
|
140
|
-
selectedReferenceDeckId:
|
|
148
|
+
selectedReferenceDeckId: initialReferenceDeckId,
|
|
141
149
|
designSystems,
|
|
142
150
|
createDeck,
|
|
143
151
|
ensureDeckPersisted,
|
|
@@ -185,24 +193,32 @@ export function FirstDeckOnboardingFlow({
|
|
|
185
193
|
prompt,
|
|
186
194
|
session,
|
|
187
195
|
t,
|
|
188
|
-
|
|
196
|
+
initialReferenceDeckId,
|
|
189
197
|
],
|
|
190
198
|
);
|
|
191
199
|
|
|
192
200
|
const handleReferenceSelect = useCallback(
|
|
193
201
|
async (selection: NewDeckReferenceSelection) => {
|
|
194
|
-
if (selection.designSystemId) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
if (selection.designSystemId !== undefined) {
|
|
203
|
+
if (selection.designSystemId) {
|
|
204
|
+
rememberReference({
|
|
205
|
+
id: selection.designSystemId,
|
|
206
|
+
kind: "design-system",
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
forgetReference("design-system");
|
|
210
|
+
}
|
|
199
211
|
}
|
|
200
|
-
if (selection.referenceDeckId) {
|
|
201
|
-
|
|
212
|
+
if (selection.referenceDeckId !== undefined) {
|
|
213
|
+
if (selection.referenceDeckId) {
|
|
214
|
+
rememberReference({ id: selection.referenceDeckId, kind: "deck" });
|
|
215
|
+
} else {
|
|
216
|
+
forgetReference("deck");
|
|
217
|
+
}
|
|
202
218
|
}
|
|
203
219
|
await startGeneration(promptFiles, selection);
|
|
204
220
|
},
|
|
205
|
-
[promptFiles, rememberReference, startGeneration],
|
|
221
|
+
[forgetReference, promptFiles, rememberReference, startGeneration],
|
|
206
222
|
);
|
|
207
223
|
|
|
208
224
|
const handleReferenceImport = useCallback(
|
|
@@ -296,7 +312,6 @@ export function FirstDeckOnboardingFlow({
|
|
|
296
312
|
setPromptFiles((current) => [...current, ...generationFiles]);
|
|
297
313
|
if (importedReference) {
|
|
298
314
|
await reloadDecks();
|
|
299
|
-
rememberReference({ id: importedReference.id, kind: "deck" });
|
|
300
315
|
}
|
|
301
316
|
return importedReference;
|
|
302
317
|
} catch (error) {
|
|
@@ -311,15 +326,7 @@ export function FirstDeckOnboardingFlow({
|
|
|
311
326
|
setReferenceImporting(false);
|
|
312
327
|
}
|
|
313
328
|
},
|
|
314
|
-
[
|
|
315
|
-
callAction,
|
|
316
|
-
createDeck,
|
|
317
|
-
deleteDeck,
|
|
318
|
-
ensureDeckPersisted,
|
|
319
|
-
reloadDecks,
|
|
320
|
-
rememberReference,
|
|
321
|
-
t,
|
|
322
|
-
],
|
|
329
|
+
[callAction, createDeck, deleteDeck, ensureDeckPersisted, reloadDecks, t],
|
|
323
330
|
);
|
|
324
331
|
|
|
325
332
|
const handleReferenceSourceImport = useCallback(
|
|
@@ -357,7 +364,6 @@ export function FirstDeckOnboardingFlow({
|
|
|
357
364
|
source: "google-slides",
|
|
358
365
|
};
|
|
359
366
|
await reloadDecks();
|
|
360
|
-
rememberReference({ id: importedReference.id, kind: "deck" });
|
|
361
367
|
return importedReference;
|
|
362
368
|
} catch (error) {
|
|
363
369
|
toast.error(t("editorToolbar.uploadFailed"), {
|
|
@@ -371,15 +377,17 @@ export function FirstDeckOnboardingFlow({
|
|
|
371
377
|
setReferenceImporting(false);
|
|
372
378
|
}
|
|
373
379
|
},
|
|
374
|
-
[callAction, reloadDecks,
|
|
380
|
+
[callAction, reloadDecks, t],
|
|
375
381
|
);
|
|
376
382
|
|
|
377
383
|
const handleReferenceSkip = useCallback(() => {
|
|
384
|
+
forgetReference("design-system");
|
|
385
|
+
forgetReference("deck");
|
|
378
386
|
void startGeneration(promptFiles, {
|
|
379
387
|
designSystemId: null,
|
|
380
388
|
referenceDeckId: null,
|
|
381
389
|
});
|
|
382
|
-
}, [promptFiles, startGeneration]);
|
|
390
|
+
}, [forgetReference, promptFiles, startGeneration]);
|
|
383
391
|
|
|
384
392
|
if (step === "references") {
|
|
385
393
|
return (
|
|
@@ -388,8 +396,7 @@ export function FirstDeckOnboardingFlow({
|
|
|
388
396
|
decks={decks}
|
|
389
397
|
designSystems={designSystems}
|
|
390
398
|
defaultDesignSystemId={initialDesignSystemId}
|
|
391
|
-
defaultReferenceDeckId={
|
|
392
|
-
recentReferences={recentReferences}
|
|
399
|
+
defaultReferenceDeckId={initialReferenceDeckId}
|
|
393
400
|
onSelect={handleReferenceSelect}
|
|
394
401
|
onImport={handleReferenceImport}
|
|
395
402
|
onImportSource={handleReferenceSourceImport}
|
|
@@ -404,8 +411,6 @@ export function FirstDeckOnboardingFlow({
|
|
|
404
411
|
chooseDeckLabel={t("home.referenceDeckPlaceholder")}
|
|
405
412
|
importingLabel={t("raw.uploading")}
|
|
406
413
|
skipLabel={t("home.referenceDeckNone")}
|
|
407
|
-
starredLabel={t("home.referenceDeckStarredGroup")}
|
|
408
|
-
otherDecksLabel={t("home.referenceDeckOtherGroup")}
|
|
409
414
|
searchDecksLabel={t("root.searchDecks")}
|
|
410
415
|
promptSummary={prompt}
|
|
411
416
|
/>
|
|
@@ -19,7 +19,7 @@ import type { AspectRatio } from "@/lib/aspect-ratios";
|
|
|
19
19
|
import {
|
|
20
20
|
findLegacyAnimationContainer,
|
|
21
21
|
getElementPath,
|
|
22
|
-
|
|
22
|
+
resolveSlideAnimationTargets,
|
|
23
23
|
} from "@/lib/slide-animation-elements";
|
|
24
24
|
|
|
25
25
|
import type { DesignSystemData } from "../../../shared/api";
|
|
@@ -40,7 +40,17 @@ interface PresentationViewProps {
|
|
|
40
40
|
* Uses slide.animations if defined, falls back to splitByParagraph auto-detection.
|
|
41
41
|
*/
|
|
42
42
|
function getAnimationSteps(slide: Slide): SlideAnimation[] | null {
|
|
43
|
-
if (slide.animations && slide.animations.length > 0)
|
|
43
|
+
if (slide.animations && slide.animations.length > 0) {
|
|
44
|
+
const doc = new DOMParser().parseFromString(slide.content, "text/html");
|
|
45
|
+
const root = doc.querySelector(".fmd-slide");
|
|
46
|
+
// Explicit metadata is authoritative only when every target still points
|
|
47
|
+
// at a unique element in the final HTML. Otherwise disable the reveal
|
|
48
|
+
// layer for this slide instead of counting invisible phantom steps while
|
|
49
|
+
// leaving the rest of the content visible.
|
|
50
|
+
return root && resolveSlideAnimationTargets(root, slide.animations)
|
|
51
|
+
? slide.animations
|
|
52
|
+
: null;
|
|
53
|
+
}
|
|
44
54
|
// Legacy splitByParagraph: auto-detect and create steps
|
|
45
55
|
if (slide.splitByParagraph) {
|
|
46
56
|
const doc = new DOMParser().parseFromString(slide.content, "text/html");
|
|
@@ -111,14 +121,16 @@ function annotateStepsForPresentation(
|
|
|
111
121
|
const root = doc.querySelector(".fmd-slide");
|
|
112
122
|
if (!root) return html;
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
const resolvedSteps = resolveSlideAnimationTargets(root, steps);
|
|
125
|
+
if (!resolvedSteps) return html;
|
|
126
|
+
|
|
127
|
+
// Annotate each resolved step element with data-pstep.
|
|
128
|
+
resolvedSteps.forEach(({ element }, stepIdx) => {
|
|
129
|
+
element.setAttribute("data-pstep", String(stepIdx));
|
|
118
130
|
});
|
|
119
131
|
|
|
120
|
-
const styleLines =
|
|
121
|
-
.map((
|
|
132
|
+
const styleLines = resolvedSteps
|
|
133
|
+
.map(({ target }, stepIdx) => {
|
|
122
134
|
if (stepIdx >= currentStep) {
|
|
123
135
|
return `[data-pstep="${stepIdx}"] { opacity: 0; pointer-events: none; }`;
|
|
124
136
|
} else if (stepIdx < currentStep - 1) {
|
|
@@ -126,7 +138,7 @@ function annotateStepsForPresentation(
|
|
|
126
138
|
return `[data-pstep="${stepIdx}"] { opacity: 1; pointer-events: auto; animation: elem-appear 1ms both; }`;
|
|
127
139
|
} else {
|
|
128
140
|
// Newly revealed — animate with its type
|
|
129
|
-
return `[data-pstep="${stepIdx}"] { opacity: 1; pointer-events: auto; ${getElemAnimCss(
|
|
141
|
+
return `[data-pstep="${stepIdx}"] { opacity: 1; pointer-events: auto; ${getElemAnimCss(target.type)} }`;
|
|
130
142
|
}
|
|
131
143
|
})
|
|
132
144
|
.join("\n");
|
|
@@ -219,6 +231,10 @@ export default function PresentationView({
|
|
|
219
231
|
const [cursorVisible, setCursorVisible] = useState(true);
|
|
220
232
|
const [needsFullscreenGesture, setNeedsFullscreenGesture] = useState(false);
|
|
221
233
|
const enteredFullscreenRef = useRef(false);
|
|
234
|
+
const transitionTimerRef = useRef<number | null>(null);
|
|
235
|
+
const queuedNavigationRef = useRef<"next" | "prev" | null>(null);
|
|
236
|
+
const goNextRef = useRef<() => void>(() => {});
|
|
237
|
+
const goPrevRef = useRef<() => void>(() => {});
|
|
222
238
|
const navigate = useNavigate();
|
|
223
239
|
|
|
224
240
|
const isShared = deckId.startsWith("__shared__/");
|
|
@@ -230,9 +246,23 @@ export default function PresentationView({
|
|
|
230
246
|
);
|
|
231
247
|
}, [clampIndex, safeSlides.length]);
|
|
232
248
|
|
|
249
|
+
const clearTransitionTimer = useCallback(() => {
|
|
250
|
+
if (transitionTimerRef.current !== null) {
|
|
251
|
+
window.clearTimeout(transitionTimerRef.current);
|
|
252
|
+
transitionTimerRef.current = null;
|
|
253
|
+
}
|
|
254
|
+
}, []);
|
|
255
|
+
|
|
256
|
+
useEffect(() => clearTransitionTimer, [clearTransitionTimer]);
|
|
257
|
+
|
|
233
258
|
useEffect(() => {
|
|
259
|
+
clearTransitionTimer();
|
|
260
|
+
queuedNavigationRef.current = null;
|
|
234
261
|
setCurrentIndex(clampIndex(startIndex));
|
|
235
|
-
|
|
262
|
+
setCurrentStep(0);
|
|
263
|
+
setPrevIndex(null);
|
|
264
|
+
setAnimating(false);
|
|
265
|
+
}, [clearTransitionTimer, clampIndex, startIndex]);
|
|
236
266
|
|
|
237
267
|
const currentSlide = safeSlides[currentIndex];
|
|
238
268
|
const animSteps = currentSlide ? getAnimationSteps(currentSlide) : null;
|
|
@@ -247,7 +277,11 @@ export default function PresentationView({
|
|
|
247
277
|
const initialStep =
|
|
248
278
|
dir === "prev" ? (incomingSteps ? incomingSteps.length : 0) : 0;
|
|
249
279
|
|
|
280
|
+
clearTransitionTimer();
|
|
281
|
+
queuedNavigationRef.current = null;
|
|
250
282
|
if (isInstant(t)) {
|
|
283
|
+
setPrevIndex(null);
|
|
284
|
+
setAnimating(false);
|
|
251
285
|
setCurrentIndex(newIndex);
|
|
252
286
|
setCurrentStep(initialStep);
|
|
253
287
|
return;
|
|
@@ -259,16 +293,28 @@ export default function PresentationView({
|
|
|
259
293
|
setCurrentIndex(newIndex);
|
|
260
294
|
setCurrentStep(initialStep);
|
|
261
295
|
|
|
262
|
-
setTimeout(() => {
|
|
296
|
+
transitionTimerRef.current = window.setTimeout(() => {
|
|
297
|
+
transitionTimerRef.current = null;
|
|
263
298
|
setPrevIndex(null);
|
|
264
299
|
setAnimating(false);
|
|
300
|
+
const queued = queuedNavigationRef.current;
|
|
301
|
+
queuedNavigationRef.current = null;
|
|
302
|
+
if (queued) {
|
|
303
|
+
window.setTimeout(() => {
|
|
304
|
+
if (queued === "next") goNextRef.current();
|
|
305
|
+
else goPrevRef.current();
|
|
306
|
+
}, 0);
|
|
307
|
+
}
|
|
265
308
|
}, 400);
|
|
266
309
|
},
|
|
267
|
-
[currentIndex, safeSlides],
|
|
310
|
+
[clearTransitionTimer, currentIndex, safeSlides],
|
|
268
311
|
);
|
|
269
312
|
|
|
270
313
|
const goNext = useCallback(() => {
|
|
271
|
-
if (animating)
|
|
314
|
+
if (animating) {
|
|
315
|
+
queuedNavigationRef.current = "next";
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
272
318
|
// Reveal next paragraph step if enabled
|
|
273
319
|
if (maxSteps > 0 && currentStep < maxSteps /* i18n-ignore */) {
|
|
274
320
|
setCurrentStep((prev) => prev + 1);
|
|
@@ -286,7 +332,10 @@ export default function PresentationView({
|
|
|
286
332
|
]);
|
|
287
333
|
|
|
288
334
|
const goPrev = useCallback(() => {
|
|
289
|
-
if (animating)
|
|
335
|
+
if (animating) {
|
|
336
|
+
queuedNavigationRef.current = "prev";
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
290
339
|
if (currentIndex <= 0) return;
|
|
291
340
|
startTransition(currentIndex - 1, "prev");
|
|
292
341
|
}, [animating, currentIndex, startTransition]);
|
|
@@ -307,8 +356,6 @@ export default function PresentationView({
|
|
|
307
356
|
// commands and mirrors whatever we echo back — so build steps stay
|
|
308
357
|
// authoritative here.
|
|
309
358
|
const channelRef = useRef<BroadcastChannel | null>(null);
|
|
310
|
-
const goNextRef = useRef(goNext);
|
|
311
|
-
const goPrevRef = useRef(goPrev);
|
|
312
359
|
goNextRef.current = goNext;
|
|
313
360
|
goPrevRef.current = goPrev;
|
|
314
361
|
|
|
@@ -21,6 +21,11 @@ export function useGeneratingSlidePreview({
|
|
|
21
21
|
const [content, setContent] = useState<string | null>(null);
|
|
22
22
|
const activeCallIdRef = useRef<string | null>(null);
|
|
23
23
|
const previousSlideCountRef = useRef(slideCount);
|
|
24
|
+
// Latest parsed value awaiting a flush; setContent fires at most once per frame
|
|
25
|
+
// and always reads this ref, so a burst of deltas within one frame collapses
|
|
26
|
+
// to a single render carrying the newest value instead of the stale one.
|
|
27
|
+
const pendingContentRef = useRef<string | null>(null);
|
|
28
|
+
const rafIdRef = useRef<number | null>(null);
|
|
24
29
|
|
|
25
30
|
useEffect(() => {
|
|
26
31
|
if (slideCount !== previousSlideCountRef.current) {
|
|
@@ -37,6 +42,18 @@ export function useGeneratingSlidePreview({
|
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
const flush = () => {
|
|
46
|
+
rafIdRef.current = null;
|
|
47
|
+
setContent(pendingContentRef.current);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const scheduleFlush = (value: string | null) => {
|
|
51
|
+
pendingContentRef.current = value;
|
|
52
|
+
if (rafIdRef.current === null) {
|
|
53
|
+
rafIdRef.current = requestAnimationFrame(flush);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
40
57
|
const handleToolInput = (event: Event) => {
|
|
41
58
|
const detail = (event as CustomEvent<ToolInputEventDetail>).detail;
|
|
42
59
|
if (detail?.tool !== "add-slide") return;
|
|
@@ -47,7 +64,7 @@ export function useGeneratingSlidePreview({
|
|
|
47
64
|
if (detail.phase === "start") {
|
|
48
65
|
if (detail.id && activeCallIdRef.current === detail.id) return;
|
|
49
66
|
activeCallIdRef.current = detail.id ?? null;
|
|
50
|
-
|
|
67
|
+
scheduleFlush(null);
|
|
51
68
|
return;
|
|
52
69
|
}
|
|
53
70
|
|
|
@@ -60,13 +77,18 @@ export function useGeneratingSlidePreview({
|
|
|
60
77
|
}
|
|
61
78
|
if (detail.id) activeCallIdRef.current = detail.id;
|
|
62
79
|
if (parsed.content !== undefined) {
|
|
63
|
-
|
|
80
|
+
scheduleFlush(parsed.content || null);
|
|
64
81
|
}
|
|
65
82
|
};
|
|
66
83
|
|
|
67
84
|
window.addEventListener("agent-native:tool-input", handleToolInput);
|
|
68
|
-
return () =>
|
|
85
|
+
return () => {
|
|
69
86
|
window.removeEventListener("agent-native:tool-input", handleToolInput);
|
|
87
|
+
if (rafIdRef.current !== null) {
|
|
88
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
89
|
+
rafIdRef.current = null;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
70
92
|
}, [deckId, generating]);
|
|
71
93
|
|
|
72
94
|
return content;
|
|
@@ -689,6 +689,7 @@ const messages = {
|
|
|
689
689
|
"Keep the default design system and reference deck unless you remove them. Search for another deck, import a file, or continue when you're ready.",
|
|
690
690
|
chooseAnotherDeck: "Choose another deck",
|
|
691
691
|
noMatchingDecks: "No matching decks found.",
|
|
692
|
+
googleSlidesImportLabel: "Slides",
|
|
692
693
|
googleSlidesReferenceTitle: "Google Slides",
|
|
693
694
|
googleSlidesReferenceConnect:
|
|
694
695
|
"Connect Google Drive to import a Slides deck.",
|
|
@@ -113,7 +113,7 @@ function describeUploadedFilesForAgent(
|
|
|
113
113
|
"",
|
|
114
114
|
"File handling rules:",
|
|
115
115
|
importedSourceDeck
|
|
116
|
-
? "- The imported source deck is the canonical source. Preserve its slide count, order, IDs, factual copy, notes, imagery, charts, tables, diagrams, and freeform objects while improving styling.
|
|
116
|
+
? "- The imported source deck is the canonical source. Preserve its slide count, order, IDs, factual copy, notes, imagery, charts, tables, diagrams, and freeform objects while improving styling. For a deck-wide restyle, use one patch-deck call with requireAllSourceSlides=true; use update-slide only for a targeted one-slide edit. Do not rebuild it with add-slide."
|
|
117
117
|
: `- PPTX files: call \`import-pptx --filePath \"<path>\" --deckId ${deckId}\` before adding or editing slides.`,
|
|
118
118
|
importedSourceDeck
|
|
119
119
|
? "- For a PDF source, keep the original full-page image in every slide and add restrained design-system chrome around it without obscuring source content. Never OCR-reconstruct a source-faithful page from extracted text."
|
|
@@ -324,19 +324,20 @@ export async function startDeckGeneration({
|
|
|
324
324
|
"Source-preserving improvement mode:",
|
|
325
325
|
`- The target deck already contains ${importedSourceDeck.slideCount} imported source slides. Treat those slides as the user's complete source, not as inspiration for a new deck.`,
|
|
326
326
|
"- Keep the exact source slide count, order, IDs, factual meaning, notes, images, charts, tables, diagrams, and freeform objects unless the user explicitly asks to change one of them.",
|
|
327
|
-
"- Read `get-deck` before editing, load the linked design system with `get-design-system`, then
|
|
327
|
+
"- Read `get-deck` once before editing to obtain every existing slide ID and source HTML, load the linked design system with `get-design-system`, then make a deck-wide restyle with one `patch-deck` call using `requireAllSourceSlides: true` and one `patch-slide` operation with `fields.content` for every source slide ID. Do not split a full-deck restyle into arbitrary batches or fall back to one-by-one `update-slide` calls; use `update-slide` only for a targeted one-slide edit. Keep every original `<img>` source and enough original factual copy for each slide; for PDF slides, use restrained design-system chrome around the page without obscuring it.",
|
|
328
328
|
"- Do not call `add-slide`, delete slides, reorder slides, or replace source images with generic cards. Do not claim success until `get-deck` verifies the same slide IDs and count after the edits.",
|
|
329
|
+
'- After the patch succeeds, verify with `get-deck` using `compact: "true"` so only slide IDs, count, and previews are returned. Do not report an initial or partial pass, and do not leave any source slides for a later run.',
|
|
329
330
|
"- If `get-deck` reports partial source fidelity or skipped images, stop and report the exact warning instead of claiming a reliable restyle.",
|
|
330
331
|
].join("\n")
|
|
331
332
|
: "";
|
|
332
333
|
const sourceModeInstructions = importedSourceDeck
|
|
333
334
|
? [
|
|
334
335
|
"The request is an in-place visual improvement of an imported source deck. Make a coherent style pass across every existing slide while preserving all source content and media.",
|
|
335
|
-
"Do not use the new-deck add-slide workflow for this source-preserving request.",
|
|
336
|
+
"Do not use the new-deck add-slide workflow for this source-preserving request. Finish every source slide in this run; if patch-deck rejects incomplete coverage, continue with the returned missing IDs instead of reporting success with a partial deck.",
|
|
336
337
|
]
|
|
337
338
|
: [
|
|
338
339
|
"Start a `manage-progress` run so progress appears in the app header. Add the first slide as soon as it is ready, then continue one slide at a time so the editor visibly fills in.",
|
|
339
|
-
`After reading any requested or imported source material, but before adding the first slide, choose a concise, specific deck title from the user's request and source material. Call \`patch-deck\` with \`deckId: \"${deckId}\"\` and \`operations: [{ \"op\": \"patch-deck-fields\", \"fields\": { \"title\": \"<generated title>\" } }]\`. Include only \`title\` in \`fields\`; omit all other optional fields. Never leave a generated deck named \"Untitled Deck\" or another placeholder.`,
|
|
340
|
+
`After reading any requested or imported source material, but before adding the first slide, choose a concise, specific deck title from the user's request and source material. Never use the deck id, run id, file id, or another opaque alphanumeric token as the title. Call \`patch-deck\` with \`deckId: \"${deckId}\"\` and \`operations: [{ \"op\": \"patch-deck-fields\", \"fields\": { \"title\": \"<generated title>\" } }]\`. Include only \`title\` in \`fields\`; omit all other optional fields. Never leave a generated deck named \"Untitled Deck\" or another placeholder.`,
|
|
340
341
|
"If the user asks for a standalone visual, diagram, hero, one-pager, poster, or a couple of visuals, create only the requested one/few polished visual slides. Do not pad the result into a full presentation.",
|
|
341
342
|
"If the request is for a presentation or deck and does not explicitly ask for one slide, infer a coherent multi-slide outline from the scope and keep adding slides until that outline is complete. Do not stop after the first slide just because the prompt has few explicit instructions.",
|
|
342
343
|
`Add slides ONE AT A TIME using the \`add-slide\` action with --deckId=${deckId}. Wait for each \`add-slide\` result before calling it again; do not batch or parallelize slide writes.`,
|
|
@@ -48,7 +48,10 @@ export function readRecentReferences(
|
|
|
48
48
|
const parsed: unknown = JSON.parse(raw);
|
|
49
49
|
if (!Array.isArray(parsed)) return { items: [], readable: false };
|
|
50
50
|
return {
|
|
51
|
-
items: parsed
|
|
51
|
+
items: parsed
|
|
52
|
+
.filter(isRecentReference)
|
|
53
|
+
.sort((first, second) => second.lastUsedAt - first.lastUsedAt)
|
|
54
|
+
.slice(0, MAX_RECENT_REFERENCES),
|
|
52
55
|
readable: true,
|
|
53
56
|
};
|
|
54
57
|
} catch {
|
|
@@ -56,6 +59,25 @@ export function readRecentReferences(
|
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
export function forgetRecentReference(
|
|
63
|
+
kind: RecentReferenceKind,
|
|
64
|
+
storage:
|
|
65
|
+
| Pick<Storage, "getItem" | "setItem">
|
|
66
|
+
| null
|
|
67
|
+
| undefined = typeof window === "undefined" ? null : window.localStorage,
|
|
68
|
+
): RecentReferencesResult {
|
|
69
|
+
const current = readRecentReferences(storage);
|
|
70
|
+
if (!storage || !current.readable) return current;
|
|
71
|
+
|
|
72
|
+
const next = current.items.filter((item) => item.kind !== kind);
|
|
73
|
+
try {
|
|
74
|
+
storage.setItem(RECENT_REFERENCES_STORAGE_KEY, JSON.stringify(next));
|
|
75
|
+
return { items: next, readable: true };
|
|
76
|
+
} catch {
|
|
77
|
+
return { items: current.items, readable: false };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
export function rememberRecentReference(
|
|
60
82
|
reference: Omit<RecentReference, "lastUsedAt">,
|
|
61
83
|
storage:
|
|
@@ -9,6 +9,14 @@ export interface AnimationTarget {
|
|
|
9
9
|
elementPath?: number[];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export interface ResolvedAnimationTarget<
|
|
13
|
+
T extends AnimationTarget = AnimationTarget,
|
|
14
|
+
> {
|
|
15
|
+
target: T;
|
|
16
|
+
element: Element;
|
|
17
|
+
key: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
const INLINE_TAGS = new Set([
|
|
13
21
|
"a",
|
|
14
22
|
"abbr",
|
|
@@ -192,15 +200,45 @@ export function resolveSlideAnimationElement(
|
|
|
192
200
|
root: Element,
|
|
193
201
|
target: AnimationTarget,
|
|
194
202
|
): Element | null {
|
|
195
|
-
if (Array.isArray(target.elementPath)
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
if (Array.isArray(target.elementPath)) {
|
|
204
|
+
// A supplied path is the identity of the target. Falling back to a
|
|
205
|
+
// legacy index after it goes stale can animate a different element while
|
|
206
|
+
// leaving the intended element visible.
|
|
207
|
+
return target.elementPath.length > 0
|
|
208
|
+
? resolveElementPath(root, target.elementPath)
|
|
209
|
+
: null;
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
const legacyContainer = findLegacyAnimationContainer(root);
|
|
201
213
|
return legacyContainer?.children.item(target.elementIndex) ?? null;
|
|
202
214
|
}
|
|
203
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Resolve an ordered animation list as one validated unit. A null result
|
|
218
|
+
* means the list cannot be rendered faithfully - a missing or duplicate
|
|
219
|
+
* target must not become a phantom click step in the presentation player.
|
|
220
|
+
*/
|
|
221
|
+
export function resolveSlideAnimationTargets<T extends AnimationTarget>(
|
|
222
|
+
root: Element,
|
|
223
|
+
targets: readonly T[],
|
|
224
|
+
): ResolvedAnimationTarget<T>[] | null {
|
|
225
|
+
const seen = new Set<string>();
|
|
226
|
+
const resolved: ResolvedAnimationTarget<T>[] = [];
|
|
227
|
+
|
|
228
|
+
for (const target of targets) {
|
|
229
|
+
const element = resolveSlideAnimationElement(root, target);
|
|
230
|
+
if (!element) return null;
|
|
231
|
+
const path = getElementPath(root, element);
|
|
232
|
+
if (!path) return null;
|
|
233
|
+
const key = animationElementKey(path);
|
|
234
|
+
if (seen.has(key)) return null;
|
|
235
|
+
seen.add(key);
|
|
236
|
+
resolved.push({ target, element, key });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return resolved;
|
|
240
|
+
}
|
|
241
|
+
|
|
204
242
|
export function getSlideAnimationTargetKey(
|
|
205
243
|
html: string,
|
|
206
244
|
target: AnimationTarget,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
emailToColor,
|
|
6
6
|
emailToName,
|
|
7
7
|
} from "@agent-native/core/client/collab";
|
|
8
|
-
import { useSession
|
|
8
|
+
import { useSession } from "@agent-native/core/client/hooks";
|
|
9
9
|
import { useT } from "@agent-native/core/client/i18n";
|
|
10
10
|
import { useOrg } from "@agent-native/core/client/org";
|
|
11
11
|
import {
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
useSlideComments,
|
|
55
55
|
type CommentThread,
|
|
56
56
|
} from "@/hooks/use-slide-comments";
|
|
57
|
-
import { getAspectRatioDims
|
|
57
|
+
import { getAspectRatioDims } from "@/lib/aspect-ratios";
|
|
58
58
|
import {
|
|
59
59
|
deckAccessCheckKey,
|
|
60
60
|
shouldShowDeckEditorSkeleton,
|
|
@@ -238,9 +238,6 @@ export default function DeckEditor() {
|
|
|
238
238
|
const [pendingComment, setPendingComment] = useState<{
|
|
239
239
|
quotedText: string;
|
|
240
240
|
} | null>(null);
|
|
241
|
-
const imageGenButtonRef = useRef<HTMLButtonElement>(null);
|
|
242
|
-
const assetsButtonRef = useRef<HTMLButtonElement>(null);
|
|
243
|
-
|
|
244
241
|
// Track which image src to replace
|
|
245
242
|
const [replaceImageSrc, setReplaceImageSrc] = useState<string | null>(null);
|
|
246
243
|
|
|
@@ -1035,16 +1032,9 @@ export default function DeckEditor() {
|
|
|
1035
1032
|
setReplaceImageSrc(null);
|
|
1036
1033
|
setAssetLibraryOpen(true);
|
|
1037
1034
|
}}
|
|
1038
|
-
imageGenButtonRef={imageGenButtonRef}
|
|
1039
|
-
assetsButtonRef={assetsButtonRef}
|
|
1040
|
-
historyOpen={historyOpen}
|
|
1041
1035
|
onShowHistory={() => setHistoryOpen(!historyOpen)}
|
|
1042
1036
|
historyButtonRef={historyButtonRef}
|
|
1043
1037
|
currentSlide={currentSlide}
|
|
1044
|
-
onUpdateSlide={(updates, slideIdOverride) => {
|
|
1045
|
-
const targetId = slideIdOverride ?? currentSlide?.id;
|
|
1046
|
-
if (targetId) updateSlide(id, targetId, updates);
|
|
1047
|
-
}}
|
|
1048
1038
|
activeUsers={slideActiveUsers.filter((u) => u.email !== session?.email)}
|
|
1049
1039
|
agentPresent={agentPresent}
|
|
1050
1040
|
agentActive={agentActive}
|
|
@@ -1118,19 +1108,6 @@ export default function DeckEditor() {
|
|
|
1118
1108
|
}
|
|
1119
1109
|
return exportDeckToGoogleSlides(deck.title, slides, deck.aspectRatio);
|
|
1120
1110
|
}}
|
|
1121
|
-
aspectRatio={deck.aspectRatio}
|
|
1122
|
-
onSetAspectRatio={(ratio: AspectRatio) => {
|
|
1123
|
-
const previous = deck.aspectRatio;
|
|
1124
|
-
// Optimistic UI: update local cache immediately so canvas resizes.
|
|
1125
|
-
updateDeck(id, { aspectRatio: ratio });
|
|
1126
|
-
callAction("update-deck-aspect-ratio", {
|
|
1127
|
-
deckId: id,
|
|
1128
|
-
aspectRatio: ratio,
|
|
1129
|
-
}).catch((err) => {
|
|
1130
|
-
console.error("Failed to set aspect ratio:", err);
|
|
1131
|
-
updateDeck(id, { aspectRatio: previous });
|
|
1132
|
-
});
|
|
1133
|
-
}}
|
|
1134
1111
|
currentSlideId={currentSlide?.id}
|
|
1135
1112
|
addSlideGenerating={addSlideGenerating}
|
|
1136
1113
|
onAddSlideGeneratingChange={setAddSlideGenerating}
|
|
@@ -1376,7 +1353,7 @@ export default function DeckEditor() {
|
|
|
1376
1353
|
<ImageGenPanel
|
|
1377
1354
|
open={imageGenOpen}
|
|
1378
1355
|
onOpenChange={setImageGenOpen}
|
|
1379
|
-
anchorRef={
|
|
1356
|
+
anchorRef={historyButtonRef}
|
|
1380
1357
|
referenceImageUrls={imageStyleReferenceUrls}
|
|
1381
1358
|
slideContext={
|
|
1382
1359
|
currentSlide
|
|
@@ -1394,7 +1371,7 @@ export default function DeckEditor() {
|
|
|
1394
1371
|
<AssetLibraryPanel
|
|
1395
1372
|
open={assetLibraryOpen}
|
|
1396
1373
|
onOpenChange={setAssetLibraryOpen}
|
|
1397
|
-
anchorRef={
|
|
1374
|
+
anchorRef={historyButtonRef}
|
|
1398
1375
|
onSelectAsset={
|
|
1399
1376
|
replaceImageSrc
|
|
1400
1377
|
? (newUrl) => {
|