@agent-native/core 0.141.7 → 0.144.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-native.eject.json +9 -0
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +19 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/analytics/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/analytics/AGENTS.md +35 -38
- package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +1 -1
- package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +17 -22
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +1 -1
- package/corpus/templates/analytics/actions/navigate.ts +4 -5
- package/corpus/templates/analytics/actions/provider-api-register.ts +50 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +44 -11
- package/corpus/templates/analytics/actions/test-custom-api-connection.ts +106 -0
- package/corpus/templates/analytics/app/components/CustomApiCard.tsx +430 -0
- package/corpus/templates/analytics/app/i18n-data.ts +680 -0
- package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +6 -0
- package/corpus/templates/analytics/app/lib/sql-query.ts +33 -6
- package/corpus/templates/analytics/app/pages/DataSources.tsx +4 -0
- package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-load-reliably-during-background-data-sw.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-remain-responsive-while-historical-data.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-analytics-query-results-now-render-as-a-table-with-csv-downl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-connect-public-https-custom-apis-and-hand-tested-endpoints-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-durable-agent-worker-memory.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-live-dashboard-panels-use-the-shared-query-timeout.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-on-demand-billing-is-available-as-a-native-dashboard-with-ta.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-06-visible-dashboard-names-are-unique.md +6 -0
- package/corpus/templates/analytics/netlify.toml +7 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +4 -0
- package/corpus/templates/analytics/server/db/index.ts +27 -0
- package/corpus/templates/analytics/server/db/schema.ts +10 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +227 -60
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +156 -45
- package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +227 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +1 -0
- package/corpus/templates/analytics/server/plugins/db.ts +52 -18
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +28 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +16 -1
- package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/assets/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/assets/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/assets/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/brain/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/brain/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/brain/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/brain/actions/list-connection-providers.ts +24 -3
- package/corpus/templates/brain/app/lib/dispatch-links.ts +44 -0
- package/corpus/templates/brain/app/routes/sources.tsx +15 -7
- package/corpus/templates/brain/changelog/2026-08-06-brain-provider-setup-links-open-dispatch-correctly-in-standa.md +6 -0
- package/corpus/templates/brain/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +26 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/calendar/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/calendar/AGENTS.md +11 -32
- package/corpus/templates/calendar/actions/update-event.ts +107 -1
- package/corpus/templates/calendar/app/components/calendar/EventCalendarSelect.tsx +83 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +57 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +59 -73
- package/corpus/templates/calendar/app/hooks/use-events.ts +4 -1
- package/corpus/templates/calendar/app/i18n-data.ts +20 -0
- package/corpus/templates/calendar/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-06-existing-events-can-move-between-connected-google-account-ca.md +6 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +101 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +7 -1
- package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/clips/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/clips/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/clips/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +9 -24
- package/corpus/templates/clips/app/components/editor/timeline-geometry.ts +15 -0
- package/corpus/templates/clips/app/components/editor/waveform.tsx +4 -17
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +14 -3
- package/corpus/templates/clips/app/components/player/player-controls.tsx +0 -3
- package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -33
- package/corpus/templates/clips/app/components/player/video-player.tsx +58 -11
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +28 -38
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +21 -27
- package/corpus/templates/clips/changelog/2026-08-06-clips-recovers-from-stalled-recording-setup-without-requirin.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-clips-starts-faster-after-a-deploy-or-idle-period-startup-no.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-deleting-a-recording-returns-cleanly-to-the-library-and-fit-.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-hide-clip-insights-from-viewers-without-analytics-access.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-06-trimmed-clips-play-on-a-clean-timeline-without-cut-markers.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +48 -20
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +275 -74
- package/corpus/templates/clips/desktop/src/lib/recording-start-guard.ts +84 -0
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +14 -4
- package/corpus/templates/clips/server/plugins/db.ts +214 -26
- package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/content/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/content/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/content/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/crm/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/crm/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/crm/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/crm/changelog/2026-08-06-crm-starts-faster-after-a-deploy-or-idle-period-one-time-dat.md +6 -0
- package/corpus/templates/crm/server/plugins/db.ts +11 -8
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +36 -1
- package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/design/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/design/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/design/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/design/actions/import-from-url.ts +1 -1
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +27 -18
- package/corpus/templates/design/app/pages/Index.tsx +6 -2
- package/corpus/templates/design/changelog/2026-08-05-deleting-a-design-from-the-home-page-no-longer-leaves-the-ap.md +6 -0
- package/corpus/templates/design/changelog/2026-08-05-renaming-a-layer-now-focuses-its-name-field-automatically.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/dispatch/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/dispatch/AGENTS.md +8 -1
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -0
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +30 -15
- package/corpus/templates/dispatch/app/i18n-data.ts +131 -0
- package/corpus/templates/dispatch/app/routes/admin._index.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.agents.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.approvals.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.$appId.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.apps.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.audit.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.automations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.destinations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.dreams.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.identities.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.integrations.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.messaging.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.metrics.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.new-app.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.operations.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.thread-debug.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.transactional-email.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/admin.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.vault.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/admin.workspace.tsx +1 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-dispatch-keeps-everyday-navigation-focused-while-management-.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-ready-workspace-apps-stay-one-click-away-in-a-compact-dispat.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-06-shared-vault-values-and-management-are-now-restricted-to-wor.md +6 -0
- package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/factory/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/factory/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/factory/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/forms/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/forms/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/forms/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/forms/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/macros/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/macros/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/macros/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/mail/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/mail/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/mail/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/mail/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
- package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/plan/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/plan/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/plan/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +48 -5
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +37 -1
- package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +6 -0
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +15 -2
- package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +29 -24
- package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/slides/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/slides/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/slides/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/corpus/templates/slides/AGENTS.md +17 -21
- package/corpus/templates/slides/_gitignore +1 -0
- package/corpus/templates/slides/actions/export-pptx.ts +470 -36
- package/corpus/templates/slides/actions/get-deck.ts +12 -0
- package/corpus/templates/slides/actions/import-file.ts +175 -139
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +271 -12
- package/corpus/templates/slides/actions/import-pptx.ts +193 -68
- package/corpus/templates/slides/actions/patch-deck.ts +51 -0
- package/corpus/templates/slides/actions/update-slide.ts +29 -3
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -13
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +25 -12
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +112 -57
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +202 -0
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +26 -14
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +173 -0
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +182 -141
- package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +18 -0
- package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +85 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +15 -0
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +27 -1
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +72 -11
- package/corpus/templates/slides/app/lib/deck-preview-frame.ts +15 -0
- package/corpus/templates/slides/app/lib/deck-share-links.ts +9 -0
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +89 -0
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +89 -0
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +3 -0
- package/corpus/templates/slides/app/pages/Index.tsx +172 -15
- package/corpus/templates/slides/changelog/2026-08-06-clicking-the-gray-canvas-around-a-slide-clears-its-selection.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-deck-thumbnails-keep-a-consistent-listing-frame-across-aspec.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-deep-linked-decks-open-directly-instead-of-being-interrupted.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-google-slides-exports-now-show-clear-import-steps-when-direc.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-import-pdf-google-slides-and-powerpoint-decks-directly-from-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-imported-powerpoint-exports-keep-text-and-images-editable-an.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-presentation-prompts-continue-adding-rendered-slides-until-t.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-public-decks-now-share-read-only-presentation-links-by-defau.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-slides-offers-a-direct-connect-google-button-when-a-drive-co.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-slides-preserve-uploaded-pdf-layouts-accept-larger-reference.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-styled-words-stay-part-of-their-text-box-instead-of-becoming.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-06-uploaded-decks-keep-their-slides-and-visuals-when-you-improv.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +7 -20
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +247 -13
- package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +67 -0
- package/corpus/templates/slides/server/handlers/import/pptx-parser.ts +2 -0
- package/corpus/templates/slides/server/handlers/uploads.ts +7 -3
- package/corpus/templates/slides/server/lib/chat-attachments.ts +7 -4
- package/corpus/templates/slides/server/lib/emails.ts +7 -2
- package/corpus/templates/slides/server/lib/google-docs-error.ts +50 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +1 -0
- package/corpus/templates/slides/server/lib/import-asset-storage.ts +127 -0
- package/corpus/templates/slides/server/lib/source-import.ts +167 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +12 -3
- package/corpus/templates/slides/server/routes/api/import-assets/[token].get.ts +70 -0
- package/corpus/templates/slides/shared/upload-types.ts +3 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/agent/production-agent.d.ts +17 -0
- package/dist/agent/production-agent.js +72 -1
- package/dist/cli/agent-plugin.d.ts +112 -0
- package/dist/cli/agent-plugin.js +1081 -0
- package/dist/cli/app-skill.js +39 -1
- package/dist/cli/code-agent-executor.d.ts +3 -0
- package/dist/cli/code-agent-executor.js +51 -13
- package/dist/cli/index.js +13 -0
- package/dist/cli/templates-meta.js +1 -1
- package/dist/client/AgentPanel.d.ts +3 -1
- package/dist/client/AgentPanel.js +9 -2
- package/dist/client/AssistantChat.d.ts +8 -0
- package/dist/client/AssistantChat.js +23 -8
- package/dist/client/NewWorkspaceAppFlow.js +1 -0
- package/dist/client/agent-chat-adapter.js +123 -4
- package/dist/client/assistant-ui-recovery.d.ts +2 -1
- package/dist/client/assistant-ui-recovery.js +7 -3
- package/dist/client/onboarding/FirstRunOnboarding.js +11 -4
- package/dist/client/org/TeamPage.js +1 -1
- package/dist/client/require-session.js +1 -1
- package/dist/client/resources/McpConnectionSuggestion.d.ts +1 -0
- package/dist/client/resources/McpConnectionSuggestion.js +27 -10
- package/dist/client/resources/McpIntegrationDialog.js +77 -45
- package/dist/client/resources/mcp-integration-catalog.d.ts +13 -1
- package/dist/client/resources/mcp-integration-catalog.js +255 -2
- package/dist/client/resources/mcp-integration-logos.js +18 -2
- package/dist/client/settings/AccountSettingsCard.js +2 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.d.ts +9 -0
- package/dist/db/client.js +51 -16
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/ddl-guard.d.ts +11 -10
- package/dist/db/ddl-guard.js +40 -12
- package/dist/deploy/build.d.ts +2 -0
- package/dist/deploy/build.js +97 -4
- package/dist/deploy/workspace-deploy.js +0 -2
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/guards/no-env-credentials.js +9 -0
- package/dist/ingestion/index.d.ts +1 -1
- package/dist/ingestion/pptx.d.ts +68 -0
- package/dist/ingestion/pptx.js +660 -213
- package/dist/integrations/plugin.js +9 -6
- package/dist/jobs/scheduler.js +79 -23
- package/dist/localization/default-messages.d.ts +54 -0
- package/dist/localization/default-messages.js +57 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp-client/oauth-routes.d.ts +2 -0
- package/dist/mcp-client/oauth-routes.js +73 -6
- package/dist/observability/routes.d.ts +5 -5
- package/dist/onboarding/app-profile.js +11 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/store.js +15 -1
- package/dist/scripts/call-agent.js +34 -4
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat/action-filters-a2a.d.ts +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +36 -1
- package/dist/server/agent-chat-plugin.js +29 -8
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +47 -11
- package/dist/server/better-auth-instance.js +5 -3
- package/dist/server/core-routes-plugin.d.ts +8 -1
- package/dist/server/core-routes-plugin.js +28 -3
- package/dist/server/credential-provider.d.ts +3 -3
- package/dist/server/credential-provider.js +90 -4
- package/dist/server/onboarding-html.d.ts +2 -0
- package/dist/server/onboarding-html.js +115 -29
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/self-dispatch.d.ts +5 -4
- package/dist/server/self-dispatch.js +25 -14
- package/dist/server/ssr-handler.d.ts +1 -1
- package/dist/server/ssr-handler.js +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/cache-control.d.ts +26 -2
- package/dist/shared/cache-control.js +43 -2
- package/dist/shared/password-policy.d.ts +3 -0
- package/dist/shared/password-policy.js +3 -0
- package/dist/sharing/actions/set-resource-visibility.js +16 -4
- package/dist/sharing/registry.d.ts +14 -0
- package/dist/styles/agent-native.css +6 -0
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
- package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/dist/templates/workspace-root/AGENTS.md +7 -0
- package/dist/user-profile/actions/change-password.js +8 -3
- package/dist/user-profile/actions/set-password.js +5 -1
- package/dist/vite/action-types-plugin.js +59 -0
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/getting-started-actions.mdx +38 -189
- package/docs/content/getting-started-database.mdx +80 -164
- package/docs/content/getting-started-pages.mdx +44 -155
- package/docs/content/getting-started.mdx +26 -106
- package/docs/content/skills-guide.mdx +13 -0
- package/package.json +6 -2
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
- package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
- package/src/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
- package/src/templates/workspace-root/AGENTS.md +7 -0
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { ssrfSafeFetch } from "@agent-native/core/extensions/url-safety";
|
|
2
3
|
import { buildDeepLink } from "@agent-native/core/server";
|
|
3
4
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
5
|
+
import pLimit from "p-limit";
|
|
4
6
|
import { z } from "zod";
|
|
5
7
|
|
|
8
|
+
import {
|
|
9
|
+
parsePptx,
|
|
10
|
+
type ParsedPresentation,
|
|
11
|
+
} from "../server/handlers/import/pptx-parser.js";
|
|
6
12
|
import { getGoogleDocsAccessToken } from "../server/lib/google-docs-oauth.js";
|
|
7
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
importPptxBufferToDeck,
|
|
15
|
+
type ImportedImageFallback,
|
|
16
|
+
} from "./import-pptx.js";
|
|
8
17
|
|
|
9
18
|
function deckDeepLink(deckId: string): string {
|
|
10
19
|
return buildDeepLink({
|
|
@@ -14,6 +23,38 @@ function deckDeepLink(deckId: string): string {
|
|
|
14
23
|
});
|
|
15
24
|
}
|
|
16
25
|
|
|
26
|
+
export function extractGoogleSlidesPresentationId(value: string): string {
|
|
27
|
+
const candidate = value.trim();
|
|
28
|
+
if (!candidate)
|
|
29
|
+
throw new Error("A Google Slides file ID or URL is required.");
|
|
30
|
+
|
|
31
|
+
if (!/^https?:\/\//i.test(candidate)) {
|
|
32
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(candidate)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"Use a Google Slides file ID or a docs.google.com presentation URL.",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return candidate;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let url: URL;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(candidate);
|
|
43
|
+
} catch {
|
|
44
|
+
throw new Error("Use a valid Google Slides presentation URL.");
|
|
45
|
+
}
|
|
46
|
+
if (url.hostname !== "docs.google.com") {
|
|
47
|
+
throw new Error("Use a docs.google.com Google Slides presentation URL.");
|
|
48
|
+
}
|
|
49
|
+
const match = url.pathname.match(
|
|
50
|
+
/^\/presentation\/d\/([a-zA-Z0-9_-]+)(?:\/|$)/,
|
|
51
|
+
);
|
|
52
|
+
if (!match) {
|
|
53
|
+
throw new Error("That URL is not a Google Slides presentation link.");
|
|
54
|
+
}
|
|
55
|
+
return match[1];
|
|
56
|
+
}
|
|
57
|
+
|
|
17
58
|
async function exportGoogleSlidesAsPptx(fileId: string, accessToken: string) {
|
|
18
59
|
const response = await fetch(
|
|
19
60
|
`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}/export?mimeType=application/vnd.openxmlformats-officedocument.presentationml.presentation`,
|
|
@@ -36,36 +77,254 @@ async function exportGoogleSlidesAsPptx(fileId: string, accessToken: string) {
|
|
|
36
77
|
return Buffer.from(bytes);
|
|
37
78
|
}
|
|
38
79
|
|
|
80
|
+
interface GoogleSlidesImageElement {
|
|
81
|
+
objectId?: string;
|
|
82
|
+
size?: {
|
|
83
|
+
width?: { magnitude?: number };
|
|
84
|
+
height?: { magnitude?: number };
|
|
85
|
+
};
|
|
86
|
+
transform?: {
|
|
87
|
+
scaleX?: number;
|
|
88
|
+
scaleY?: number;
|
|
89
|
+
translateX?: number;
|
|
90
|
+
translateY?: number;
|
|
91
|
+
};
|
|
92
|
+
image?: {
|
|
93
|
+
contentUrl?: string;
|
|
94
|
+
imageProperties?: {
|
|
95
|
+
cropProperties?: {
|
|
96
|
+
leftOffset?: number;
|
|
97
|
+
rightOffset?: number;
|
|
98
|
+
topOffset?: number;
|
|
99
|
+
bottomOffset?: number;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface GoogleSlidesPresentationImages {
|
|
106
|
+
slides?: Array<{
|
|
107
|
+
pageElements?: GoogleSlidesImageElement[];
|
|
108
|
+
}>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface GoogleSlidesImageCandidate {
|
|
112
|
+
slideIndex: number;
|
|
113
|
+
imageIndex: number;
|
|
114
|
+
objectId?: string;
|
|
115
|
+
contentUrl: string;
|
|
116
|
+
x: number;
|
|
117
|
+
y: number;
|
|
118
|
+
width: number;
|
|
119
|
+
height: number;
|
|
120
|
+
crop?: ImportedImageFallback["crop"];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function finiteNumber(value: number | undefined): number | undefined {
|
|
124
|
+
return value != null && Number.isFinite(value) ? value : undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Google Drive's PPTX export can omit image page elements that remain present
|
|
129
|
+
* in the native Slides document. Read those native objects as a fidelity
|
|
130
|
+
* fallback so a direct Google Slides import does not silently lose artwork.
|
|
131
|
+
*/
|
|
132
|
+
async function fetchGoogleSlidesImageFallbacks(
|
|
133
|
+
fileId: string,
|
|
134
|
+
accessToken: string,
|
|
135
|
+
presentation: ParsedPresentation,
|
|
136
|
+
): Promise<ImportedImageFallback[]> {
|
|
137
|
+
const fields =
|
|
138
|
+
"slides(pageElements(objectId,size,transform,image(contentUrl,imageProperties(cropProperties))))";
|
|
139
|
+
const response = await ssrfSafeFetch(
|
|
140
|
+
`https://slides.googleapis.com/v1/presentations/${encodeURIComponent(fileId)}?fields=${encodeURIComponent(fields)}`,
|
|
141
|
+
{ headers: { Authorization: `Bearer ${accessToken}` } },
|
|
142
|
+
{ httpsOnly: true, maxRedirects: 2 },
|
|
143
|
+
);
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Google Slides returned HTTP ${response.status} while reading native image elements. Reconnect Google Drive and try the import again.`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const googlePresentation =
|
|
150
|
+
(await response.json()) as GoogleSlidesPresentationImages;
|
|
151
|
+
const candidates: GoogleSlidesImageCandidate[] = [];
|
|
152
|
+
const candidateKeys = new Set<string>();
|
|
153
|
+
|
|
154
|
+
for (const [slideIndex, slide] of (
|
|
155
|
+
googlePresentation.slides ?? []
|
|
156
|
+
).entries()) {
|
|
157
|
+
for (const [imageIndex, element] of (slide.pageElements ?? []).entries()) {
|
|
158
|
+
const contentUrl = element.image?.contentUrl;
|
|
159
|
+
const baseWidth = finiteNumber(element.size?.width?.magnitude);
|
|
160
|
+
const baseHeight = finiteNumber(element.size?.height?.magnitude);
|
|
161
|
+
const scaleX = finiteNumber(element.transform?.scaleX) ?? 1;
|
|
162
|
+
const scaleY = finiteNumber(element.transform?.scaleY) ?? 1;
|
|
163
|
+
const x = finiteNumber(element.transform?.translateX);
|
|
164
|
+
const y = finiteNumber(element.transform?.translateY);
|
|
165
|
+
const width =
|
|
166
|
+
baseWidth == null ? undefined : baseWidth * Math.abs(scaleX);
|
|
167
|
+
const height =
|
|
168
|
+
baseHeight == null ? undefined : baseHeight * Math.abs(scaleY);
|
|
169
|
+
if (
|
|
170
|
+
!contentUrl ||
|
|
171
|
+
x == null ||
|
|
172
|
+
y == null ||
|
|
173
|
+
width == null ||
|
|
174
|
+
height == null ||
|
|
175
|
+
width <= 0 ||
|
|
176
|
+
height <= 0
|
|
177
|
+
) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const existingImage = presentation.slides[slideIndex]?.elements.some(
|
|
182
|
+
(existing) =>
|
|
183
|
+
existing.kind === "image" &&
|
|
184
|
+
Math.abs(existing.x - x) < 1000 &&
|
|
185
|
+
Math.abs(existing.y - y) < 1000 &&
|
|
186
|
+
Math.abs(existing.width - width) < 1000 &&
|
|
187
|
+
Math.abs(existing.height - height) < 1000,
|
|
188
|
+
);
|
|
189
|
+
if (existingImage) continue;
|
|
190
|
+
|
|
191
|
+
const cropProperties = element.image?.imageProperties?.cropProperties;
|
|
192
|
+
const crop = cropProperties
|
|
193
|
+
? {
|
|
194
|
+
left: cropProperties.leftOffset ?? 0,
|
|
195
|
+
top: cropProperties.topOffset ?? 0,
|
|
196
|
+
right: cropProperties.rightOffset ?? 0,
|
|
197
|
+
bottom: cropProperties.bottomOffset ?? 0,
|
|
198
|
+
}
|
|
199
|
+
: undefined;
|
|
200
|
+
const candidateKey = `${slideIndex}:${x}:${y}:${width}:${height}`;
|
|
201
|
+
if (candidateKeys.has(candidateKey)) continue;
|
|
202
|
+
candidateKeys.add(candidateKey);
|
|
203
|
+
candidates.push({
|
|
204
|
+
slideIndex,
|
|
205
|
+
imageIndex,
|
|
206
|
+
...(element.objectId ? { objectId: element.objectId } : {}),
|
|
207
|
+
contentUrl,
|
|
208
|
+
x,
|
|
209
|
+
y,
|
|
210
|
+
width,
|
|
211
|
+
height,
|
|
212
|
+
...(crop ? { crop } : {}),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const downloadLimit = pLimit(4);
|
|
218
|
+
const imageDownloads = new Map<
|
|
219
|
+
string,
|
|
220
|
+
Promise<{ data: Uint8Array; mimeType: string }>
|
|
221
|
+
>();
|
|
222
|
+
const downloadImage = (candidate: GoogleSlidesImageCandidate) => {
|
|
223
|
+
const existing = imageDownloads.get(candidate.contentUrl);
|
|
224
|
+
if (existing) return existing;
|
|
225
|
+
|
|
226
|
+
const download = downloadLimit(async () => {
|
|
227
|
+
const imageResponse = await ssrfSafeFetch(
|
|
228
|
+
candidate.contentUrl,
|
|
229
|
+
{ headers: { Authorization: `Bearer ${accessToken}` } },
|
|
230
|
+
{ httpsOnly: true, maxRedirects: 2 },
|
|
231
|
+
);
|
|
232
|
+
if (!imageResponse.ok) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Google Slides returned HTTP ${imageResponse.status} while downloading image ${candidate.imageIndex + 1} on slide ${candidate.slideIndex + 1}.`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
const mimeType =
|
|
238
|
+
imageResponse.headers.get("content-type")?.split(";", 1)[0] ||
|
|
239
|
+
"image/png";
|
|
240
|
+
if (!mimeType.startsWith("image/")) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Google Slides returned a non-image asset for image ${candidate.imageIndex + 1} on slide ${candidate.slideIndex + 1}.`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
data: new Uint8Array(await imageResponse.arrayBuffer()),
|
|
247
|
+
mimeType,
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
imageDownloads.set(candidate.contentUrl, download);
|
|
251
|
+
return download;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return Promise.all(
|
|
255
|
+
candidates.map(async (candidate) => {
|
|
256
|
+
const { data, mimeType } = await downloadImage(candidate);
|
|
257
|
+
return {
|
|
258
|
+
slideIndex: candidate.slideIndex,
|
|
259
|
+
x: candidate.x,
|
|
260
|
+
y: candidate.y,
|
|
261
|
+
width: candidate.width,
|
|
262
|
+
height: candidate.height,
|
|
263
|
+
data,
|
|
264
|
+
mimeType,
|
|
265
|
+
name: `google-slides-${candidate.slideIndex + 1}-${candidate.objectId ?? candidate.imageIndex}.png`,
|
|
266
|
+
...(candidate.crop ? { crop: candidate.crop } : {}),
|
|
267
|
+
};
|
|
268
|
+
}),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
39
272
|
export default defineAction({
|
|
40
273
|
description:
|
|
41
|
-
"Import a Google Slides deck selected from Google Picker and save it as a reusable Slides reference deck. " +
|
|
274
|
+
"Import a Google Slides deck selected from Google Picker or provided as a Google Slides URL and save it as a reusable Slides reference deck. " +
|
|
42
275
|
"The selected presentation is exported from Drive as PPTX, parsed, and stored as a normal deck in the caller's Slides workspace.",
|
|
43
|
-
schema: z
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
276
|
+
schema: z
|
|
277
|
+
.object({
|
|
278
|
+
fileId: z
|
|
279
|
+
.string()
|
|
280
|
+
.optional()
|
|
281
|
+
.describe("Google Slides file ID from Google Picker"),
|
|
282
|
+
presentationUrl: z
|
|
283
|
+
.string()
|
|
284
|
+
.url()
|
|
285
|
+
.optional()
|
|
286
|
+
.describe("A docs.google.com Google Slides presentation URL"),
|
|
287
|
+
title: z
|
|
288
|
+
.string()
|
|
289
|
+
.optional()
|
|
290
|
+
.describe("Optional title for the imported reference deck"),
|
|
291
|
+
})
|
|
292
|
+
.refine(
|
|
293
|
+
({ fileId, presentationUrl }) => Boolean(fileId || presentationUrl),
|
|
294
|
+
{
|
|
295
|
+
message: "Provide either fileId or presentationUrl.",
|
|
296
|
+
},
|
|
297
|
+
),
|
|
298
|
+
run: async ({ fileId, presentationUrl, title }) => {
|
|
51
299
|
const owner = getRequestUserEmail();
|
|
52
300
|
if (!owner) throw new Error("no authenticated user");
|
|
53
301
|
|
|
54
302
|
const connection = await getGoogleDocsAccessToken(owner);
|
|
55
303
|
if (!connection) {
|
|
56
304
|
throw new Error(
|
|
57
|
-
"
|
|
305
|
+
"Google Drive is not connected. Use the Connect Google button in Slides, then try again.",
|
|
58
306
|
);
|
|
59
307
|
}
|
|
60
308
|
|
|
309
|
+
const presentationId = extractGoogleSlidesPresentationId(
|
|
310
|
+
presentationUrl ?? fileId ?? "",
|
|
311
|
+
);
|
|
61
312
|
const fileBuffer = await exportGoogleSlidesAsPptx(
|
|
62
|
-
|
|
313
|
+
presentationId,
|
|
314
|
+
connection.accessToken,
|
|
315
|
+
);
|
|
316
|
+
const parsedPresentation = await parsePptx(fileBuffer);
|
|
317
|
+
const imageFallbacks = await fetchGoogleSlidesImageFallbacks(
|
|
318
|
+
presentationId,
|
|
63
319
|
connection.accessToken,
|
|
320
|
+
parsedPresentation,
|
|
64
321
|
);
|
|
65
322
|
return importPptxBufferToDeck({
|
|
66
323
|
fileBuffer,
|
|
324
|
+
parsedPresentation,
|
|
67
325
|
title,
|
|
68
326
|
source: "import-google-slides-reference",
|
|
327
|
+
imageFallbacks,
|
|
69
328
|
});
|
|
70
329
|
},
|
|
71
330
|
link: ({ result }) => {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
2
|
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
|
-
import { uploadFile } from "@agent-native/core/file-upload";
|
|
4
3
|
import {
|
|
5
4
|
getRequestUserEmail,
|
|
6
5
|
getRequestOrgId,
|
|
@@ -13,58 +12,86 @@ import { z } from "zod";
|
|
|
13
12
|
import { getDb, schema } from "../server/db/index.js";
|
|
14
13
|
import { notifyClients } from "../server/handlers/decks.js";
|
|
15
14
|
import { convertToSlideHtml } from "../server/handlers/import/html-converter.js";
|
|
15
|
+
import { uploadPptxSlideImages } from "../server/handlers/import/pptx-assets.js";
|
|
16
16
|
import {
|
|
17
17
|
parsePptx,
|
|
18
|
-
type
|
|
18
|
+
type ParsedElement,
|
|
19
|
+
type ParsedImage,
|
|
20
|
+
type ParsedPresentation,
|
|
19
21
|
} from "../server/handlers/import/pptx-parser.js";
|
|
22
|
+
import { buildSourceImportMetadata } from "../server/lib/source-import.js";
|
|
23
|
+
import {
|
|
24
|
+
ASPECT_RATIOS,
|
|
25
|
+
DEFAULT_ASPECT_RATIO,
|
|
26
|
+
type AspectRatio,
|
|
27
|
+
} from "../shared/aspect-ratios.js";
|
|
20
28
|
import { getDeckUrl } from "./_app-url.js";
|
|
21
29
|
import { readUserUploadedFile } from "./_uploaded-files.js";
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
):
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
31
|
+
export interface ImportedImageFallback {
|
|
32
|
+
slideIndex: number;
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
data: Uint8Array;
|
|
38
|
+
mimeType: string;
|
|
39
|
+
name: string;
|
|
40
|
+
crop?: ParsedImage["crop"];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Add source-native image objects that a Google Slides PPTX export omitted. */
|
|
44
|
+
export function applyImageFallbacks(
|
|
45
|
+
presentation: Awaited<ReturnType<typeof parsePptx>>,
|
|
46
|
+
fallbacks: ImportedImageFallback[] = [],
|
|
47
|
+
): number {
|
|
48
|
+
let added = 0;
|
|
49
|
+
for (const [fallbackIndex, fallback] of fallbacks.entries()) {
|
|
50
|
+
const slide = presentation.slides[fallback.slideIndex];
|
|
51
|
+
if (!slide || fallback.width <= 0 || fallback.height <= 0) continue;
|
|
52
|
+
|
|
53
|
+
const duplicate = slide.elements.some(
|
|
54
|
+
(element) =>
|
|
55
|
+
element.kind === "image" &&
|
|
56
|
+
Math.abs(element.x - fallback.x) < 1000 &&
|
|
57
|
+
Math.abs(element.y - fallback.y) < 1000 &&
|
|
58
|
+
Math.abs(element.width - fallback.width) < 1000 &&
|
|
59
|
+
Math.abs(element.height - fallback.height) < 1000,
|
|
60
|
+
);
|
|
61
|
+
if (duplicate) continue;
|
|
62
|
+
|
|
63
|
+
const image: ParsedImage = {
|
|
64
|
+
data: fallback.data,
|
|
65
|
+
mimeType: fallback.mimeType,
|
|
66
|
+
name: fallback.name,
|
|
67
|
+
aspectRatio: fallback.width / fallback.height,
|
|
68
|
+
...(fallback.crop ? { crop: fallback.crop } : {}),
|
|
69
|
+
};
|
|
70
|
+
const element: ParsedElement = {
|
|
71
|
+
id: `image-fallback-${fallback.slideIndex}-${fallbackIndex}`,
|
|
72
|
+
name: fallback.name,
|
|
73
|
+
kind: "image",
|
|
74
|
+
x: fallback.x,
|
|
75
|
+
y: fallback.y,
|
|
76
|
+
width: fallback.width,
|
|
77
|
+
height: fallback.height,
|
|
78
|
+
image,
|
|
79
|
+
};
|
|
80
|
+
slide.images.push(image);
|
|
81
|
+
slide.elements.push(element);
|
|
82
|
+
added++;
|
|
60
83
|
}
|
|
84
|
+
return added;
|
|
61
85
|
}
|
|
62
86
|
|
|
63
87
|
export async function importPptxBufferToDeck(args: {
|
|
64
88
|
fileBuffer: Buffer;
|
|
65
89
|
title?: string;
|
|
66
90
|
deckId?: string;
|
|
91
|
+
designSystemId?: string | null;
|
|
67
92
|
source?: string;
|
|
93
|
+
imageFallbacks?: ImportedImageFallback[];
|
|
94
|
+
parsedPresentation?: ParsedPresentation;
|
|
68
95
|
}): Promise<{
|
|
69
96
|
id: string;
|
|
70
97
|
title: string;
|
|
@@ -74,8 +101,17 @@ export async function importPptxBufferToDeck(args: {
|
|
|
74
101
|
url: string;
|
|
75
102
|
imagesSkipped?: number;
|
|
76
103
|
}> {
|
|
77
|
-
const {
|
|
78
|
-
|
|
104
|
+
const {
|
|
105
|
+
fileBuffer,
|
|
106
|
+
title,
|
|
107
|
+
deckId,
|
|
108
|
+
designSystemId,
|
|
109
|
+
source = "import-pptx",
|
|
110
|
+
imageFallbacks,
|
|
111
|
+
parsedPresentation,
|
|
112
|
+
} = args;
|
|
113
|
+
const presentation = parsedPresentation ?? (await parsePptx(fileBuffer));
|
|
114
|
+
applyImageFallbacks(presentation, imageFallbacks);
|
|
79
115
|
const deckTitle = title || presentation.title || "Imported Presentation";
|
|
80
116
|
const ownerEmail = getRequestUserEmail();
|
|
81
117
|
if (!ownerEmail) throw new Error("no authenticated user");
|
|
@@ -88,38 +124,38 @@ export async function importPptxBufferToDeck(args: {
|
|
|
88
124
|
await assertAccess("deck", deckId, "editor");
|
|
89
125
|
}
|
|
90
126
|
|
|
91
|
-
// Convert each parsed slide to
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
// one outbound upload per slide at once. An image can end up unused
|
|
95
|
-
// (unsupported format, or upload storage not configured) without
|
|
96
|
-
// failing the whole import — the slide's text still imports fine — but
|
|
97
|
-
// that shouldn't be a silent, invisible degradation, so it's counted
|
|
98
|
-
// and returned to the caller.
|
|
127
|
+
// Convert each parsed slide to its positioned scene graph, uploading every
|
|
128
|
+
// browser-renderable image so the imported deck keeps the source layering
|
|
129
|
+
// and media instead of collapsing to a one-image approximation.
|
|
99
130
|
const uploadLimit = pLimit(4);
|
|
100
131
|
const results = await Promise.all(
|
|
101
132
|
presentation.slides.map((parsedSlide, i) =>
|
|
102
133
|
uploadLimit(async () => {
|
|
103
|
-
const
|
|
104
|
-
parsedSlide,
|
|
105
|
-
i,
|
|
134
|
+
const uploadedImages = await uploadPptxSlideImages({
|
|
135
|
+
slide: parsedSlide,
|
|
136
|
+
slideIndex: i,
|
|
106
137
|
ownerEmail,
|
|
138
|
+
});
|
|
139
|
+
const html = convertToSlideHtml(
|
|
140
|
+
parsedSlide,
|
|
141
|
+
uploadedImages.urls,
|
|
142
|
+
themeFont,
|
|
107
143
|
);
|
|
108
|
-
const
|
|
144
|
+
const id = `slide-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
109
145
|
return {
|
|
110
146
|
slide: {
|
|
111
|
-
id
|
|
147
|
+
id,
|
|
112
148
|
content: html,
|
|
113
149
|
layout: parsedSlide.layoutHint ?? "content",
|
|
114
150
|
notes: parsedSlide.notes,
|
|
151
|
+
...(parsedSlide.transition
|
|
152
|
+
? { transition: parsedSlide.transition }
|
|
153
|
+
: {}),
|
|
154
|
+
...(parsedSlide.splitByParagraph ? { splitByParagraph: true } : {}),
|
|
115
155
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
imageSkippedCount: Math.max(
|
|
120
|
-
0,
|
|
121
|
-
parsedSlide.images.length - (imageUrl ? 1 : 0),
|
|
122
|
-
),
|
|
156
|
+
sourceText: parsedSlide.texts.map((text) => text.content).join("\n"),
|
|
157
|
+
imageUrls: Object.values(uploadedImages.urls),
|
|
158
|
+
imageSkippedCount: uploadedImages.imageSkippedCount,
|
|
123
159
|
};
|
|
124
160
|
}),
|
|
125
161
|
),
|
|
@@ -129,6 +165,26 @@ export async function importPptxBufferToDeck(args: {
|
|
|
129
165
|
(total, r) => total + r.imageSkippedCount,
|
|
130
166
|
0,
|
|
131
167
|
);
|
|
168
|
+
if (imagesSkipped > 0) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Source-faithful PPTX import could not preserve ${imagesSkipped} image(s). No deck was written. Retry with browser-renderable images or use a PDF export for page-faithful preservation.`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const sourceImport = buildSourceImportMetadata({
|
|
174
|
+
format: "pptx",
|
|
175
|
+
slides: results.map((result) => ({
|
|
176
|
+
id: result.slide.id,
|
|
177
|
+
text: result.sourceText,
|
|
178
|
+
notes: result.slide.notes ?? "",
|
|
179
|
+
imageUrls: result.imageUrls,
|
|
180
|
+
editableText: true,
|
|
181
|
+
})),
|
|
182
|
+
imagesSkipped,
|
|
183
|
+
});
|
|
184
|
+
const aspectRatio = nearestAspectRatio(
|
|
185
|
+
presentation.slides[0]?.widthEmu,
|
|
186
|
+
presentation.slides[0]?.heightEmu,
|
|
187
|
+
);
|
|
132
188
|
|
|
133
189
|
const db = getDb();
|
|
134
190
|
const now = new Date().toISOString();
|
|
@@ -143,10 +199,25 @@ export async function importPptxBufferToDeck(args: {
|
|
|
143
199
|
throw new Error(`Deck ${deckId} not found`);
|
|
144
200
|
}
|
|
145
201
|
|
|
146
|
-
const
|
|
202
|
+
const previousData = safeParseDeckData(existing[0].data);
|
|
203
|
+
const data = {
|
|
204
|
+
...previousData,
|
|
205
|
+
title: deckTitle,
|
|
206
|
+
slides,
|
|
207
|
+
...(aspectRatio ? { aspectRatio } : {}),
|
|
208
|
+
sourceImport,
|
|
209
|
+
updatedAt: now,
|
|
210
|
+
};
|
|
147
211
|
await db
|
|
148
212
|
.update(schema.decks)
|
|
149
|
-
.set({
|
|
213
|
+
.set({
|
|
214
|
+
title: deckTitle,
|
|
215
|
+
data: JSON.stringify(data),
|
|
216
|
+
...(designSystemId !== undefined
|
|
217
|
+
? { designSystemId }
|
|
218
|
+
: { designSystemId: existing[0].designSystemId }),
|
|
219
|
+
updatedAt: now,
|
|
220
|
+
})
|
|
150
221
|
.where(eq(schema.decks.id, deckId));
|
|
151
222
|
|
|
152
223
|
notifyClients(deckId);
|
|
@@ -168,13 +239,21 @@ export async function importPptxBufferToDeck(args: {
|
|
|
168
239
|
|
|
169
240
|
// Create new deck
|
|
170
241
|
const id = `deck-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
171
|
-
const data = {
|
|
242
|
+
const data = {
|
|
243
|
+
title: deckTitle,
|
|
244
|
+
slides,
|
|
245
|
+
...(aspectRatio ? { aspectRatio } : {}),
|
|
246
|
+
sourceImport,
|
|
247
|
+
createdAt: now,
|
|
248
|
+
updatedAt: now,
|
|
249
|
+
};
|
|
172
250
|
await db.insert(schema.decks).values({
|
|
173
251
|
id,
|
|
174
252
|
title: deckTitle,
|
|
175
253
|
data: JSON.stringify(data),
|
|
176
254
|
ownerEmail,
|
|
177
255
|
orgId: getRequestOrgId(),
|
|
256
|
+
designSystemId: designSystemId ?? undefined,
|
|
178
257
|
createdAt: now,
|
|
179
258
|
updatedAt: now,
|
|
180
259
|
});
|
|
@@ -197,8 +276,8 @@ export default defineAction({
|
|
|
197
276
|
description:
|
|
198
277
|
"Import a PPTX file and create a slide deck from it. " +
|
|
199
278
|
"Parses the PowerPoint file, extracts text and layout information, " +
|
|
200
|
-
"converts each slide to the app's HTML format, and creates or updates a deck. " +
|
|
201
|
-
"Returns the deck ID and slide count.",
|
|
279
|
+
"converts each slide to the app's positioned HTML format, records source-preservation metadata, and creates or updates a deck. " +
|
|
280
|
+
"If an embedded image cannot be preserved, the action fails before writing a partial deck. Returns the deck ID and slide count.",
|
|
202
281
|
schema: z.object({
|
|
203
282
|
filePath: z
|
|
204
283
|
.string()
|
|
@@ -209,6 +288,11 @@ export default defineAction({
|
|
|
209
288
|
.describe(
|
|
210
289
|
"If provided, import slides into this existing deck (replaces all slides)",
|
|
211
290
|
),
|
|
291
|
+
designSystemId: z
|
|
292
|
+
.string()
|
|
293
|
+
.nullable()
|
|
294
|
+
.optional()
|
|
295
|
+
.describe("Optional design system to link when creating a new deck"),
|
|
212
296
|
title: z
|
|
213
297
|
.string()
|
|
214
298
|
.optional()
|
|
@@ -216,8 +300,49 @@ export default defineAction({
|
|
|
216
300
|
"Deck title — defaults to the title extracted from the presentation",
|
|
217
301
|
),
|
|
218
302
|
}),
|
|
219
|
-
run: async ({ filePath, deckId, title }) => {
|
|
303
|
+
run: async ({ filePath, deckId, title, designSystemId }) => {
|
|
220
304
|
const { data: fileBuffer } = await readUserUploadedFile(filePath);
|
|
221
|
-
return importPptxBufferToDeck({
|
|
305
|
+
return importPptxBufferToDeck({
|
|
306
|
+
fileBuffer,
|
|
307
|
+
deckId,
|
|
308
|
+
title,
|
|
309
|
+
designSystemId,
|
|
310
|
+
});
|
|
222
311
|
},
|
|
223
312
|
});
|
|
313
|
+
|
|
314
|
+
function nearestAspectRatio(
|
|
315
|
+
width: number | undefined,
|
|
316
|
+
height: number | undefined,
|
|
317
|
+
): AspectRatio | undefined {
|
|
318
|
+
if (!width || !height || width <= 0 || height <= 0) return undefined;
|
|
319
|
+
const target = width / height;
|
|
320
|
+
let best: AspectRatio = DEFAULT_ASPECT_RATIO;
|
|
321
|
+
let bestDiff = Infinity;
|
|
322
|
+
for (const key of Object.keys(ASPECT_RATIOS) as AspectRatio[]) {
|
|
323
|
+
const preset = ASPECT_RATIOS[key];
|
|
324
|
+
const diff = Math.abs(preset.width / preset.height - target);
|
|
325
|
+
if (diff < bestDiff) {
|
|
326
|
+
best = key;
|
|
327
|
+
bestDiff = diff;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return best;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function safeParseDeckData(raw: string): Record<string, unknown> {
|
|
334
|
+
let parsed: unknown;
|
|
335
|
+
try {
|
|
336
|
+
parsed = JSON.parse(raw);
|
|
337
|
+
} catch {
|
|
338
|
+
throw new Error(
|
|
339
|
+
"The target deck contains invalid JSON; refusing to overwrite it.",
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
"The target deck data is invalid; refusing to overwrite it.",
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
return parsed as Record<string, unknown>;
|
|
348
|
+
}
|