@agent-native/core 0.149.0 → 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 +139 -10
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
- package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +10 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +74 -63
- 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 +29 -7
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
- 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/export-pdf-client.ts +84 -25
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
- 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 +16 -27
- package/corpus/templates/slides/app/pages/Index.tsx +256 -98
- package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
- 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/handlers/import/html-converter.ts +115 -22
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
- package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
- 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/progress/RunsTray.js +14 -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/integrations/webhook-handler.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- 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
|
@@ -3,9 +3,12 @@ import { PromptComposer } from "@agent-native/core/client/composer";
|
|
|
3
3
|
import { ensureEmbedAuthFetchInterceptor } from "@agent-native/core/client/host";
|
|
4
4
|
import { useT } from "@agent-native/core/client/i18n";
|
|
5
5
|
import {
|
|
6
|
+
IconArrowLeft,
|
|
6
7
|
IconBrandGoogle,
|
|
7
8
|
IconFileTypePdf,
|
|
9
|
+
IconLoader2,
|
|
8
10
|
IconPresentation,
|
|
11
|
+
IconUpload,
|
|
9
12
|
} from "@tabler/icons-react";
|
|
10
13
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
11
14
|
import { createPortal } from "react-dom";
|
|
@@ -86,7 +89,7 @@ interface PromptPopoverProps {
|
|
|
86
89
|
placeholder?: string;
|
|
87
90
|
onSkip?: () => void;
|
|
88
91
|
skipLabel?: string;
|
|
89
|
-
onSubmit: (prompt: string, files: UploadedFile[]) => void
|
|
92
|
+
onSubmit: (prompt: string, files: UploadedFile[]) => void | Promise<void>;
|
|
90
93
|
loading?: boolean;
|
|
91
94
|
anchorRef?: React.RefObject<HTMLElement | null>;
|
|
92
95
|
centered?: boolean;
|
|
@@ -125,10 +128,14 @@ export default function PromptPopover({
|
|
|
125
128
|
}: PromptPopoverProps) {
|
|
126
129
|
const t = useT();
|
|
127
130
|
const [uploading, setUploading] = useState(false);
|
|
131
|
+
const [submitting, setSubmitting] = useState(false);
|
|
128
132
|
const [promptText, setPromptText] = useState("");
|
|
129
133
|
const [googleDocContext, setGoogleDocContext] = useState("");
|
|
130
134
|
const [googleSlidesUrl, setGoogleSlidesUrl] = useState("");
|
|
131
|
-
const [
|
|
135
|
+
const [importMode, setImportMode] = useState<PromptImportSource | null>(null);
|
|
136
|
+
const [selectedImportFile, setSelectedImportFile] = useState<File | null>(
|
|
137
|
+
null,
|
|
138
|
+
);
|
|
132
139
|
const [importingSource, setImportingSource] =
|
|
133
140
|
useState<PromptImportSource | null>(null);
|
|
134
141
|
const pdfInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -216,16 +223,20 @@ export default function PromptPopover({
|
|
|
216
223
|
if (files.length > 0 && onBeforeUpload?.(enrichedText, files) === false) {
|
|
217
224
|
return;
|
|
218
225
|
}
|
|
226
|
+
setSubmitting(true);
|
|
219
227
|
try {
|
|
220
228
|
const uploaded = await uploadFiles(files);
|
|
221
|
-
onSubmit(enrichedText, uploaded);
|
|
229
|
+
await onSubmit(enrichedText, uploaded);
|
|
230
|
+
setSubmitting(false);
|
|
222
231
|
} catch (error) {
|
|
232
|
+
setSubmitting(false);
|
|
223
233
|
toast.error(t("raw.uploadFailed"), {
|
|
224
234
|
description:
|
|
225
235
|
error instanceof Error
|
|
226
236
|
? error.message
|
|
227
237
|
: t("raw.uploadAttachedFailed"),
|
|
228
238
|
});
|
|
239
|
+
throw error;
|
|
229
240
|
}
|
|
230
241
|
},
|
|
231
242
|
[googleDocContext, onBeforeUpload, onSubmit, uploadFiles, t],
|
|
@@ -255,11 +266,25 @@ export default function PromptPopover({
|
|
|
255
266
|
const handleFileImport = useCallback(
|
|
256
267
|
(kind: "pdf" | "pptx", file: File | undefined) => {
|
|
257
268
|
if (!file) return;
|
|
269
|
+
setSelectedImportFile(file);
|
|
258
270
|
void runImport({ kind, files: [file] });
|
|
259
271
|
},
|
|
260
272
|
[runImport],
|
|
261
273
|
);
|
|
262
274
|
|
|
275
|
+
const chooseImportMode = useCallback((kind: PromptImportSource) => {
|
|
276
|
+
setImportMode(kind);
|
|
277
|
+
setSelectedImportFile(null);
|
|
278
|
+
setGoogleSlidesUrl("");
|
|
279
|
+
}, []);
|
|
280
|
+
|
|
281
|
+
const returnToPrompt = useCallback(() => {
|
|
282
|
+
if (importingSource) return;
|
|
283
|
+
setImportMode(null);
|
|
284
|
+
setSelectedImportFile(null);
|
|
285
|
+
setGoogleSlidesUrl("");
|
|
286
|
+
}, [importingSource]);
|
|
287
|
+
|
|
263
288
|
const handleGoogleSlidesImport = useCallback(() => {
|
|
264
289
|
const url = googleSlidesUrl.trim();
|
|
265
290
|
if (!url) return;
|
|
@@ -271,13 +296,29 @@ export default function PromptPopover({
|
|
|
271
296
|
setPromptText("");
|
|
272
297
|
setGoogleDocContext("");
|
|
273
298
|
setGoogleSlidesUrl("");
|
|
274
|
-
|
|
299
|
+
setImportMode(null);
|
|
300
|
+
setSelectedImportFile(null);
|
|
275
301
|
setImportingSource(null);
|
|
302
|
+
setSubmitting(false);
|
|
276
303
|
}
|
|
277
304
|
}, [open]);
|
|
278
305
|
|
|
279
306
|
if (!open) return null;
|
|
280
307
|
|
|
308
|
+
const importEnabled = Boolean(onImport && importFromLabel);
|
|
309
|
+
const importFromCopy = importFromLabel?.trim().toLowerCase().startsWith("or ")
|
|
310
|
+
? importFromLabel.trim()
|
|
311
|
+
: importFromLabel
|
|
312
|
+
? `or ${importFromLabel.trim().toLowerCase()}`
|
|
313
|
+
: "";
|
|
314
|
+
const importModeLabel =
|
|
315
|
+
importMode === "google-slides"
|
|
316
|
+
? t("home.googleSlidesReferenceTitle")
|
|
317
|
+
: importMode === "pdf"
|
|
318
|
+
? "PDF"
|
|
319
|
+
: "PPT";
|
|
320
|
+
const importInputRef = importMode === "pdf" ? pdfInputRef : pptxInputRef;
|
|
321
|
+
|
|
281
322
|
const popover = (
|
|
282
323
|
<>
|
|
283
324
|
{centered && (
|
|
@@ -296,7 +337,7 @@ export default function PromptPopover({
|
|
|
296
337
|
>
|
|
297
338
|
<div className="flex items-center justify-between gap-3 px-4 pb-2.5 pt-3.5">
|
|
298
339
|
<span className="text-sm font-medium text-foreground">{title}</span>
|
|
299
|
-
{onSkip && (
|
|
340
|
+
{onSkip && !importMode && !submitting && (
|
|
300
341
|
<button
|
|
301
342
|
type="button"
|
|
302
343
|
onClick={() => {
|
|
@@ -310,132 +351,214 @@ export default function PromptPopover({
|
|
|
310
351
|
)}
|
|
311
352
|
</div>
|
|
312
353
|
|
|
313
|
-
|
|
314
|
-
<
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
initialText={initialText}
|
|
325
|
-
initialTextKey={initialTextKey}
|
|
354
|
+
{importEnabled && (
|
|
355
|
+
<input
|
|
356
|
+
ref={pdfInputRef}
|
|
357
|
+
type="file"
|
|
358
|
+
accept=".pdf,application/pdf"
|
|
359
|
+
className="sr-only"
|
|
360
|
+
aria-label={t("editorToolbar.importFile")}
|
|
361
|
+
onChange={(event) => {
|
|
362
|
+
handleFileImport("pdf", event.target.files?.[0]);
|
|
363
|
+
event.target.value = "";
|
|
364
|
+
}}
|
|
326
365
|
/>
|
|
327
|
-
|
|
366
|
+
)}
|
|
367
|
+
{importEnabled && (
|
|
368
|
+
<input
|
|
369
|
+
ref={pptxInputRef}
|
|
370
|
+
type="file"
|
|
371
|
+
accept=".pptx,application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
372
|
+
className="sr-only"
|
|
373
|
+
aria-label={t("editorToolbar.importFile")}
|
|
374
|
+
onChange={(event) => {
|
|
375
|
+
handleFileImport("pptx", event.target.files?.[0]);
|
|
376
|
+
event.target.value = "";
|
|
377
|
+
}}
|
|
378
|
+
/>
|
|
379
|
+
)}
|
|
328
380
|
|
|
329
|
-
{
|
|
330
|
-
<div
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
event.target.value = "";
|
|
355
|
-
}}
|
|
381
|
+
<div className={importEnabled ? "grid" : undefined}>
|
|
382
|
+
<div
|
|
383
|
+
className={[
|
|
384
|
+
importEnabled ? "col-start-1 row-start-1" : "",
|
|
385
|
+
importMode ? "invisible pointer-events-none" : "",
|
|
386
|
+
]
|
|
387
|
+
.filter(Boolean)
|
|
388
|
+
.join(" ")}
|
|
389
|
+
aria-hidden={importMode ? true : undefined}
|
|
390
|
+
>
|
|
391
|
+
<div className="px-2.5 pb-2.5">
|
|
392
|
+
<PromptComposer
|
|
393
|
+
autoFocus
|
|
394
|
+
attachmentsEnabled
|
|
395
|
+
maxDocumentAttachmentBytes={MAX_REFERENCE_FILE_BYTES}
|
|
396
|
+
documentAttachmentLimitLabel="Slides reference files"
|
|
397
|
+
disabled={
|
|
398
|
+
loading || uploading || submitting || Boolean(importMode)
|
|
399
|
+
}
|
|
400
|
+
placeholder={placeholder}
|
|
401
|
+
onSubmit={handleSubmit}
|
|
402
|
+
onTextChange={setPromptText}
|
|
403
|
+
draftScope={draftScope}
|
|
404
|
+
initialText={initialText}
|
|
405
|
+
initialTextKey={initialTextKey}
|
|
356
406
|
/>
|
|
357
|
-
<Button
|
|
358
|
-
type="button"
|
|
359
|
-
variant="ghost"
|
|
360
|
-
size="sm"
|
|
361
|
-
className="h-7 gap-1.5 px-2 text-xs text-muted-foreground hover:text-foreground"
|
|
362
|
-
disabled={importingSource !== null || loading || uploading}
|
|
363
|
-
onClick={() => pdfInputRef.current?.click()}
|
|
364
|
-
>
|
|
365
|
-
<IconFileTypePdf className="size-3.5" />
|
|
366
|
-
PDF
|
|
367
|
-
</Button>
|
|
368
|
-
<Button
|
|
369
|
-
type="button"
|
|
370
|
-
variant="ghost"
|
|
371
|
-
size="sm"
|
|
372
|
-
className="h-7 gap-1.5 px-2 text-xs text-muted-foreground hover:text-foreground"
|
|
373
|
-
disabled={importingSource !== null || loading || uploading}
|
|
374
|
-
onClick={() => setGoogleSlidesInputOpen((current) => !current)}
|
|
375
|
-
>
|
|
376
|
-
<IconBrandGoogle className="size-3.5" />
|
|
377
|
-
{t("home.googleSlidesReferenceTitle")}
|
|
378
|
-
</Button>
|
|
379
|
-
<Button
|
|
380
|
-
type="button"
|
|
381
|
-
variant="ghost"
|
|
382
|
-
size="sm"
|
|
383
|
-
className="h-7 gap-1.5 px-2 text-xs text-muted-foreground hover:text-foreground"
|
|
384
|
-
disabled={importingSource !== null || loading || uploading}
|
|
385
|
-
onClick={() => pptxInputRef.current?.click()}
|
|
386
|
-
>
|
|
387
|
-
<IconPresentation className="size-3.5" />
|
|
388
|
-
PPT
|
|
389
|
-
</Button>
|
|
390
|
-
{importingSource && (
|
|
391
|
-
<span className="ml-auto text-xs text-muted-foreground">
|
|
392
|
-
{importingLabel}
|
|
393
|
-
</span>
|
|
394
|
-
)}
|
|
395
407
|
</div>
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
408
|
+
|
|
409
|
+
{submitting && (
|
|
410
|
+
<div
|
|
411
|
+
className="flex items-center gap-2 border-t border-border/60 px-4 py-2.5 text-xs text-muted-foreground"
|
|
412
|
+
role="status"
|
|
413
|
+
aria-live="polite"
|
|
414
|
+
>
|
|
415
|
+
<IconLoader2 className="size-3.5 animate-spin" />
|
|
416
|
+
<span>{t("raw.uploading")}</span>
|
|
417
|
+
</div>
|
|
418
|
+
)}
|
|
419
|
+
|
|
420
|
+
{importEnabled && (
|
|
421
|
+
<div className="border-t border-border/60 px-4 pb-3 pt-2.5">
|
|
422
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
423
|
+
<span className="mr-1 text-xs text-muted-foreground">
|
|
424
|
+
{importFromCopy}
|
|
425
|
+
</span>
|
|
426
|
+
<Button
|
|
427
|
+
type="button"
|
|
428
|
+
variant="ghost"
|
|
429
|
+
size="sm"
|
|
430
|
+
className="h-7 gap-1.5 px-2 text-xs text-muted-foreground hover:text-foreground"
|
|
431
|
+
disabled={loading || uploading || submitting}
|
|
432
|
+
onClick={() => chooseImportMode("pdf")}
|
|
433
|
+
>
|
|
434
|
+
<IconFileTypePdf className="size-3.5" />
|
|
435
|
+
PDF
|
|
436
|
+
</Button>
|
|
413
437
|
<Button
|
|
414
438
|
type="button"
|
|
439
|
+
variant="ghost"
|
|
415
440
|
size="sm"
|
|
416
|
-
className="h-
|
|
417
|
-
disabled={
|
|
418
|
-
|
|
419
|
-
importingSource !== null ||
|
|
420
|
-
loading ||
|
|
421
|
-
uploading
|
|
422
|
-
}
|
|
423
|
-
onClick={handleGoogleSlidesImport}
|
|
441
|
+
className="h-7 gap-1.5 px-2 text-xs text-muted-foreground hover:text-foreground"
|
|
442
|
+
disabled={loading || uploading || submitting}
|
|
443
|
+
onClick={() => chooseImportMode("google-slides")}
|
|
424
444
|
>
|
|
425
|
-
|
|
445
|
+
<IconBrandGoogle className="size-3.5" />
|
|
446
|
+
{t("home.googleSlidesImportLabel")}
|
|
447
|
+
</Button>
|
|
448
|
+
<Button
|
|
449
|
+
type="button"
|
|
450
|
+
variant="ghost"
|
|
451
|
+
size="sm"
|
|
452
|
+
className="h-7 gap-1.5 px-2 text-xs text-muted-foreground hover:text-foreground"
|
|
453
|
+
disabled={loading || uploading || submitting}
|
|
454
|
+
onClick={() => chooseImportMode("pptx")}
|
|
455
|
+
>
|
|
456
|
+
<IconPresentation className="size-3.5" />
|
|
457
|
+
PPT
|
|
426
458
|
</Button>
|
|
427
459
|
</div>
|
|
428
460
|
</div>
|
|
429
461
|
)}
|
|
462
|
+
|
|
463
|
+
{children}
|
|
464
|
+
|
|
465
|
+
<GoogleDocImportHint
|
|
466
|
+
promptText={promptText}
|
|
467
|
+
onSourceContextChange={setGoogleDocContext}
|
|
468
|
+
/>
|
|
430
469
|
</div>
|
|
431
|
-
)}
|
|
432
470
|
|
|
433
|
-
|
|
471
|
+
{importEnabled && importMode && (
|
|
472
|
+
<div className="col-start-1 row-start-1 flex min-h-full flex-col px-4 pb-4 pt-3">
|
|
473
|
+
<div className="flex items-center gap-2">
|
|
474
|
+
<button
|
|
475
|
+
type="button"
|
|
476
|
+
onClick={returnToPrompt}
|
|
477
|
+
disabled={importingSource !== null}
|
|
478
|
+
className="-ms-1 inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
|
|
479
|
+
aria-label={
|
|
480
|
+
"Back to prompt" /* i18n-ignore -- local import-flow control label */
|
|
481
|
+
}
|
|
482
|
+
>
|
|
483
|
+
<IconArrowLeft className="size-4" />
|
|
484
|
+
</button>
|
|
485
|
+
<span className="text-sm font-medium text-foreground">
|
|
486
|
+
Import {importModeLabel}
|
|
487
|
+
</span>
|
|
488
|
+
</div>
|
|
434
489
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
490
|
+
<div className="flex flex-1 flex-col justify-center gap-3">
|
|
491
|
+
{importMode === "google-slides" ? (
|
|
492
|
+
<>
|
|
493
|
+
<GoogleDriveConnectionCta />
|
|
494
|
+
<div className="flex gap-2">
|
|
495
|
+
<Input
|
|
496
|
+
autoFocus
|
|
497
|
+
type="url"
|
|
498
|
+
value={googleSlidesUrl}
|
|
499
|
+
placeholder={t("home.googleSlidesReferenceUrl")}
|
|
500
|
+
aria-label={t("home.googleSlidesReferenceUrl")}
|
|
501
|
+
className="h-8 text-xs"
|
|
502
|
+
disabled={importingSource !== null || loading}
|
|
503
|
+
onChange={(event) =>
|
|
504
|
+
setGoogleSlidesUrl(event.target.value)
|
|
505
|
+
}
|
|
506
|
+
onKeyDown={(event) => {
|
|
507
|
+
if (event.key === "Enter") handleGoogleSlidesImport();
|
|
508
|
+
}}
|
|
509
|
+
/>
|
|
510
|
+
<Button
|
|
511
|
+
type="button"
|
|
512
|
+
size="sm"
|
|
513
|
+
className="h-8 shrink-0 px-3 text-xs"
|
|
514
|
+
disabled={
|
|
515
|
+
!googleSlidesUrl.trim() ||
|
|
516
|
+
importingSource !== null ||
|
|
517
|
+
loading
|
|
518
|
+
}
|
|
519
|
+
onClick={handleGoogleSlidesImport}
|
|
520
|
+
>
|
|
521
|
+
Import
|
|
522
|
+
</Button>
|
|
523
|
+
</div>
|
|
524
|
+
</>
|
|
525
|
+
) : (
|
|
526
|
+
<>
|
|
527
|
+
<p className="text-xs text-muted-foreground">
|
|
528
|
+
Choose a {importModeLabel} file to open it as a deck.
|
|
529
|
+
</p>
|
|
530
|
+
<Button
|
|
531
|
+
type="button"
|
|
532
|
+
variant="outline"
|
|
533
|
+
className="w-full justify-center gap-2"
|
|
534
|
+
disabled={importingSource !== null || loading}
|
|
535
|
+
onClick={() => importInputRef.current?.click()}
|
|
536
|
+
>
|
|
537
|
+
<IconUpload className="size-4" />
|
|
538
|
+
Upload {importModeLabel}
|
|
539
|
+
</Button>
|
|
540
|
+
{selectedImportFile && (
|
|
541
|
+
<p className="truncate text-center text-xs text-muted-foreground">
|
|
542
|
+
{selectedImportFile.name}
|
|
543
|
+
</p>
|
|
544
|
+
)}
|
|
545
|
+
</>
|
|
546
|
+
)}
|
|
547
|
+
</div>
|
|
548
|
+
|
|
549
|
+
{importingSource && (
|
|
550
|
+
<div
|
|
551
|
+
className="flex items-center justify-center gap-2 text-xs text-muted-foreground"
|
|
552
|
+
role="status"
|
|
553
|
+
aria-live="polite"
|
|
554
|
+
>
|
|
555
|
+
<IconLoader2 className="size-3.5 animate-spin" />
|
|
556
|
+
<span>{importingLabel}</span>
|
|
557
|
+
</div>
|
|
558
|
+
)}
|
|
559
|
+
</div>
|
|
560
|
+
)}
|
|
561
|
+
</div>
|
|
439
562
|
</div>
|
|
440
563
|
</>
|
|
441
564
|
);
|
|
@@ -119,6 +119,8 @@ import {
|
|
|
119
119
|
getSlideTextBoxDefaultColor,
|
|
120
120
|
getSlideSelectionIdentity,
|
|
121
121
|
getSlideSelectionMode,
|
|
122
|
+
findPersistedImageObject,
|
|
123
|
+
isDeletableFlowImage,
|
|
122
124
|
removeSlideObjectAndLayoutSpacer,
|
|
123
125
|
resolveSlideObjectContainingBlock,
|
|
124
126
|
type CopiedSlideObjects,
|
|
@@ -148,7 +150,7 @@ function ExcalidrawExitButton(props: { onExit: () => void; label: string }) {
|
|
|
148
150
|
<Button
|
|
149
151
|
variant="ghost"
|
|
150
152
|
size="icon"
|
|
151
|
-
className="absolute right-3 top-3 z-20 h-8 w-8 cursor-pointer border border-border bg-popover/95 shadow-lg
|
|
153
|
+
className="absolute right-3 top-3 z-20 h-8 w-8 cursor-pointer border border-border bg-popover/95 shadow-lg"
|
|
152
154
|
onClick={props.onExit}
|
|
153
155
|
aria-label={props.label}
|
|
154
156
|
>
|
|
@@ -650,7 +652,7 @@ function SameSlidePresenceIndicator({ users }: { users: CollabUser[] }) {
|
|
|
650
652
|
? `${users[0].name} is here`
|
|
651
653
|
: `${users.length} others here`;
|
|
652
654
|
return (
|
|
653
|
-
<div className="pointer-events-auto flex items-center gap-1.5 rounded-full border border-border bg-popover/95 py-1 pl-1 pr-2.5 text-xs text-popover-foreground shadow-lg
|
|
655
|
+
<div className="pointer-events-auto flex items-center gap-1.5 rounded-full border border-border bg-popover/95 py-1 pl-1 pr-2.5 text-xs text-popover-foreground shadow-lg">
|
|
654
656
|
<div className="flex items-center">
|
|
655
657
|
{visible.map((u) => (
|
|
656
658
|
<SamePresenceAvatar key={u.email} user={u} />
|
|
@@ -2404,9 +2406,24 @@ export default function SlideEditor({
|
|
|
2404
2406
|
clearMultiSelection();
|
|
2405
2407
|
} else {
|
|
2406
2408
|
const element = resolveSelectedElement();
|
|
2407
|
-
if (!element
|
|
2408
|
-
|
|
2409
|
-
|
|
2409
|
+
if (!element) return;
|
|
2410
|
+
if (isPersistedFreeformObject(element)) {
|
|
2411
|
+
e.preventDefault();
|
|
2412
|
+
removeSlideObjectAndLayoutSpacer(element);
|
|
2413
|
+
} else if (isDeletableFlowImage(element)) {
|
|
2414
|
+
e.preventDefault();
|
|
2415
|
+
// An imported PPTX/PDF image is an <img> (or placeholder) nested in
|
|
2416
|
+
// an absolutely positioned wrapper that carries the persisted object
|
|
2417
|
+
// id. Removing only the inner node would leave that wrapper and its
|
|
2418
|
+
// durable metadata behind as an invisible ghost object.
|
|
2419
|
+
const owner = findPersistedImageObject(element, slideContent);
|
|
2420
|
+
if (owner) removeSlideObjectAndLayoutSpacer(owner);
|
|
2421
|
+
else element.remove();
|
|
2422
|
+
setSelectedImg(null);
|
|
2423
|
+
setImageOverlay(null);
|
|
2424
|
+
} else {
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2410
2427
|
clearSelectedElement();
|
|
2411
2428
|
}
|
|
2412
2429
|
|
|
@@ -4229,7 +4246,7 @@ export default function SlideEditor({
|
|
|
4229
4246
|
</div>
|
|
4230
4247
|
) : (
|
|
4231
4248
|
<div className="relative h-full bg-background">
|
|
4232
|
-
<div className="absolute right-3 top-3 z-20 flex h-8 items-center gap-0.5 rounded-md border border-border bg-popover/95 px-1 shadow-lg
|
|
4249
|
+
<div className="absolute right-3 top-3 z-20 flex h-8 items-center gap-0.5 rounded-md border border-border bg-popover/95 px-1 shadow-lg">
|
|
4233
4250
|
<Tooltip>
|
|
4234
4251
|
<TooltipTrigger asChild>
|
|
4235
4252
|
<Button
|
|
@@ -4413,7 +4430,12 @@ export default function SlideEditor({
|
|
|
4413
4430
|
onSendToAgent={sendSelectionToAgent}
|
|
4414
4431
|
/>
|
|
4415
4432
|
|
|
4416
|
-
<BlockBubbleMenu
|
|
4433
|
+
<BlockBubbleMenu
|
|
4434
|
+
editingEl={editingEl}
|
|
4435
|
+
slideId={slide.id}
|
|
4436
|
+
deckId={deckId}
|
|
4437
|
+
onCommitInlineEdit={exitInlineEdit}
|
|
4438
|
+
/>
|
|
4417
4439
|
|
|
4418
4440
|
{pendingUpdateCount > 0 && (
|
|
4419
4441
|
<div className="absolute top-4 right-4 z-50">
|
|
@@ -521,6 +521,51 @@ export function removeSlideObjectAndLayoutSpacer(element: HTMLElement): void {
|
|
|
521
521
|
element.remove();
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
/**
|
|
525
|
+
* Whether Delete should remove `element` even though it is not a freeform
|
|
526
|
+
* canvas object.
|
|
527
|
+
*
|
|
528
|
+
* Flow-layout nodes are deliberately excluded from object operations, because
|
|
529
|
+
* deleting an arbitrary one collapses the layout around it. An image is the
|
|
530
|
+
* exception: it is a leaf, users select it directly, and removing it leaves
|
|
531
|
+
* the surrounding grid or card intact. Without this, selecting a picture in a
|
|
532
|
+
* card grid and pressing Delete silently did nothing.
|
|
533
|
+
*/
|
|
534
|
+
export function isDeletableFlowImage(element: HTMLElement): boolean {
|
|
535
|
+
return (
|
|
536
|
+
element.tagName === "IMG" ||
|
|
537
|
+
element.classList.contains("fmd-img-placeholder")
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* The persisted image object that owns `element`, if any.
|
|
543
|
+
*
|
|
544
|
+
* PPTX/PDF import wraps each picture in an absolutely positioned
|
|
545
|
+
* `.fmd-pptx-image` div carrying the durable `data-slide-object-id`, with the
|
|
546
|
+
* `<img>` (or an empty placeholder) inside it. Deleting the inner node alone
|
|
547
|
+
* leaves that wrapper behind as an invisible object that still occupies its
|
|
548
|
+
* slot and still round-trips through save. Matching on the image wrapper
|
|
549
|
+
* specifically — rather than any positioned ancestor — keeps this from
|
|
550
|
+
* swallowing a whole card or column that merely contains a picture.
|
|
551
|
+
*/
|
|
552
|
+
export function findPersistedImageObject(
|
|
553
|
+
element: HTMLElement,
|
|
554
|
+
root: HTMLElement,
|
|
555
|
+
): HTMLElement | null {
|
|
556
|
+
let current: HTMLElement | null = element;
|
|
557
|
+
while (current && current !== root && root.contains(current)) {
|
|
558
|
+
const isImageWrapper =
|
|
559
|
+
current.classList.contains("fmd-pptx-image") ||
|
|
560
|
+
current.getAttribute("data-pptx-element-kind") === "image";
|
|
561
|
+
if (isImageWrapper && current.getAttribute("data-slide-object-id")) {
|
|
562
|
+
return current;
|
|
563
|
+
}
|
|
564
|
+
current = current.parentElement;
|
|
565
|
+
}
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
|
|
524
569
|
/** Convert a viewport click into the unscaled fmd-slide coordinate system. */
|
|
525
570
|
export function clientPointToSlideCoordinates(
|
|
526
571
|
clientX: number,
|
|
@@ -87,7 +87,7 @@ export function AgentWorkIndicator() {
|
|
|
87
87
|
|
|
88
88
|
return (
|
|
89
89
|
<div className="pointer-events-none fixed bottom-4 left-1/2 z-50 w-[calc(100vw-2rem)] max-w-sm -translate-x-1/2 md:bottom-5">
|
|
90
|
-
<div className="pointer-events-auto flex items-center justify-between gap-3 rounded-lg border border-border bg-popover/95 px-3 py-2 text-popover-foreground shadow-xl shadow-black/20
|
|
90
|
+
<div className="pointer-events-auto flex items-center justify-between gap-3 rounded-lg border border-border bg-popover/95 px-3 py-2 text-popover-foreground shadow-xl shadow-black/20">
|
|
91
91
|
<div className="flex min-w-0 items-center gap-2">
|
|
92
92
|
<IconLoader2 className="h-4 w-4 shrink-0 animate-spin text-[#609FF8]" />
|
|
93
93
|
<span className="truncate text-sm font-medium">
|