@agent-native/core 0.186.0-nightly-20260923174435 → 0.186.0
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/CHANGELOG.md +6 -0
- package/corpus/templates/analytics/app/components/Markdown.tsx +3 -3
- package/corpus/templates/analytics/app/i18n/en-US.ts +1513 -3
- package/corpus/templates/analytics/app/routes/$.tsx +2 -2
- package/corpus/templates/analytics/app/routes/agents.tsx +2 -2
- package/corpus/templates/analytics/app/routes/analyses.$id.tsx +2 -2
- package/corpus/templates/analytics/app/routes/analyses._index.tsx +2 -2
- package/corpus/templates/analytics/app/routes/ask.tsx +2 -2
- package/corpus/templates/analytics/app/routes/dashboards.$id.tsx +2 -2
- package/corpus/templates/analytics/app/routes/data-dictionary.tsx +2 -2
- package/corpus/templates/analytics/app/routes/data-sources.tsx +2 -2
- package/corpus/templates/analytics/app/routes/extensions.$id.tsx +2 -2
- package/corpus/templates/analytics/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/analytics/app/routes/monitoring._index.tsx +2 -2
- package/corpus/templates/analytics/app/routes/sessions.$recordingId.tsx +2 -2
- package/corpus/templates/analytics/app/routes/sessions._index.tsx +2 -2
- package/corpus/templates/analytics/app/routes/settings.tsx +2 -2
- package/corpus/templates/analytics/app/routes/team.tsx +2 -2
- package/corpus/templates/analytics/changelog/2026-09-23-analytics-opens-faster-by-loading-translation-catalogs-and-r.md +6 -0
- package/corpus/templates/calendar/CHANGELOG.md +12 -0
- package/corpus/templates/calendar/app/i18n/en-US.ts +985 -3
- package/corpus/templates/calendar/app/i18n/index.ts +16 -11
- package/corpus/templates/calendar/app/routes/_app.$.tsx +2 -2
- package/corpus/templates/calendar/app/routes/_app.agent.tsx +2 -2
- package/corpus/templates/calendar/app/routes/_app.booking-links.tsx +2 -2
- package/corpus/templates/calendar/app/routes/_app.settings.tsx +2 -2
- package/corpus/templates/calendar/app/routes/_app.shared-availability.add.tsx +2 -4
- package/corpus/templates/calendar/app/routes/_app.team.tsx +2 -2
- package/corpus/templates/calendar/app/routes/booking-og-meta.ts +2 -2
- package/corpus/templates/calendar/app/routes/booking.manage.$token.tsx +2 -2
- package/corpus/templates/calendar/app/routes/event.tsx +2 -2
- package/corpus/templates/calendar/changelog/2026-09-23-calendar-opens-faster-by-loading-translated-catalogs-only-wh.md +6 -0
- package/corpus/templates/clips/CHANGELOG.md +10 -0
- package/corpus/templates/clips/app/components/library/recording-card.tsx +1 -1
- package/corpus/templates/clips/app/components/player/agent-view-count.tsx +61 -0
- package/corpus/templates/clips/app/components/player/recording-views-badge.tsx +57 -72
- package/corpus/templates/clips/changelog/2026-09-23-insights-charts-load-only-when-opened-keeping-the-recording-.md +6 -0
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +1 -1
- package/corpus/templates/content/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/content/app/components/layout/Layout.tsx +5 -5
- package/corpus/templates/content/app/root.tsx +2 -1
- package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +2 -2
- package/corpus/templates/design/CHANGELOG.md +7 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +55 -20
- package/corpus/templates/design/app/i18n/en-US.ts +1964 -21
- package/corpus/templates/design/app/pages/Index.tsx +5 -1
- package/corpus/templates/design/app/pages/Templates.tsx +5 -1
- package/corpus/templates/design/app/routes/$.tsx +2 -2
- package/corpus/templates/design/app/routes/design-systems.tsx +2 -2
- package/corpus/templates/design/app/routes/design-systems_.setup.tsx +2 -2
- package/corpus/templates/design/app/routes/design.$id.tsx +2 -2
- package/corpus/templates/design/app/routes/extensions.$id.tsx +2 -2
- package/corpus/templates/design/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/design/app/routes/extensions.tsx +2 -2
- package/corpus/templates/design/app/routes/observability.tsx +2 -2
- package/corpus/templates/design/app/routes/present.$id.tsx +2 -2
- package/corpus/templates/design/app/routes/settings.tsx +2 -2
- package/corpus/templates/design/app/routes/team.tsx +2 -2
- package/corpus/templates/design/app/routes/templates.tsx +2 -2
- package/corpus/templates/design/app/routes/visual-edit.tsx +2 -2
- package/corpus/templates/design/app/routes/visual-edit_.$id.tsx +2 -2
- package/corpus/templates/design/changelog/2026-09-23-design-loads-translated-catalogs-and-ai-prompt-tools-only-wh.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +56 -13
- package/corpus/templates/plan/app/lib/plan-local-bridge.ts +2 -9
- package/corpus/templates/plan/app/lib/plan-return-path.ts +8 -0
- package/corpus/templates/slides/CHANGELOG.md +6 -0
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +6 -17
- package/corpus/templates/slides/app/components/editor/DeferredPopoverFallback.tsx +148 -0
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +7 -1
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +66 -42
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +7 -284
- package/corpus/templates/slides/app/lib/deferred-editor-surfaces.ts +25 -0
- package/corpus/templates/slides/app/lib/portaled-layer.ts +7 -0
- package/corpus/templates/slides/app/lib/prompt-file-uploads.ts +269 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
- package/corpus/templates/slides/app/pages/Index.tsx +139 -52
- package/corpus/templates/slides/changelog/2026-09-23-slides-loads-ai-prompt-tools-and-extra-slide-controls-only-w.md +6 -0
- package/dist/client/agent-chat/index.d.ts +2 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/chat/assistant-chat-history-version.d.ts +13 -0
- package/dist/client/chat/assistant-chat-history-version.js +18 -0
- package/dist/client/chat/message-components.d.ts +3 -8
- package/dist/client/chat/message-components.js +2 -18
- package/dist/client/chat/run-recovery.js +2 -1
- package/dist/client/lazy-chunk-retry-fallback.d.ts +3 -0
- package/dist/client/lazy-chunk-retry-fallback.js +9 -0
- package/dist/client/settings/deferred-builder-connect-popover.d.ts +1 -3
- package/dist/client/settings/deferred-builder-connect-popover.js +3 -9
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +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/org/index.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/package.json +8 -4
- package/corpus/templates/analytics/app/i18n-data.ts +0 -13975
- package/corpus/templates/calendar/app/i18n-data.ts +0 -10572
- package/corpus/templates/design/app/i18n-data.ts +0 -17719
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from "@agent-native/core/client/collab";
|
|
6
6
|
import { useAvatarUrl } from "@agent-native/core/client/hooks";
|
|
7
7
|
import { useT } from "@agent-native/core/client/i18n";
|
|
8
|
+
import { LazyChunkErrorBoundary } from "@agent-native/core/client/lazy-chunk-error-boundary";
|
|
8
9
|
import { DEFAULT_AGENT_IDENTITY } from "@agent-native/toolkit/collab-ui";
|
|
9
10
|
import {
|
|
10
11
|
useSortable,
|
|
@@ -15,13 +16,13 @@ import { CSS } from "@dnd-kit/utilities";
|
|
|
15
16
|
import { appStateKeyForBrowserTab } from "@shared/app-state-tabs";
|
|
16
17
|
import { hashSlideContent, type DeckFitState } from "@shared/slide-fit";
|
|
17
18
|
import { IconEyeOff } from "@tabler/icons-react";
|
|
18
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
19
|
+
import { Suspense, useCallback, useEffect, useRef, useState } from "react";
|
|
19
20
|
import { toast } from "sonner";
|
|
20
21
|
|
|
21
22
|
import SlideRenderer from "@/components/deck/SlideRenderer";
|
|
22
23
|
import type { SlideOverflowInfo } from "@/components/deck/SlideRenderer";
|
|
23
|
-
import { AddSlidePopover } from "@/components/editor/AddSlidePopover";
|
|
24
24
|
import { AiEditingMarker } from "@/components/editor/AiEditingMarker";
|
|
25
|
+
import { DeferredPopoverFallback } from "@/components/editor/DeferredPopoverFallback";
|
|
25
26
|
import GeneratingSlidePreview from "@/components/editor/GeneratingSlidePreview";
|
|
26
27
|
import {
|
|
27
28
|
ContextMenu,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
} from "@/components/ui/tooltip";
|
|
39
40
|
import { defaultSlideContent, type Slide } from "@/context/DeckContext";
|
|
40
41
|
import { getAspectRatioDims, type AspectRatio } from "@/lib/aspect-ratios";
|
|
42
|
+
import { DeferredAddSlidePopover } from "@/lib/deferred-editor-surfaces";
|
|
41
43
|
import { TAB_ID } from "@/lib/tab-id";
|
|
42
44
|
import { shortcutLabel } from "@/lib/utils";
|
|
43
45
|
|
|
@@ -669,6 +671,10 @@ export default function EditorSidebar({
|
|
|
669
671
|
const t = useT();
|
|
670
672
|
const [describeAnchorEl, setDescribeAnchorEl] =
|
|
671
673
|
useState<HTMLButtonElement | null>(null);
|
|
674
|
+
const closeDescribePopover = useCallback(() => {
|
|
675
|
+
onCloseDescribe();
|
|
676
|
+
setDescribeAnchorEl(null);
|
|
677
|
+
}, [onCloseDescribe]);
|
|
672
678
|
const [thumbnailListScrolled, setThumbnailListScrolled] = useState(false);
|
|
673
679
|
const slideButtonRefs = useRef(new Map<string, HTMLButtonElement>());
|
|
674
680
|
const focusAfterDeleteRef = useRef<string | null>(null);
|
|
@@ -1019,47 +1025,65 @@ export default function EditorSidebar({
|
|
|
1019
1025
|
</div>
|
|
1020
1026
|
</div>
|
|
1021
1027
|
{describeSlideId && describeSlideIndex !== -1 && describeAnchorEl && (
|
|
1022
|
-
<
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
onAddSlideGeneratingChange?.(true, describeSlideId);
|
|
1040
|
-
try {
|
|
1041
|
-
await onAwaitAddSlidePersisted?.();
|
|
1042
|
-
} catch (error) {
|
|
1043
|
-
console.error("Failed to persist new slide:", error);
|
|
1044
|
-
onAddSlideGeneratingChange?.(false, null);
|
|
1045
|
-
// Only remove the placeholder if it's still untouched —
|
|
1046
|
-
// the save retries take long enough that the user could have
|
|
1047
|
-
// started editing it directly on the canvas in the meantime,
|
|
1048
|
-
// and deleting it would destroy that work.
|
|
1049
|
-
const current = slides.find((s) => s.id === describeSlideId);
|
|
1050
|
-
if (
|
|
1051
|
-
current?.content === defaultSlideContent.blank &&
|
|
1052
|
-
!current.notes
|
|
1053
|
-
) {
|
|
1054
|
-
onRemoveFailedSlide?.(describeSlideId);
|
|
1055
|
-
}
|
|
1056
|
-
toast.error(t("editorSidebar.newSlideSaveFailed"));
|
|
1057
|
-
return false;
|
|
1028
|
+
<LazyChunkErrorBoundary
|
|
1029
|
+
fallback={
|
|
1030
|
+
<DeferredPopoverFallback
|
|
1031
|
+
surface="add-slide"
|
|
1032
|
+
anchorRef={{ current: describeAnchorEl }}
|
|
1033
|
+
failed
|
|
1034
|
+
onClose={closeDescribePopover}
|
|
1035
|
+
/>
|
|
1036
|
+
}
|
|
1037
|
+
>
|
|
1038
|
+
<Suspense
|
|
1039
|
+
fallback={
|
|
1040
|
+
<DeferredPopoverFallback
|
|
1041
|
+
surface="add-slide"
|
|
1042
|
+
anchorRef={{ current: describeAnchorEl }}
|
|
1043
|
+
onClose={closeDescribePopover}
|
|
1044
|
+
/>
|
|
1058
1045
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1046
|
+
>
|
|
1047
|
+
<DeferredAddSlidePopover
|
|
1048
|
+
open
|
|
1049
|
+
onOpenChange={(open) => {
|
|
1050
|
+
if (!open) closeDescribePopover();
|
|
1051
|
+
}}
|
|
1052
|
+
anchorRef={{ current: describeAnchorEl }}
|
|
1053
|
+
placement="right"
|
|
1054
|
+
deckId={deckId}
|
|
1055
|
+
deckTitle={deckTitle}
|
|
1056
|
+
activeSlideId={describeSlideId}
|
|
1057
|
+
activeSlideIndex={describeSlideIndex}
|
|
1058
|
+
slideCount={slides.length}
|
|
1059
|
+
targetSlideId={describeSlideId}
|
|
1060
|
+
agentSubmit={async (message, context) => {
|
|
1061
|
+
onAddSlideGeneratingChange?.(true, describeSlideId);
|
|
1062
|
+
try {
|
|
1063
|
+
await onAwaitAddSlidePersisted?.();
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
console.error("Failed to persist new slide:", error);
|
|
1066
|
+
onAddSlideGeneratingChange?.(false, null);
|
|
1067
|
+
// Only remove the placeholder if it's still untouched —
|
|
1068
|
+
// the save retries take long enough that the user could have
|
|
1069
|
+
// started editing it directly on the canvas in the meantime,
|
|
1070
|
+
// and deleting it would destroy that work.
|
|
1071
|
+
const current = slides.find((s) => s.id === describeSlideId);
|
|
1072
|
+
if (
|
|
1073
|
+
current?.content === defaultSlideContent.blank &&
|
|
1074
|
+
!current.notes
|
|
1075
|
+
) {
|
|
1076
|
+
onRemoveFailedSlide?.(describeSlideId);
|
|
1077
|
+
}
|
|
1078
|
+
toast.error(t("editorSidebar.newSlideSaveFailed"));
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
1081
|
+
addSlideAgentSubmit(message, context);
|
|
1082
|
+
return true;
|
|
1083
|
+
}}
|
|
1084
|
+
/>
|
|
1085
|
+
</Suspense>
|
|
1086
|
+
</LazyChunkErrorBoundary>
|
|
1063
1087
|
)}
|
|
1064
1088
|
</div>
|
|
1065
1089
|
);
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { appBasePath } from "@agent-native/core/client/api-path";
|
|
2
1
|
import {
|
|
3
2
|
PromptComposer,
|
|
4
3
|
type PromptComposerSubmitOptions,
|
|
5
4
|
useEagerFileUploads,
|
|
6
5
|
} from "@agent-native/core/client/composer";
|
|
7
|
-
import { ensureEmbedAuthFetchInterceptor } from "@agent-native/core/client/host";
|
|
8
6
|
import { useT } from "@agent-native/core/client/i18n";
|
|
9
7
|
import {
|
|
10
8
|
IconArrowLeft,
|
|
@@ -18,31 +16,20 @@ import { useState, useEffect, useCallback, useRef } from "react";
|
|
|
18
16
|
import { createPortal } from "react-dom";
|
|
19
17
|
import { toast } from "sonner";
|
|
20
18
|
|
|
19
|
+
import { isInsidePortaledLayer } from "@/lib/portaled-layer";
|
|
21
20
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} from "@/lib/
|
|
21
|
+
deleteUploadedPromptFile,
|
|
22
|
+
uploadPromptFiles,
|
|
23
|
+
type UploadedFile,
|
|
24
|
+
} from "@/lib/prompt-file-uploads";
|
|
26
25
|
|
|
27
|
-
import {
|
|
28
|
-
MAX_REFERENCE_FILE_BYTES,
|
|
29
|
-
MAX_REFERENCE_FILES,
|
|
30
|
-
} from "../../../shared/upload-types";
|
|
26
|
+
import { MAX_REFERENCE_FILE_BYTES } from "../../../shared/upload-types";
|
|
31
27
|
import { Button } from "../ui/button";
|
|
32
28
|
import { Input } from "../ui/input";
|
|
33
29
|
import { GoogleDocImportHint } from "./GoogleDocImportHint";
|
|
34
30
|
import { GoogleDriveConnectionCta } from "./GoogleDriveConnectionCta";
|
|
35
31
|
|
|
36
|
-
export
|
|
37
|
-
path: string;
|
|
38
|
-
url?: string;
|
|
39
|
-
/** Browser-only fallback when the upload provider did not return a public URL. */
|
|
40
|
-
dataUrl?: string;
|
|
41
|
-
originalName: string;
|
|
42
|
-
filename: string;
|
|
43
|
-
type: string;
|
|
44
|
-
size: number;
|
|
45
|
-
}
|
|
32
|
+
export type { UploadedFile } from "@/lib/prompt-file-uploads";
|
|
46
33
|
|
|
47
34
|
export interface PromptChatAttachment {
|
|
48
35
|
type: "file";
|
|
@@ -51,52 +38,6 @@ export interface PromptChatAttachment {
|
|
|
51
38
|
displayOnly: true;
|
|
52
39
|
text?: string;
|
|
53
40
|
}
|
|
54
|
-
export async function addInlineImageFallbacks(
|
|
55
|
-
files: File[],
|
|
56
|
-
uploaded: UploadedFile[],
|
|
57
|
-
): Promise<UploadedFile[]> {
|
|
58
|
-
const inlineDataUrls: string[] = [];
|
|
59
|
-
const result: UploadedFile[] = [];
|
|
60
|
-
for (let index = 0; index < uploaded.length; index++) {
|
|
61
|
-
const uploadedFile = uploaded[index];
|
|
62
|
-
const file = files[index];
|
|
63
|
-
const isImage =
|
|
64
|
-
uploadedFile.type.startsWith("image/") ||
|
|
65
|
-
Boolean(file?.type.startsWith("image/"));
|
|
66
|
-
if (!isImage || !file) {
|
|
67
|
-
result.push(uploadedFile);
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (uploadedFile.url) {
|
|
71
|
-
const { dataUrl: _dataUrl, ...withoutDataUrl } = uploadedFile;
|
|
72
|
-
result.push(withoutDataUrl);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (uploadedFile.dataUrl) {
|
|
76
|
-
if (canAddInlineImageToPayload(inlineDataUrls, uploadedFile.dataUrl)) {
|
|
77
|
-
inlineDataUrls.push(uploadedFile.dataUrl);
|
|
78
|
-
result.push(uploadedFile);
|
|
79
|
-
} else {
|
|
80
|
-
const { dataUrl: _dataUrl, ...withoutDataUrl } = uploadedFile;
|
|
81
|
-
result.push(withoutDataUrl);
|
|
82
|
-
}
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (!canInlineImageFile(file)) {
|
|
86
|
-
result.push(uploadedFile);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const dataUrl = await readFileAsDataUrl(file);
|
|
90
|
-
if (canAddInlineImageToPayload(inlineDataUrls, dataUrl)) {
|
|
91
|
-
inlineDataUrls.push(dataUrl);
|
|
92
|
-
result.push({ ...uploadedFile, dataUrl });
|
|
93
|
-
} else {
|
|
94
|
-
result.push(uploadedFile);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return result;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
41
|
export async function createPromptChatAttachments(
|
|
101
42
|
attachments: ReadonlyArray<unknown> | undefined,
|
|
102
43
|
uploaded: UploadedFile[],
|
|
@@ -158,224 +99,6 @@ export async function createPromptChatAttachments(
|
|
|
158
99
|
return result;
|
|
159
100
|
}
|
|
160
101
|
|
|
161
|
-
// Netlify functions cap request bodies well under what a real PPTX/PDF
|
|
162
|
-
// needs, so any file above this size streams through the chunked upload
|
|
163
|
-
// endpoints (sub-4 MB slices, reassembled server-side) instead of one
|
|
164
|
-
// multipart POST.
|
|
165
|
-
const CHUNK_UPLOAD_THRESHOLD_BYTES = 4 * 1024 * 1024;
|
|
166
|
-
const CHUNK_SIZE_BYTES = 4 * 1024 * 1024;
|
|
167
|
-
|
|
168
|
-
async function readUploadJson(response: Response): Promise<unknown> {
|
|
169
|
-
try {
|
|
170
|
-
return await response.json();
|
|
171
|
-
} catch (error) {
|
|
172
|
-
throw new Error(`Upload returned invalid JSON (${response.status})`, {
|
|
173
|
-
cause: error,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function extractErrorMessage(data: unknown): string | null {
|
|
179
|
-
if (
|
|
180
|
-
data &&
|
|
181
|
-
typeof data === "object" &&
|
|
182
|
-
"error" in data &&
|
|
183
|
-
typeof (data as { error: unknown }).error === "string" &&
|
|
184
|
-
(data as { error: string }).error.trim()
|
|
185
|
-
) {
|
|
186
|
-
return (data as { error: string }).error;
|
|
187
|
-
}
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function uploadFilesMultipart(files: File[]): Promise<UploadedFile[]> {
|
|
192
|
-
const formData = new FormData();
|
|
193
|
-
files.forEach((file) => formData.append("files", file));
|
|
194
|
-
const response = await fetch(`${appBasePath()}/api/uploads`, {
|
|
195
|
-
method: "POST",
|
|
196
|
-
body: formData,
|
|
197
|
-
credentials: "include",
|
|
198
|
-
});
|
|
199
|
-
const data = await readUploadJson(response);
|
|
200
|
-
if (!response.ok) {
|
|
201
|
-
throw new Error(
|
|
202
|
-
extractErrorMessage(data) || `Upload failed (${response.status})`,
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
if (!Array.isArray(data)) {
|
|
206
|
-
throw new Error("Upload failed: invalid response");
|
|
207
|
-
}
|
|
208
|
-
return data as UploadedFile[];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async function deleteUploadedPromptFile(file: UploadedFile): Promise<void> {
|
|
212
|
-
const response = await fetch(`${appBasePath()}/api/uploads`, {
|
|
213
|
-
method: "DELETE",
|
|
214
|
-
headers: { "content-type": "application/json" },
|
|
215
|
-
credentials: "include",
|
|
216
|
-
body: JSON.stringify({ path: file.path }),
|
|
217
|
-
});
|
|
218
|
-
if (!response.ok) {
|
|
219
|
-
throw new Error(`Upload cleanup failed (${response.status})`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
async function cleanupUploadedPromptFiles(files: UploadedFile[]) {
|
|
224
|
-
const results = await Promise.allSettled(
|
|
225
|
-
files.map((file) => deleteUploadedPromptFile(file)),
|
|
226
|
-
);
|
|
227
|
-
results.forEach((result) => {
|
|
228
|
-
if (result.status === "rejected") {
|
|
229
|
-
console.error("Eager upload cleanup failed", result.reason);
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function uploadFileChunked(file: File): Promise<UploadedFile> {
|
|
235
|
-
const startResponse = await fetch(
|
|
236
|
-
`${appBasePath()}/api/uploads-chunked/start`,
|
|
237
|
-
{
|
|
238
|
-
method: "POST",
|
|
239
|
-
headers: { "Content-Type": "application/json" },
|
|
240
|
-
credentials: "include",
|
|
241
|
-
body: JSON.stringify({
|
|
242
|
-
filename: file.name,
|
|
243
|
-
mimetype: file.type || "application/octet-stream",
|
|
244
|
-
declaredSize: file.size,
|
|
245
|
-
}),
|
|
246
|
-
},
|
|
247
|
-
);
|
|
248
|
-
const startData = await readUploadJson(startResponse);
|
|
249
|
-
if (!startResponse.ok) {
|
|
250
|
-
throw new Error(
|
|
251
|
-
extractErrorMessage(startData) ||
|
|
252
|
-
`Upload failed (${startResponse.status})`,
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
if (
|
|
256
|
-
startData &&
|
|
257
|
-
typeof startData === "object" &&
|
|
258
|
-
(startData as { uploadMode?: unknown }).uploadMode === "multipart"
|
|
259
|
-
) {
|
|
260
|
-
const [uploaded] = await uploadFilesMultipart([file]);
|
|
261
|
-
if (!uploaded) throw new Error("Upload failed: no file returned");
|
|
262
|
-
return uploaded;
|
|
263
|
-
}
|
|
264
|
-
const sessionId =
|
|
265
|
-
startData && typeof startData === "object"
|
|
266
|
-
? (startData as { sessionId?: unknown }).sessionId
|
|
267
|
-
: undefined;
|
|
268
|
-
if (typeof sessionId !== "string" || !sessionId) {
|
|
269
|
-
throw new Error("Upload failed: session ID missing");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const totalChunks = Math.max(1, Math.ceil(file.size / CHUNK_SIZE_BYTES));
|
|
273
|
-
for (let index = 0; index < totalChunks; index++) {
|
|
274
|
-
const start = index * CHUNK_SIZE_BYTES;
|
|
275
|
-
const end = Math.min(start + CHUNK_SIZE_BYTES, file.size);
|
|
276
|
-
const isFinal = index === totalChunks - 1;
|
|
277
|
-
const chunkResponse = await fetch(
|
|
278
|
-
`${appBasePath()}/api/uploads-chunked/${sessionId}/chunk?index=${index}&isFinal=${
|
|
279
|
-
isFinal ? "1" : "0"
|
|
280
|
-
}`,
|
|
281
|
-
{
|
|
282
|
-
method: "POST",
|
|
283
|
-
credentials: "include",
|
|
284
|
-
headers: { "Content-Type": "application/octet-stream" },
|
|
285
|
-
body: file.slice(start, end),
|
|
286
|
-
},
|
|
287
|
-
);
|
|
288
|
-
const chunkData = await readUploadJson(chunkResponse);
|
|
289
|
-
if (!chunkResponse.ok) {
|
|
290
|
-
throw new Error(
|
|
291
|
-
extractErrorMessage(chunkData) ||
|
|
292
|
-
`Upload failed (${chunkResponse.status})`,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
if (isFinal) {
|
|
296
|
-
const result = Array.isArray(chunkData)
|
|
297
|
-
? (chunkData[0] as UploadedFile)
|
|
298
|
-
: undefined;
|
|
299
|
-
if (!result) throw new Error("Upload failed: no file returned");
|
|
300
|
-
return result;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
throw new Error("Upload failed: no final chunk response");
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
export async function uploadPromptFiles(
|
|
307
|
-
files: File[],
|
|
308
|
-
): Promise<UploadedFile[]> {
|
|
309
|
-
if (files.length === 0) return [];
|
|
310
|
-
if (files.length > MAX_REFERENCE_FILES) {
|
|
311
|
-
throw new Error(`Too many files (max ${MAX_REFERENCE_FILES})`);
|
|
312
|
-
}
|
|
313
|
-
ensureEmbedAuthFetchInterceptor();
|
|
314
|
-
const smallIndices = files.flatMap((file, index) =>
|
|
315
|
-
file.size <= CHUNK_UPLOAD_THRESHOLD_BYTES ? [index] : [],
|
|
316
|
-
);
|
|
317
|
-
const largeIndices = files.flatMap((file, index) =>
|
|
318
|
-
file.size > CHUNK_UPLOAD_THRESHOLD_BYTES ? [index] : [],
|
|
319
|
-
);
|
|
320
|
-
const smallPromise =
|
|
321
|
-
smallIndices.length > 0
|
|
322
|
-
? uploadFilesMultipart(smallIndices.map((index) => files[index]))
|
|
323
|
-
: Promise.resolve([] as UploadedFile[]);
|
|
324
|
-
const [smallResult, largeResults] = await Promise.all([
|
|
325
|
-
smallPromise.then(
|
|
326
|
-
(value) => ({ status: "fulfilled" as const, value }),
|
|
327
|
-
(reason) => ({ status: "rejected" as const, reason }),
|
|
328
|
-
),
|
|
329
|
-
Promise.allSettled(
|
|
330
|
-
largeIndices.map((index) => uploadFileChunked(files[index])),
|
|
331
|
-
),
|
|
332
|
-
]);
|
|
333
|
-
const successfulLargeUploads = largeResults.flatMap((result) =>
|
|
334
|
-
result.status === "fulfilled" ? [result.value] : [],
|
|
335
|
-
);
|
|
336
|
-
const failedLargeResult = largeResults.find(
|
|
337
|
-
(result) => result.status === "rejected",
|
|
338
|
-
);
|
|
339
|
-
if (smallResult.status === "rejected") {
|
|
340
|
-
await cleanupUploadedPromptFiles([...successfulLargeUploads]);
|
|
341
|
-
throw smallResult.reason;
|
|
342
|
-
}
|
|
343
|
-
if (failedLargeResult) {
|
|
344
|
-
await cleanupUploadedPromptFiles([
|
|
345
|
-
...smallResult.value,
|
|
346
|
-
...successfulLargeUploads,
|
|
347
|
-
]);
|
|
348
|
-
throw failedLargeResult.reason;
|
|
349
|
-
}
|
|
350
|
-
const smallUploads = smallResult.value;
|
|
351
|
-
const largeUploads = successfulLargeUploads;
|
|
352
|
-
if (smallUploads.length !== smallIndices.length) {
|
|
353
|
-
throw new Error("Upload failed: response file count did not match request");
|
|
354
|
-
}
|
|
355
|
-
const uploads = new Array<UploadedFile>(files.length);
|
|
356
|
-
smallIndices.forEach((fileIndex, resultIndex) => {
|
|
357
|
-
uploads[fileIndex] = smallUploads[resultIndex];
|
|
358
|
-
});
|
|
359
|
-
largeIndices.forEach((fileIndex, resultIndex) => {
|
|
360
|
-
uploads[fileIndex] = largeUploads[resultIndex];
|
|
361
|
-
});
|
|
362
|
-
return addInlineImageFallbacks(files, uploads);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Radix popovers portal to `document.body`, so a mousedown inside the model
|
|
367
|
-
* picker or attachment menu reads as "outside" any panel that hosts a composer.
|
|
368
|
-
* Closing on it unmounts the popover before its own click fires, which looks
|
|
369
|
-
* exactly like a dead button.
|
|
370
|
-
*/
|
|
371
|
-
export function isInsidePortaledLayer(target: EventTarget | null): boolean {
|
|
372
|
-
return Boolean(
|
|
373
|
-
(target as Element | null)?.closest?.(
|
|
374
|
-
"[data-radix-popper-content-wrapper]",
|
|
375
|
-
),
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
102
|
export type PromptImportSource = "pdf" | "pptx" | "google-slides";
|
|
380
103
|
|
|
381
104
|
export type PromptImportSelection =
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { lazy } from "react";
|
|
2
|
+
|
|
3
|
+
let addSlidePopoverModule:
|
|
4
|
+
| Promise<typeof import("@/components/editor/AddSlidePopover")>
|
|
5
|
+
| undefined;
|
|
6
|
+
|
|
7
|
+
function loadAddSlidePopover() {
|
|
8
|
+
addSlidePopoverModule ??= import("@/components/editor/AddSlidePopover").catch(
|
|
9
|
+
(error) => {
|
|
10
|
+
addSlidePopoverModule = undefined;
|
|
11
|
+
throw error;
|
|
12
|
+
},
|
|
13
|
+
);
|
|
14
|
+
return addSlidePopoverModule;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function preloadAddSlidePopover() {
|
|
18
|
+
// Hover/focus loads are speculative; the opened lazy render owns recovery.
|
|
19
|
+
void loadAddSlidePopover().catch(() => {});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const DeferredAddSlidePopover = lazy(async () => {
|
|
23
|
+
const module = await loadAddSlidePopover();
|
|
24
|
+
return { default: module.AddSlidePopover };
|
|
25
|
+
});
|