@agent-native/core 0.124.2 → 0.124.4
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/README.md +1 -1
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +16 -0
- package/corpus/core/docs/content/cloneable-saas.mdx +9 -9
- package/corpus/core/docs/content/creating-templates.mdx +2 -2
- package/corpus/core/docs/content/deployment.mdx +23 -0
- package/corpus/core/docs/content/faq.mdx +4 -4
- package/corpus/core/docs/content/key-concepts.mdx +1 -1
- package/corpus/core/docs/content/multi-app-workspace.mdx +1 -1
- package/corpus/core/docs/content/syncing-template-changes.mdx +1 -1
- package/corpus/core/docs/content/template-analytics-developers.mdx +2 -2
- package/corpus/core/docs/content/template-analytics.mdx +2 -2
- package/corpus/core/docs/content/template-assets-developers.mdx +3 -3
- package/corpus/core/docs/content/template-assets.mdx +2 -2
- package/corpus/core/docs/content/template-brain-developers.mdx +2 -2
- package/corpus/core/docs/content/template-brain.mdx +1 -1
- package/corpus/core/docs/content/template-calendar-developers.mdx +1 -1
- package/corpus/core/docs/content/template-calendar.mdx +1 -1
- package/corpus/core/docs/content/template-chat.mdx +1 -1
- package/corpus/core/docs/content/template-clips-developers.mdx +4 -4
- package/corpus/core/docs/content/template-clips.mdx +2 -2
- package/corpus/core/docs/content/template-content-developers.mdx +2 -2
- package/corpus/core/docs/content/template-content.mdx +1 -1
- package/corpus/core/docs/content/template-design-brand-and-figma.mdx +1 -1
- package/corpus/core/docs/content/template-design-developers.mdx +2 -2
- package/corpus/core/docs/content/template-design.mdx +2 -2
- package/corpus/core/docs/content/template-dispatch-developers.mdx +5 -5
- package/corpus/core/docs/content/template-forms-developers.mdx +1 -1
- package/corpus/core/docs/content/template-forms.mdx +1 -1
- package/corpus/core/docs/content/template-mail-developers.mdx +2 -2
- package/corpus/core/docs/content/template-mail.mdx +2 -2
- package/corpus/core/docs/content/template-plan-developers.mdx +3 -3
- package/corpus/core/docs/content/template-plan.mdx +3 -3
- package/corpus/core/docs/content/template-slides-developers.mdx +4 -4
- package/corpus/core/docs/content/template-slides.mdx +1 -1
- package/corpus/core/docs/content/what-is-agent-native.mdx +5 -5
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/auth-policy.ts +1 -1
- package/corpus/core/src/agent/production-agent.ts +14 -1
- package/corpus/core/src/agent/run-manager.ts +6 -13
- package/corpus/core/src/cli/index.ts +11 -1
- package/corpus/core/src/client/route-chunk-recovery.ts +18 -7
- package/corpus/core/src/db/client.ts +7 -6
- package/corpus/core/src/db/migrations.ts +2 -1
- package/corpus/core/src/deploy/build.ts +290 -4
- package/corpus/core/src/deploy/nitro-preset.ts +50 -0
- package/corpus/core/src/integrations/pending-tasks-retry-job.ts +2 -1
- package/corpus/core/src/scripts/call-agent.ts +4 -2
- package/corpus/core/src/server/agent-discovery.ts +1 -0
- package/corpus/core/src/server/agent-run-context.ts +13 -3
- package/corpus/core/src/server/request-context.ts +2 -0
- package/corpus/core/src/shared/cloudflare-globals.d.ts +2 -1
- package/corpus/core/src/shared/runtime.ts +2 -0
- package/corpus/core/src/shared/streaming-text-smoothing.ts +15 -14
- package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +29 -0
- package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/de-DE.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/en-US.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/es-ES.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +6 -6
- package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +6 -6
- package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
- package/corpus/core/src/vite/client.ts +20 -1
- package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +9 -2
- package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +3 -0
- package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -1
- package/corpus/templates/analytics/app/global.css +10 -9
- package/corpus/templates/analytics/app/hooks/use-chart-tooltip-portal.ts +59 -18
- package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +14 -2
- package/corpus/templates/analytics/app/i18n-data.ts +102 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +232 -2
- package/corpus/templates/analytics/changelog/2026-07-27-chart-tooltips-stay-beside-the-cursor-when-they-extend-beyon.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-27-chat-no-longer-claims-a-data-source-is-disconnected-when-it-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-27-dashboard-changes-can-be-undone-with-cmd-z-and-actions-menu.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-27-dashboard-redo-with-cmd-shift-z-and-actions-menu.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-27-dashboard-skeletons-are-subtler-and-smoother.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +61 -20
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -3
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +7 -32
- package/corpus/templates/calendar/changelog/2026-07-27-calendar-s-sidebar-now-uses-a-cleaner-layout-with-fewer-util.md +6 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +14 -6
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +32 -37
- package/corpus/templates/content/changelog/2026-07-27-content-sidebar-page-lists-now-load-with-quiet-skeletons-and.md +6 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +34 -11
- package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +8 -0
- package/corpus/templates/design/actions/generate-design.ts +259 -36
- package/corpus/templates/design/actions/generate-screens.ts +51 -14
- package/corpus/templates/design/actions/present-design-variants.ts +6 -3
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +8 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +158 -9
- package/corpus/templates/design/app/components/design/design-canvas/content-size-report.ts +124 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +61 -9
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +16 -18
- package/corpus/templates/design/changelog/2026-07-24-ai-designs-now-default-to-a-full-size-desktop-mobile-pair-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-24-design-frames-now-fill-the-viewport-height-and-grow-to-fit-t.md +6 -0
- package/corpus/templates/design/changelog/2026-07-24-generating-an-additional-screen-now-places-it-beside-the-exi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-27-collapsed-sidebar-keeps-the-agent-native-logo.md +6 -0
- package/corpus/templates/dispatch/AGENTS.md +7 -2
- package/corpus/templates/dispatch/actions/view-screen.ts +3 -2
- package/corpus/templates/dispatch/changelog/2026-07-17-curated-template-remixes.md +1 -1
- package/corpus/templates/dispatch/changelog/2026-07-27-creating-an-app-from-a-curated-template-now-uses-clearer-mor.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-27-thread-debug-accepts-request-run-ids.md +6 -0
- package/corpus/templates/slides/actions/create-design-system.ts +10 -2
- package/corpus/templates/slides/actions/import-file.ts +173 -1
- package/corpus/templates/slides/actions/list-slide-comments.ts +16 -1
- package/corpus/templates/slides/actions/update-design-system.ts +13 -2
- package/corpus/templates/slides/app/components/editor/AssetLibraryPanel.tsx +35 -12
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +36 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +3 -0
- package/corpus/templates/slides/app/lib/dynamic-import.ts +63 -0
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +3 -2
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +5 -2
- package/corpus/templates/slides/app/pages/DesignSystems.tsx +9 -1
- package/corpus/templates/slides/server/db/schema.ts +11 -0
- package/corpus/templates/slides/server/handlers/assets.ts +64 -19
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +38 -0
- package/corpus/templates/slides/server/plugins/db.ts +20 -0
- package/corpus/templates/slides/shared/design-system-validation.ts +62 -0
- package/dist/a2a/auth-policy.js +1 -1
- package/dist/a2a/auth-policy.js.map +1 -1
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +12 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +2 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +4 -10
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/cli/index.js +8 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/client/route-chunk-recovery.d.ts.map +1 -1
- package/dist/client/route-chunk-recovery.js +16 -7
- package/dist/client/route-chunk-recovery.js.map +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +4 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +2 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/deploy/build.d.ts +25 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +227 -4
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/nitro-preset.d.ts +8 -0
- package/dist/deploy/nitro-preset.d.ts.map +1 -0
- package/dist/deploy/nitro-preset.js +36 -0
- package/dist/deploy/nitro-preset.js.map +1 -0
- package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-retry-job.js +2 -1
- package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +4 -2
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-discovery.d.ts.map +1 -1
- package/dist/server/agent-discovery.js +1 -0
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/agent-run-context.d.ts.map +1 -1
- package/dist/server/agent-run-context.js +10 -3
- package/dist/server/agent-run-context.js.map +1 -1
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/runtime.d.ts.map +1 -1
- package/dist/shared/runtime.js +2 -0
- package/dist/shared/runtime.js.map +1 -1
- package/dist/shared/streaming-text-smoothing.d.ts.map +1 -1
- package/dist/shared/streaming-text-smoothing.js +15 -8
- package/dist/shared/streaming-text-smoothing.js.map +1 -1
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +29 -0
- package/dist/templates/default/app/i18n/ar-SA.ts +6 -6
- package/dist/templates/default/app/i18n/de-DE.ts +6 -6
- package/dist/templates/default/app/i18n/en-US.ts +6 -6
- package/dist/templates/default/app/i18n/es-ES.ts +6 -6
- package/dist/templates/default/app/i18n/fr-FR.ts +6 -6
- package/dist/templates/default/app/i18n/hi-IN.ts +6 -6
- package/dist/templates/default/app/i18n/ja-JP.ts +6 -6
- package/dist/templates/default/app/i18n/ko-KR.ts +6 -6
- package/dist/templates/default/app/i18n/pt-BR.ts +6 -6
- package/dist/templates/default/app/i18n/zh-CN.ts +6 -6
- package/dist/templates/default/app/i18n/zh-TW.ts +6 -6
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
- package/dist/vite/client.d.ts +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +16 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/cloneable-saas.mdx +9 -9
- package/docs/content/creating-templates.mdx +2 -2
- package/docs/content/deployment.mdx +23 -0
- package/docs/content/faq.mdx +4 -4
- package/docs/content/key-concepts.mdx +1 -1
- package/docs/content/multi-app-workspace.mdx +1 -1
- package/docs/content/syncing-template-changes.mdx +1 -1
- package/docs/content/template-analytics-developers.mdx +2 -2
- package/docs/content/template-analytics.mdx +2 -2
- package/docs/content/template-assets-developers.mdx +3 -3
- package/docs/content/template-assets.mdx +2 -2
- package/docs/content/template-brain-developers.mdx +2 -2
- package/docs/content/template-brain.mdx +1 -1
- package/docs/content/template-calendar-developers.mdx +1 -1
- package/docs/content/template-calendar.mdx +1 -1
- package/docs/content/template-chat.mdx +1 -1
- package/docs/content/template-clips-developers.mdx +4 -4
- package/docs/content/template-clips.mdx +2 -2
- package/docs/content/template-content-developers.mdx +2 -2
- package/docs/content/template-content.mdx +1 -1
- package/docs/content/template-design-brand-and-figma.mdx +1 -1
- package/docs/content/template-design-developers.mdx +2 -2
- package/docs/content/template-design.mdx +2 -2
- package/docs/content/template-dispatch-developers.mdx +5 -5
- package/docs/content/template-forms-developers.mdx +1 -1
- package/docs/content/template-forms.mdx +1 -1
- package/docs/content/template-mail-developers.mdx +2 -2
- package/docs/content/template-mail.mdx +2 -2
- package/docs/content/template-plan-developers.mdx +3 -3
- package/docs/content/template-plan.mdx +3 -3
- package/docs/content/template-slides-developers.mdx +4 -4
- package/docs/content/template-slides.mdx +1 -1
- package/docs/content/what-is-agent-native.mdx +5 -5
- package/package.json +1 -1
- package/src/a2a/auth-policy.ts +1 -1
- package/src/agent/production-agent.ts +14 -1
- package/src/agent/run-manager.ts +6 -13
- package/src/cli/index.ts +11 -1
- package/src/client/route-chunk-recovery.ts +18 -7
- package/src/db/client.ts +7 -6
- package/src/db/migrations.ts +2 -1
- package/src/deploy/build.ts +290 -4
- package/src/deploy/nitro-preset.ts +50 -0
- package/src/integrations/pending-tasks-retry-job.ts +2 -1
- package/src/scripts/call-agent.ts +4 -2
- package/src/server/agent-discovery.ts +1 -0
- package/src/server/agent-run-context.ts +13 -3
- package/src/server/request-context.ts +2 -0
- package/src/shared/cloudflare-globals.d.ts +2 -1
- package/src/shared/runtime.ts +2 -0
- package/src/shared/streaming-text-smoothing.ts +15 -14
- package/src/templates/default/.agents/skills/secrets/SKILL.md +29 -0
- package/src/templates/default/app/i18n/ar-SA.ts +6 -6
- package/src/templates/default/app/i18n/de-DE.ts +6 -6
- package/src/templates/default/app/i18n/en-US.ts +6 -6
- package/src/templates/default/app/i18n/es-ES.ts +6 -6
- package/src/templates/default/app/i18n/fr-FR.ts +6 -6
- package/src/templates/default/app/i18n/hi-IN.ts +6 -6
- package/src/templates/default/app/i18n/ja-JP.ts +6 -6
- package/src/templates/default/app/i18n/ko-KR.ts +6 -6
- package/src/templates/default/app/i18n/pt-BR.ts +6 -6
- package/src/templates/default/app/i18n/zh-CN.ts +6 -6
- package/src/templates/default/app/i18n/zh-TW.ts +6 -6
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +29 -0
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +29 -0
- package/src/vite/client.ts +20 -1
- /package/corpus/templates/slides/server/routes/api/assets/{[filename].delete.ts → [id].delete.ts} +0 -0
|
@@ -8,6 +8,7 @@ import { nanoid } from "nanoid";
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
10
|
import { getDb, schema } from "../server/db/index.js";
|
|
11
|
+
import { missingDesignSystemDataFields } from "../shared/design-system-validation.js";
|
|
11
12
|
|
|
12
13
|
export default defineAction({
|
|
13
14
|
description:
|
|
@@ -36,12 +37,19 @@ export default defineAction({
|
|
|
36
37
|
),
|
|
37
38
|
}),
|
|
38
39
|
run: async ({ title, description, data, assets, customInstructions }) => {
|
|
39
|
-
|
|
40
|
+
let parsedData: unknown;
|
|
40
41
|
try {
|
|
41
|
-
JSON.parse(data);
|
|
42
|
+
parsedData = JSON.parse(data);
|
|
42
43
|
} catch {
|
|
43
44
|
throw new Error("data must be a valid JSON string");
|
|
44
45
|
}
|
|
46
|
+
const missingFields = missingDesignSystemDataFields(parsedData);
|
|
47
|
+
if (missingFields.length > 0) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"data is missing required design system field(s): " +
|
|
50
|
+
missingFields.join(", "),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
45
53
|
if (assets) {
|
|
46
54
|
try {
|
|
47
55
|
JSON.parse(assets);
|
|
@@ -2,6 +2,7 @@ import path from "path";
|
|
|
2
2
|
|
|
3
3
|
import { defineAction } from "@agent-native/core";
|
|
4
4
|
import { writeAppState } from "@agent-native/core/application-state";
|
|
5
|
+
import { uploadFile } from "@agent-native/core/file-upload";
|
|
5
6
|
import { startBuilderDesignSystemIndex } from "@agent-native/core/server";
|
|
6
7
|
import {
|
|
7
8
|
getRequestOrgId,
|
|
@@ -15,6 +16,11 @@ import { getDb, schema } from "../server/db/index.js";
|
|
|
15
16
|
import { notifyClients } from "../server/handlers/decks.js";
|
|
16
17
|
import { upsertBuilderProxyDesignSystem } from "../server/lib/builder-design-system-proxy.js";
|
|
17
18
|
import { setupPdfParse } from "../server/lib/pdf-parse-setup.js";
|
|
19
|
+
import {
|
|
20
|
+
ASPECT_RATIOS,
|
|
21
|
+
DEFAULT_ASPECT_RATIO,
|
|
22
|
+
type AspectRatio,
|
|
23
|
+
} from "../shared/aspect-ratios.js";
|
|
18
24
|
import { readUserUploadedFile } from "./_uploaded-files.js";
|
|
19
25
|
|
|
20
26
|
const DEFAULT_MAX_SOURCE_CHARS = 60_000;
|
|
@@ -213,6 +219,25 @@ export default defineAction({
|
|
|
213
219
|
|
|
214
220
|
if (detectedFormat === "pdf") {
|
|
215
221
|
const { PDFParse, canvasFactory } = await setupPdfParse();
|
|
222
|
+
const title = titleFromPath(filename);
|
|
223
|
+
|
|
224
|
+
// Designed slide PDFs (photo backgrounds, gradients, custom
|
|
225
|
+
// typography) bake their visuals into vector/image page content with
|
|
226
|
+
// no reliable text/shape structure to reconstruct, so importing into a
|
|
227
|
+
// deck rasterizes each page instead of flattening it to generic bullet
|
|
228
|
+
// text. Falls through to the text-only path below when the optional
|
|
229
|
+
// canvas renderer isn't available in this runtime.
|
|
230
|
+
if (importIntoDeck && canvasFactory) {
|
|
231
|
+
if (!deckId) throw new Error("deckId is required to import into deck");
|
|
232
|
+
return importPdfPagesAsFullBleedSlides({
|
|
233
|
+
fileBuffer,
|
|
234
|
+
title,
|
|
235
|
+
deckId,
|
|
236
|
+
PDFParse,
|
|
237
|
+
canvasFactory,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
216
241
|
const { convertSectionsToSlides } =
|
|
217
242
|
await import("../server/handlers/import/html-converter.js");
|
|
218
243
|
const pdf = new PDFParse({
|
|
@@ -222,7 +247,6 @@ export default defineAction({
|
|
|
222
247
|
const result = await pdf.getText().finally(() => pdf.destroy());
|
|
223
248
|
const pages = normalizePdfPages(result);
|
|
224
249
|
const textPages = pages.filter((p) => p.text.trim());
|
|
225
|
-
const title = titleFromPath(filename);
|
|
226
250
|
|
|
227
251
|
if (textPages.length === 0) {
|
|
228
252
|
throw new Error(
|
|
@@ -278,6 +302,152 @@ export default defineAction({
|
|
|
278
302
|
},
|
|
279
303
|
});
|
|
280
304
|
|
|
305
|
+
/** Closest configured deck aspect ratio to a source image's own dimensions. */
|
|
306
|
+
function nearestAspectRatio(width: number, height: number): AspectRatio {
|
|
307
|
+
const target = width / height;
|
|
308
|
+
let best: AspectRatio = DEFAULT_ASPECT_RATIO;
|
|
309
|
+
let bestDiff = Infinity;
|
|
310
|
+
for (const key of Object.keys(ASPECT_RATIOS) as AspectRatio[]) {
|
|
311
|
+
const preset = ASPECT_RATIOS[key];
|
|
312
|
+
const diff = Math.abs(preset.width / preset.height - target);
|
|
313
|
+
if (diff < bestDiff) {
|
|
314
|
+
bestDiff = diff;
|
|
315
|
+
best = key;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return best;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function importPdfPagesAsFullBleedSlides(args: {
|
|
322
|
+
fileBuffer: Buffer;
|
|
323
|
+
title: string;
|
|
324
|
+
deckId: string;
|
|
325
|
+
PDFParse: Awaited<ReturnType<typeof setupPdfParse>>["PDFParse"];
|
|
326
|
+
canvasFactory: object;
|
|
327
|
+
}) {
|
|
328
|
+
const { fileBuffer, title, deckId, PDFParse, canvasFactory } = args;
|
|
329
|
+
const { buildFullBleedImageSlideHtml, convertSectionsToSlides } =
|
|
330
|
+
await import("../server/handlers/import/html-converter.js");
|
|
331
|
+
|
|
332
|
+
const pdf = new PDFParse({
|
|
333
|
+
data: new Uint8Array(fileBuffer),
|
|
334
|
+
CanvasFactory: canvasFactory,
|
|
335
|
+
});
|
|
336
|
+
let pages: { num: number; text: string }[];
|
|
337
|
+
let imagesByPage: Map<
|
|
338
|
+
number,
|
|
339
|
+
{ data: Uint8Array; width: number; height: number }
|
|
340
|
+
>;
|
|
341
|
+
try {
|
|
342
|
+
pages = normalizePdfPages(await pdf.getText());
|
|
343
|
+
// Reuse the page's own embedded photo rather than rasterizing the whole
|
|
344
|
+
// page: headless canvas text rendering depends on the PDF's embedded
|
|
345
|
+
// fonts resolving in this runtime, which is unreliable, so real
|
|
346
|
+
// extracted text is drawn as HTML below instead of trusting the render.
|
|
347
|
+
const imageResult = await pdf.getImage({
|
|
348
|
+
imageBuffer: true,
|
|
349
|
+
imageDataUrl: false,
|
|
350
|
+
});
|
|
351
|
+
imagesByPage = new Map();
|
|
352
|
+
for (const page of imageResult.pages) {
|
|
353
|
+
const largest = page.images.reduce<
|
|
354
|
+
{ data: Uint8Array; width: number; height: number } | undefined
|
|
355
|
+
>((best, image) => {
|
|
356
|
+
if (!best || image.width * image.height > best.width * best.height) {
|
|
357
|
+
return { data: image.data, width: image.width, height: image.height };
|
|
358
|
+
}
|
|
359
|
+
return best;
|
|
360
|
+
}, undefined);
|
|
361
|
+
if (largest) imagesByPage.set(page.pageNumber, largest);
|
|
362
|
+
}
|
|
363
|
+
} finally {
|
|
364
|
+
await pdf.destroy();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Source decks (e.g. Instagram carousel exports) are commonly portrait or
|
|
368
|
+
// square, not the deck editor's 16:9 default — match the canvas to the
|
|
369
|
+
// first embedded photo's own proportions instead of stretching/cropping it.
|
|
370
|
+
const firstImage = pages
|
|
371
|
+
.map((page) => imagesByPage.get(page.num))
|
|
372
|
+
.find((image): image is NonNullable<typeof image> => Boolean(image));
|
|
373
|
+
const aspectRatio = firstImage
|
|
374
|
+
? nearestAspectRatio(firstImage.width, firstImage.height)
|
|
375
|
+
: undefined;
|
|
376
|
+
|
|
377
|
+
const ownerEmail = getRequestUserEmail();
|
|
378
|
+
const slides = await Promise.all(
|
|
379
|
+
pages.map(async (page) => {
|
|
380
|
+
const lines = page.text
|
|
381
|
+
.split(/\r?\n/)
|
|
382
|
+
.map((line) => line.trim())
|
|
383
|
+
.filter(Boolean);
|
|
384
|
+
const backgroundImage = imagesByPage.get(page.num);
|
|
385
|
+
|
|
386
|
+
if (!backgroundImage) {
|
|
387
|
+
const [content] = convertSectionsToSlides([
|
|
388
|
+
{ heading: lines[0] ?? `Page ${page.num}`, content: page.text },
|
|
389
|
+
]);
|
|
390
|
+
return {
|
|
391
|
+
id: newSlideId(),
|
|
392
|
+
content: content ?? '<div class="fmd-slide"></div>',
|
|
393
|
+
layout: "content",
|
|
394
|
+
notes: "",
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const uploadResult = await uploadFile({
|
|
399
|
+
data: Buffer.from(backgroundImage.data),
|
|
400
|
+
filename: `slide-import-${Date.now()}-p${page.num}.png`,
|
|
401
|
+
mimeType: "image/png",
|
|
402
|
+
ownerEmail: ownerEmail ?? undefined,
|
|
403
|
+
recordAsset: false,
|
|
404
|
+
});
|
|
405
|
+
if (!uploadResult?.url) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
"File storage is not configured. Connect Builder.io or another upload provider before importing PDF slides.",
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
// pdf-parse breaks wrapped text across lines with no way to tell a
|
|
411
|
+
// wrapped title from a heading followed by a body paragraph. Treat a
|
|
412
|
+
// couple of short lines as one wrapped title (join them so the
|
|
413
|
+
// sentence isn't cut off); three or more lines as a heading plus body
|
|
414
|
+
// text, rendered with distinct sizes so they don't collapse into one
|
|
415
|
+
// flat paragraph.
|
|
416
|
+
const heading =
|
|
417
|
+
lines.length > 2 ? lines[0] : lines.join(" ") || undefined;
|
|
418
|
+
const subtitle = lines.length > 2 ? lines.slice(1).join(" ") : undefined;
|
|
419
|
+
return {
|
|
420
|
+
id: newSlideId(),
|
|
421
|
+
content: buildFullBleedImageSlideHtml(
|
|
422
|
+
uploadResult.url,
|
|
423
|
+
heading,
|
|
424
|
+
subtitle,
|
|
425
|
+
),
|
|
426
|
+
layout: "full-image",
|
|
427
|
+
notes: page.text,
|
|
428
|
+
};
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
await replaceDeckSlides(
|
|
433
|
+
deckId,
|
|
434
|
+
title,
|
|
435
|
+
slides,
|
|
436
|
+
"import-file:pdf",
|
|
437
|
+
aspectRatio,
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
format: "pdf",
|
|
442
|
+
title,
|
|
443
|
+
pageCount: slides.length,
|
|
444
|
+
slideCount: slides.length,
|
|
445
|
+
aspectRatio,
|
|
446
|
+
deckId,
|
|
447
|
+
imported: true,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
281
451
|
function newSlideId(): string {
|
|
282
452
|
return `slide-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
283
453
|
}
|
|
@@ -367,6 +537,7 @@ async function replaceDeckSlides(
|
|
|
367
537
|
notes?: string;
|
|
368
538
|
}>,
|
|
369
539
|
source: string,
|
|
540
|
+
aspectRatio?: AspectRatio,
|
|
370
541
|
) {
|
|
371
542
|
await assertAccess("deck", deckId, "editor");
|
|
372
543
|
|
|
@@ -387,6 +558,7 @@ async function replaceDeckSlides(
|
|
|
387
558
|
...previousData,
|
|
388
559
|
title,
|
|
389
560
|
slides,
|
|
561
|
+
...(aspectRatio ? { aspectRatio } : {}),
|
|
390
562
|
updatedAt: now,
|
|
391
563
|
};
|
|
392
564
|
|
|
@@ -27,6 +27,21 @@ export default defineAction({
|
|
|
27
27
|
),
|
|
28
28
|
)
|
|
29
29
|
.orderBy(asc(schema.slideComments.createdAt));
|
|
30
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
comments: rows.map((row) => ({
|
|
32
|
+
id: row.id,
|
|
33
|
+
deck_id: row.deckId,
|
|
34
|
+
slide_id: row.slideId,
|
|
35
|
+
thread_id: row.threadId,
|
|
36
|
+
parent_id: row.parentId,
|
|
37
|
+
content: row.content,
|
|
38
|
+
quoted_text: row.quotedText,
|
|
39
|
+
author_email: row.authorEmail,
|
|
40
|
+
author_name: row.authorName,
|
|
41
|
+
resolved: row.resolved,
|
|
42
|
+
created_at: row.createdAt,
|
|
43
|
+
updated_at: row.updatedAt,
|
|
44
|
+
})),
|
|
45
|
+
};
|
|
31
46
|
},
|
|
32
47
|
});
|
|
@@ -4,6 +4,7 @@ import { eq } from "drizzle-orm";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import { missingDesignSystemDataFields } from "../shared/design-system-validation.js";
|
|
7
8
|
|
|
8
9
|
export default defineAction({
|
|
9
10
|
description:
|
|
@@ -29,13 +30,23 @@ export default defineAction({
|
|
|
29
30
|
),
|
|
30
31
|
}),
|
|
31
32
|
run: async ({ id, title, description, data, assets, customInstructions }) => {
|
|
32
|
-
// Validate that data/assets are valid JSON
|
|
33
|
+
// Validate that data/assets are valid JSON, and that data has the shape
|
|
34
|
+
// the Design Systems page and slide renderers read unconditionally
|
|
35
|
+
// (data.colors.*, data.typography.*) — see create-design-system.ts.
|
|
33
36
|
if (data !== undefined) {
|
|
37
|
+
let parsedData: unknown;
|
|
34
38
|
try {
|
|
35
|
-
JSON.parse(data);
|
|
39
|
+
parsedData = JSON.parse(data);
|
|
36
40
|
} catch {
|
|
37
41
|
throw new Error("data must be a valid JSON string");
|
|
38
42
|
}
|
|
43
|
+
const missingFields = missingDesignSystemDataFields(parsedData);
|
|
44
|
+
if (missingFields.length > 0) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"data is missing required design system field(s): " +
|
|
47
|
+
missingFields.join(", "),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
39
50
|
}
|
|
40
51
|
if (assets !== undefined) {
|
|
41
52
|
try {
|
|
@@ -3,8 +3,10 @@ import { useT } from "@agent-native/core/client/i18n";
|
|
|
3
3
|
import { IconUpload, IconTrash, IconLoader2, IconX } from "@tabler/icons-react";
|
|
4
4
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
|
+
import { toast } from "sonner";
|
|
6
7
|
|
|
7
8
|
interface Asset {
|
|
9
|
+
id: string;
|
|
8
10
|
url: string;
|
|
9
11
|
filename: string;
|
|
10
12
|
size: number;
|
|
@@ -67,33 +69,54 @@ export default function AssetLibraryPanel({
|
|
|
67
69
|
const files = e.target.files;
|
|
68
70
|
if (!files || files.length === 0) return;
|
|
69
71
|
setUploading(true);
|
|
72
|
+
const failures: string[] = [];
|
|
70
73
|
try {
|
|
71
74
|
for (const file of Array.from(files)) {
|
|
72
75
|
const form = new FormData();
|
|
73
76
|
form.append("file", file);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
try {
|
|
78
|
+
const res = await fetch(`${appBasePath()}/api/assets/upload`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
body: form,
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
const body = await res.json().catch(() => null);
|
|
84
|
+
failures.push(
|
|
85
|
+
`${file.name}: ${body?.error || `HTTP ${res.status}`}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
failures.push(`${file.name}: upload failed`);
|
|
90
|
+
}
|
|
78
91
|
}
|
|
79
92
|
await fetchAssets();
|
|
80
|
-
|
|
93
|
+
if (failures.length > 0) {
|
|
94
|
+
toast.error(t("raw.assetUploadFailed"), {
|
|
95
|
+
description: failures.join("\n"),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
81
98
|
} finally {
|
|
82
99
|
setUploading(false);
|
|
83
100
|
e.target.value = "";
|
|
84
101
|
}
|
|
85
102
|
};
|
|
86
103
|
|
|
87
|
-
const handleDelete = async (
|
|
104
|
+
const handleDelete = async (id: string) => {
|
|
88
105
|
try {
|
|
89
|
-
await fetch(
|
|
90
|
-
`${appBasePath()}/api/assets/${encodeURIComponent(
|
|
106
|
+
const res = await fetch(
|
|
107
|
+
`${appBasePath()}/api/assets/${encodeURIComponent(id)}`,
|
|
91
108
|
{
|
|
92
109
|
method: "DELETE",
|
|
93
110
|
},
|
|
94
111
|
);
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
toast.error(t("raw.assetDeleteFailed"));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
setAssets((prev) => prev.filter((a) => a.id !== id));
|
|
117
|
+
} catch {
|
|
118
|
+
toast.error(t("raw.assetDeleteFailed"));
|
|
119
|
+
}
|
|
97
120
|
};
|
|
98
121
|
|
|
99
122
|
const handleSelect = (url: string) => {
|
|
@@ -185,7 +208,7 @@ export default function AssetLibraryPanel({
|
|
|
185
208
|
<div className="grid grid-cols-3 gap-2">
|
|
186
209
|
{assets.map((asset) => (
|
|
187
210
|
<div
|
|
188
|
-
key={asset.
|
|
211
|
+
key={asset.id}
|
|
189
212
|
className="group relative aspect-square rounded-md overflow-hidden border border-border bg-muted"
|
|
190
213
|
>
|
|
191
214
|
<button
|
|
@@ -204,7 +227,7 @@ export default function AssetLibraryPanel({
|
|
|
204
227
|
<button
|
|
205
228
|
onClick={(e) => {
|
|
206
229
|
e.stopPropagation();
|
|
207
|
-
handleDelete(asset.
|
|
230
|
+
handleDelete(asset.id);
|
|
208
231
|
}}
|
|
209
232
|
className="absolute top-0.5 right-0.5 w-5 h-5 bg-black/70 text-white/70 hover:text-red-400 flex items-center justify-center rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
|
|
210
233
|
aria-label={`Delete ${asset.filename}`}
|
|
@@ -164,6 +164,16 @@ const backgroundOptions = [
|
|
|
164
164
|
"bg-[#ffffff]",
|
|
165
165
|
];
|
|
166
166
|
|
|
167
|
+
const HEX_COLOR_PATTERN = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
168
|
+
|
|
169
|
+
/** Native <input type="color"> only accepts 3/6-digit hex — fall back to
|
|
170
|
+
* black for gradients or other raw CSS values so the picker still opens. */
|
|
171
|
+
function toColorInputValue(background: string | undefined): string {
|
|
172
|
+
return background && HEX_COLOR_PATTERN.test(background)
|
|
173
|
+
? background
|
|
174
|
+
: "#000000";
|
|
175
|
+
}
|
|
176
|
+
|
|
167
177
|
/** Popover anchored to a button ref */
|
|
168
178
|
function ToolbarPopover({
|
|
169
179
|
open,
|
|
@@ -294,6 +304,9 @@ export default function EditorToolbar({
|
|
|
294
304
|
}, []);
|
|
295
305
|
|
|
296
306
|
const activeAspectRatio: AspectRatio = aspectRatio ?? DEFAULT_ASPECT_RATIO;
|
|
307
|
+
const isCustomBackground =
|
|
308
|
+
!!currentSlide?.background &&
|
|
309
|
+
!backgroundOptions.includes(currentSlide.background);
|
|
297
310
|
const [layoutOpen, setLayoutOpen] = useState(false);
|
|
298
311
|
const layoutRef = useRef<HTMLButtonElement>(null);
|
|
299
312
|
|
|
@@ -544,6 +557,29 @@ export default function EditorToolbar({
|
|
|
544
557
|
}`}
|
|
545
558
|
/>
|
|
546
559
|
))}
|
|
560
|
+
<label
|
|
561
|
+
className={`relative w-10 h-7 rounded-md border cursor-pointer overflow-hidden transition-all ${
|
|
562
|
+
isCustomBackground
|
|
563
|
+
? "border-[#609FF8] ring-1 ring-[#609FF8]/30"
|
|
564
|
+
: "border-border hover:border-foreground/20"
|
|
565
|
+
}`}
|
|
566
|
+
style={{
|
|
567
|
+
background: isCustomBackground
|
|
568
|
+
? currentSlide.background
|
|
569
|
+
: "conic-gradient(from 180deg, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)",
|
|
570
|
+
}}
|
|
571
|
+
title={t("editorToolbar.customColor")}
|
|
572
|
+
>
|
|
573
|
+
<input
|
|
574
|
+
type="color"
|
|
575
|
+
value={toColorInputValue(currentSlide.background)}
|
|
576
|
+
onChange={(e) => {
|
|
577
|
+
onUpdateSlide!({ background: e.target.value });
|
|
578
|
+
}}
|
|
579
|
+
className="absolute inset-0 h-full w-full cursor-pointer opacity-0"
|
|
580
|
+
aria-label={t("editorToolbar.customColor")}
|
|
581
|
+
/>
|
|
582
|
+
</label>
|
|
547
583
|
</div>
|
|
548
584
|
</div>
|
|
549
585
|
|
|
@@ -61,6 +61,8 @@ const messages = {
|
|
|
61
61
|
deckOptions: "Deck options",
|
|
62
62
|
generatingImage: "Generating image...",
|
|
63
63
|
noAssetsYet: "No assets yet.",
|
|
64
|
+
assetUploadFailed: "Some uploads failed",
|
|
65
|
+
assetDeleteFailed: "Failed to delete asset",
|
|
64
66
|
contentMarkdown: "Content (Markdown)",
|
|
65
67
|
speakerNotes: "Speaker Notes",
|
|
66
68
|
speakerNotesForSlide: "Speaker Notes — Slide {{index}} / {{count}}",
|
|
@@ -192,6 +194,7 @@ const messages = {
|
|
|
192
194
|
slideSettings: "Slide settings",
|
|
193
195
|
layout: "Layout",
|
|
194
196
|
background: "Background",
|
|
197
|
+
customColor: "Custom color",
|
|
195
198
|
media: "Media",
|
|
196
199
|
generateImage: "Generate Image",
|
|
197
200
|
assetLibrary: "Asset Library",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const RELOAD_GUARD_KEY = "slides:export-module-reload-attempted";
|
|
2
|
+
|
|
3
|
+
// Chrome/Firefox/Safari all use this wording (or close to it) for a dynamic
|
|
4
|
+
// `import()` whose chunk 404s — typically because the app was redeployed
|
|
5
|
+
// after the page loaded and the browser still references an old, now-missing
|
|
6
|
+
// chunk hash. Calling `import()` again with the *same* specifier re-requests
|
|
7
|
+
// the same now-missing URL and fails immediately, so a plain retry never
|
|
8
|
+
// recovers this case — only a full reload picks up the current chunk map.
|
|
9
|
+
const STALE_CHUNK_PATTERN =
|
|
10
|
+
/fetch dynamically imported module|error loading dynamically imported module/i;
|
|
11
|
+
|
|
12
|
+
function isStaleChunkError(error: unknown): boolean {
|
|
13
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14
|
+
return STALE_CHUNK_PATTERN.test(message);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hasAlreadyReloaded(): boolean {
|
|
18
|
+
try {
|
|
19
|
+
return sessionStorage.getItem(RELOAD_GUARD_KEY) === "1";
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function markReloaded(): void {
|
|
26
|
+
try {
|
|
27
|
+
sessionStorage.setItem(RELOAD_GUARD_KEY, "1");
|
|
28
|
+
} catch {
|
|
29
|
+
// sessionStorage unavailable (e.g. private mode) — reload once anyway,
|
|
30
|
+
// worst case we reload more than once instead of not recovering at all.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Lazy `import()` calls for export libraries (jsPDF, modern-screenshot,
|
|
36
|
+
* dom-to-pptx) fail with "Failed to fetch dynamically imported module" when
|
|
37
|
+
* the chunk is momentarily unreachable (flaky network) or stale after a
|
|
38
|
+
* redeploy. For the stale-chunk case, retrying the same specifier is a no-op,
|
|
39
|
+
* so we reload the page once to pick up the current chunk map; for anything
|
|
40
|
+
* else, a same-specifier retry can still clear a transient blip.
|
|
41
|
+
*/
|
|
42
|
+
export async function importExportModule<T>(
|
|
43
|
+
loader: () => Promise<T>,
|
|
44
|
+
): Promise<T> {
|
|
45
|
+
try {
|
|
46
|
+
return await loader();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (isStaleChunkError(error) && !hasAlreadyReloaded()) {
|
|
49
|
+
markReloaded();
|
|
50
|
+
window.location.reload();
|
|
51
|
+
// The reload navigates away; never resolve so callers don't proceed
|
|
52
|
+
// against a page that's about to unload.
|
|
53
|
+
return new Promise<T>(() => {});
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return await loader();
|
|
57
|
+
} catch {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"Couldn't load the export module. Refresh the page and try again.",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* doesn't shrink the captured pixels.
|
|
12
12
|
*/
|
|
13
13
|
import { type AspectRatio, getAspectRatioDims } from "./aspect-ratios";
|
|
14
|
+
import { importExportModule } from "./dynamic-import";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Cross-origin <img> elements without an explicit `crossOrigin="anonymous"`
|
|
@@ -99,8 +100,8 @@ export async function exportDeckAsPdf(
|
|
|
99
100
|
// JPEG (vs PNG) keeps a typical 8-slide deck under ~10 MB instead of
|
|
100
101
|
// ~100 MB — at 0.92 quality the difference is invisible on slide content.
|
|
101
102
|
const [{ domToJpeg }, { jsPDF }] = await Promise.all([
|
|
102
|
-
import("modern-screenshot"),
|
|
103
|
-
import("jspdf"),
|
|
103
|
+
importExportModule(() => import("modern-screenshot")),
|
|
104
|
+
importExportModule(() => import("jspdf")),
|
|
104
105
|
]);
|
|
105
106
|
|
|
106
107
|
// Web fonts (Poppins) must finish loading before capture — otherwise
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AspectRatio, getAspectRatioDims } from "./aspect-ratios";
|
|
2
|
+
import { importExportModule } from "./dynamic-import";
|
|
2
3
|
import {
|
|
3
4
|
findSlideExportSource,
|
|
4
5
|
preloadImagesWithCors,
|
|
@@ -83,7 +84,7 @@ export async function addSpeakerNotesToPptxBlob(
|
|
|
83
84
|
const hasNotes = slides.some((slide) => slide.notes?.trim());
|
|
84
85
|
if (!hasNotes) return blob;
|
|
85
86
|
|
|
86
|
-
const { default: JSZip } = await import("jszip");
|
|
87
|
+
const { default: JSZip } = await importExportModule(() => import("jszip"));
|
|
87
88
|
const zip = await JSZip.loadAsync(blob);
|
|
88
89
|
|
|
89
90
|
const contentTypesFile = zip.file("[Content_Types].xml");
|
|
@@ -317,7 +318,9 @@ export async function exportDeckAsPptx(
|
|
|
317
318
|
slides: PptxExportSlide[],
|
|
318
319
|
aspectRatio?: AspectRatio,
|
|
319
320
|
): Promise<void> {
|
|
320
|
-
const { exportToPptx } = await
|
|
321
|
+
const { exportToPptx } = await importExportModule(
|
|
322
|
+
() => import("dom-to-pptx"),
|
|
323
|
+
);
|
|
321
324
|
|
|
322
325
|
if (typeof document !== "undefined" && document.fonts?.ready) {
|
|
323
326
|
await document.fonts.ready;
|
|
@@ -18,6 +18,7 @@ import { Button } from "@/components/ui/button";
|
|
|
18
18
|
import { useDesignSystems } from "@/hooks/use-design-systems";
|
|
19
19
|
|
|
20
20
|
import type { DesignSystemData } from "../../shared/api";
|
|
21
|
+
import { missingDesignSystemDataFields } from "../../shared/design-system-validation";
|
|
21
22
|
|
|
22
23
|
export default function DesignSystems() {
|
|
23
24
|
const t = useT();
|
|
@@ -52,7 +53,14 @@ export default function DesignSystems() {
|
|
|
52
53
|
|
|
53
54
|
const parseDesignData = (dataStr: string): DesignSystemData | null => {
|
|
54
55
|
try {
|
|
55
|
-
|
|
56
|
+
const parsed = JSON.parse(dataStr) as DesignSystemData;
|
|
57
|
+
// DesignSystemCard reads colors.* / typography.* unconditionally, down
|
|
58
|
+
// to nested fields like typography.headingFont. Rows written before
|
|
59
|
+
// create/update validation existed can have `colors: {}` and still
|
|
60
|
+
// pass a truthy check, so reuse the same nested-field validator the
|
|
61
|
+
// actions use rather than only checking the top-level objects exist.
|
|
62
|
+
if (missingDesignSystemDataFields(parsed).length > 0) return null;
|
|
63
|
+
return parsed;
|
|
56
64
|
} catch {
|
|
57
65
|
return null;
|
|
58
66
|
}
|
|
@@ -57,6 +57,17 @@ export const deckShareLinks = table("deck_share_links", {
|
|
|
57
57
|
createdAt: text("created_at").notNull().default(now()),
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
export const uploadedAssets = table("uploaded_assets", {
|
|
61
|
+
id: text("id").primaryKey(),
|
|
62
|
+
filename: text("filename").notNull(),
|
|
63
|
+
url: text("url").notNull(),
|
|
64
|
+
type: text("type").notNull(),
|
|
65
|
+
size: integer("size").notNull(),
|
|
66
|
+
provider: text("provider"),
|
|
67
|
+
ownerEmail: text("owner_email").notNull(),
|
|
68
|
+
createdAt: text("created_at").notNull().default(now()),
|
|
69
|
+
});
|
|
70
|
+
|
|
60
71
|
export const slideComments = table("slide_comments", {
|
|
61
72
|
id: text("id").primaryKey(),
|
|
62
73
|
deckId: text("deck_id").notNull(),
|