@agent-native/core 0.141.7 → 0.144.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/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 +133 -44
- 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/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/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/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 +7 -9
- package/corpus/templates/slides/_gitignore +1 -0
- package/corpus/templates/slides/actions/export-pptx.ts +402 -36
- package/corpus/templates/slides/actions/import-file.ts +46 -85
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +271 -12
- package/corpus/templates/slides/actions/import-pptx.ts +85 -60
- 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 +4 -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 +1 -1
- 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/slide-animation-elements.ts +3 -0
- package/corpus/templates/slides/app/pages/Index.tsx +91 -1
- 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/server/handlers/google-docs-auth.ts +7 -20
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +236 -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 +6 -3
- 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/plugins/agent-chat.ts +4 -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/routes.d.ts +1 -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/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/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/app-profile.js +11 -0
- 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 +9 -9
- 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/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/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
|
@@ -15,6 +15,7 @@ import { z } from "zod";
|
|
|
15
15
|
|
|
16
16
|
import { getDb, schema } from "../server/db/index.js";
|
|
17
17
|
import { notifyClients } from "../server/handlers/decks.js";
|
|
18
|
+
import { uploadPptxSlideImages } from "../server/handlers/import/pptx-assets.js";
|
|
18
19
|
import { upsertBuilderProxyDesignSystem } from "../server/lib/builder-design-system-proxy.js";
|
|
19
20
|
import { setupPdfParse } from "../server/lib/pdf-parse-setup.js";
|
|
20
21
|
import {
|
|
@@ -32,7 +33,7 @@ export default defineAction({
|
|
|
32
33
|
"Import a file (PPTX, DOCX, PDF, FIG) and extract content for creating slides or slide design systems. " +
|
|
33
34
|
"For PPTX files, returns parsed slides with text and layout info ready for conversion. " +
|
|
34
35
|
"For DOCX files, returns structured sections extracted from the document. " +
|
|
35
|
-
"For PDF files, returns extracted text organized by page. " +
|
|
36
|
+
"For PDF files, returns extracted text organized by page, or source-faithful page-image slides when importIntoDeck is true. " +
|
|
36
37
|
"For Figma .fig files, requires Builder.io (free tier available) and starts Builder design-system indexing; the returned Builder job/design-system ids are the source of truth. " +
|
|
37
38
|
"The agent can then use the extracted content to create a deck via create-deck or add-slide, or tell the user where Builder is indexing the design system.",
|
|
38
39
|
schema: z.object({
|
|
@@ -53,7 +54,7 @@ export default defineAction({
|
|
|
53
54
|
.optional()
|
|
54
55
|
.default(false)
|
|
55
56
|
.describe(
|
|
56
|
-
"If true, append slides
|
|
57
|
+
"If true, append slides to deckId. PDF pages are imported as source-faithful images that preserve their original layout and aspect ratio; use the default false when editable extracted source text is needed.",
|
|
57
58
|
),
|
|
58
59
|
maxChars: z.coerce
|
|
59
60
|
.number()
|
|
@@ -176,6 +177,8 @@ export default defineAction({
|
|
|
176
177
|
imageNames: slide.images.map((img) => img.name),
|
|
177
178
|
notes: slide.notes,
|
|
178
179
|
layoutHint: slide.layoutHint,
|
|
180
|
+
transition: slide.transition,
|
|
181
|
+
splitByParagraph: slide.splitByParagraph,
|
|
179
182
|
})),
|
|
180
183
|
theme: presentation.theme,
|
|
181
184
|
deckId,
|
|
@@ -339,7 +342,7 @@ async function importPdfPagesAsFullBleedSlides(args: {
|
|
|
339
342
|
canvasFactory: object;
|
|
340
343
|
}) {
|
|
341
344
|
const { fileBuffer, title, deckId, PDFParse, canvasFactory } = args;
|
|
342
|
-
const {
|
|
345
|
+
const { buildFullPageImageSlideHtml, convertSectionsToSlides } =
|
|
343
346
|
await import("../server/handlers/import/html-converter.js");
|
|
344
347
|
|
|
345
348
|
const pdf = new PDFParse({
|
|
@@ -347,31 +350,26 @@ async function importPdfPagesAsFullBleedSlides(args: {
|
|
|
347
350
|
CanvasFactory: canvasFactory,
|
|
348
351
|
});
|
|
349
352
|
let pages: { num: number; text: string }[];
|
|
350
|
-
let
|
|
353
|
+
let screenshotsByPage: Map<
|
|
351
354
|
number,
|
|
352
355
|
{ data: Uint8Array; width: number; height: number }
|
|
353
356
|
>;
|
|
354
357
|
try {
|
|
355
358
|
pages = normalizePdfPages(await pdf.getText());
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const imageResult = await pdf.getImage({
|
|
359
|
+
// Rasterize the complete page so positioned text, vector shapes, logos,
|
|
360
|
+
// photos, and the source page proportions survive the import together.
|
|
361
|
+
const screenshotResult = await pdf.getScreenshot({
|
|
362
|
+
desiredWidth: 1600,
|
|
361
363
|
imageBuffer: true,
|
|
362
364
|
imageDataUrl: false,
|
|
363
365
|
});
|
|
364
|
-
|
|
365
|
-
for (const page of
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
return best;
|
|
373
|
-
}, undefined);
|
|
374
|
-
if (largest) imagesByPage.set(page.pageNumber, largest);
|
|
366
|
+
screenshotsByPage = new Map();
|
|
367
|
+
for (const page of screenshotResult.pages) {
|
|
368
|
+
screenshotsByPage.set(page.pageNumber, {
|
|
369
|
+
data: page.data,
|
|
370
|
+
width: page.width,
|
|
371
|
+
height: page.height,
|
|
372
|
+
});
|
|
375
373
|
}
|
|
376
374
|
} finally {
|
|
377
375
|
await pdf.destroy();
|
|
@@ -379,12 +377,12 @@ async function importPdfPagesAsFullBleedSlides(args: {
|
|
|
379
377
|
|
|
380
378
|
// Source decks (e.g. Instagram carousel exports) are commonly portrait or
|
|
381
379
|
// square, not the deck editor's 16:9 default — match the canvas to the
|
|
382
|
-
// first
|
|
383
|
-
const
|
|
384
|
-
.map((page) =>
|
|
380
|
+
// first rendered page's own proportions instead of stretching/cropping it.
|
|
381
|
+
const firstScreenshot = pages
|
|
382
|
+
.map((page) => screenshotsByPage.get(page.num))
|
|
385
383
|
.find((image): image is NonNullable<typeof image> => Boolean(image));
|
|
386
|
-
const aspectRatio =
|
|
387
|
-
? nearestAspectRatio(
|
|
384
|
+
const aspectRatio = firstScreenshot
|
|
385
|
+
? nearestAspectRatio(firstScreenshot.width, firstScreenshot.height)
|
|
388
386
|
: undefined;
|
|
389
387
|
|
|
390
388
|
const ownerEmail = getRequestUserEmail();
|
|
@@ -394,9 +392,9 @@ async function importPdfPagesAsFullBleedSlides(args: {
|
|
|
394
392
|
.split(/\r?\n/)
|
|
395
393
|
.map((line) => line.trim())
|
|
396
394
|
.filter(Boolean);
|
|
397
|
-
const
|
|
395
|
+
const screenshot = screenshotsByPage.get(page.num);
|
|
398
396
|
|
|
399
|
-
if (!
|
|
397
|
+
if (!screenshot) {
|
|
400
398
|
const [content] = convertSectionsToSlides([
|
|
401
399
|
{ heading: lines[0] ?? `Page ${page.num}`, content: page.text },
|
|
402
400
|
]);
|
|
@@ -409,7 +407,7 @@ async function importPdfPagesAsFullBleedSlides(args: {
|
|
|
409
407
|
}
|
|
410
408
|
|
|
411
409
|
const uploadResult = await uploadFile({
|
|
412
|
-
data: Buffer.from(
|
|
410
|
+
data: Buffer.from(screenshot.data),
|
|
413
411
|
filename: `slide-import-${Date.now()}-p${page.num}.png`,
|
|
414
412
|
mimeType: "image/png",
|
|
415
413
|
ownerEmail: ownerEmail ?? undefined,
|
|
@@ -420,22 +418,9 @@ async function importPdfPagesAsFullBleedSlides(args: {
|
|
|
420
418
|
"File storage is not configured. Connect Builder.io (free tier available) or another upload provider before importing PDF slides.",
|
|
421
419
|
);
|
|
422
420
|
}
|
|
423
|
-
// pdf-parse breaks wrapped text across lines with no way to tell a
|
|
424
|
-
// wrapped title from a heading followed by a body paragraph. Treat a
|
|
425
|
-
// couple of short lines as one wrapped title (join them so the
|
|
426
|
-
// sentence isn't cut off); three or more lines as a heading plus body
|
|
427
|
-
// text, rendered with distinct sizes so they don't collapse into one
|
|
428
|
-
// flat paragraph.
|
|
429
|
-
const heading =
|
|
430
|
-
lines.length > 2 ? lines[0] : lines.join(" ") || undefined;
|
|
431
|
-
const subtitle = lines.length > 2 ? lines.slice(1).join(" ") : undefined;
|
|
432
421
|
return {
|
|
433
422
|
id: newSlideId(),
|
|
434
|
-
content:
|
|
435
|
-
uploadResult.url,
|
|
436
|
-
heading,
|
|
437
|
-
subtitle,
|
|
438
|
-
),
|
|
423
|
+
content: buildFullPageImageSlideHtml(uploadResult.url),
|
|
439
424
|
layout: "full-image",
|
|
440
425
|
notes: page.text,
|
|
441
426
|
};
|
|
@@ -459,65 +444,39 @@ function newSlideId(): string {
|
|
|
459
444
|
return `slide-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
460
445
|
}
|
|
461
446
|
|
|
462
|
-
// EMF/WMF (Windows metafiles) and TIFF are valid PPTX embed formats but
|
|
463
|
-
// browsers can't render them in an <img> tag — uploading and linking one
|
|
464
|
-
// would just produce a broken image icon.
|
|
465
|
-
const PPTX_BROWSER_RENDERABLE_IMAGE_MIME_TYPES = new Set([
|
|
466
|
-
"image/png",
|
|
467
|
-
"image/jpeg",
|
|
468
|
-
"image/gif",
|
|
469
|
-
"image/webp",
|
|
470
|
-
"image/svg+xml",
|
|
471
|
-
"image/bmp",
|
|
472
|
-
]);
|
|
473
|
-
|
|
474
447
|
async function buildPptxSlide(
|
|
475
448
|
slide: import("../server/handlers/import/pptx-parser.js").ParsedSlide,
|
|
476
449
|
slideIndex: number,
|
|
477
450
|
ownerEmail: string,
|
|
478
451
|
themeFont: string | undefined,
|
|
479
452
|
): Promise<{
|
|
480
|
-
slide: {
|
|
453
|
+
slide: {
|
|
454
|
+
id: string;
|
|
455
|
+
content: string;
|
|
456
|
+
layout: string;
|
|
457
|
+
notes?: string;
|
|
458
|
+
transition?: "instant" | "none" | "fade" | "slide" | "zoom";
|
|
459
|
+
splitByParagraph?: boolean;
|
|
460
|
+
};
|
|
481
461
|
imageSkippedCount: number;
|
|
482
462
|
}> {
|
|
483
463
|
const { convertToSlideHtml } =
|
|
484
464
|
await import("../server/handlers/import/html-converter.js");
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const imageUrl = uploadable
|
|
491
|
-
? await uploadFile({
|
|
492
|
-
data: Buffer.from(uploadable.data),
|
|
493
|
-
filename:
|
|
494
|
-
"pptx-import-" +
|
|
495
|
-
Date.now() +
|
|
496
|
-
"-s" +
|
|
497
|
-
slideIndex +
|
|
498
|
-
"-" +
|
|
499
|
-
uploadable.name,
|
|
500
|
-
mimeType: uploadable.mimeType,
|
|
501
|
-
ownerEmail,
|
|
502
|
-
recordAsset: false,
|
|
503
|
-
})
|
|
504
|
-
.then((result) => result?.url)
|
|
505
|
-
// A single slide's upload failing (network/API/rate-limit)
|
|
506
|
-
// shouldn't abort the whole deck replacement — fall back to a
|
|
507
|
-
// placeholder like an unsupported format would.
|
|
508
|
-
.catch(() => undefined)
|
|
509
|
-
: undefined;
|
|
465
|
+
const uploadedImages = await uploadPptxSlideImages({
|
|
466
|
+
slide,
|
|
467
|
+
slideIndex,
|
|
468
|
+
ownerEmail,
|
|
469
|
+
});
|
|
510
470
|
return {
|
|
511
471
|
slide: {
|
|
512
472
|
id: newSlideId(),
|
|
513
|
-
content: convertToSlideHtml(slide,
|
|
473
|
+
content: convertToSlideHtml(slide, uploadedImages.urls, themeFont),
|
|
514
474
|
layout: slide.layoutHint ?? "content",
|
|
515
475
|
notes: slide.notes,
|
|
476
|
+
...(slide.transition ? { transition: slide.transition } : {}),
|
|
477
|
+
...(slide.splitByParagraph ? { splitByParagraph: true } : {}),
|
|
516
478
|
},
|
|
517
|
-
|
|
518
|
-
// image on that slide is unconditionally dropped too — not just the
|
|
519
|
-
// first one when it's unsupported.
|
|
520
|
-
imageSkippedCount: Math.max(0, slide.images.length - (imageUrl ? 1 : 0)),
|
|
479
|
+
imageSkippedCount: uploadedImages.imageSkippedCount,
|
|
521
480
|
};
|
|
522
481
|
}
|
|
523
482
|
|
|
@@ -604,6 +563,8 @@ async function appendDeckSlides(
|
|
|
604
563
|
content: string;
|
|
605
564
|
layout: string;
|
|
606
565
|
notes?: string;
|
|
566
|
+
transition?: "instant" | "none" | "fade" | "slide" | "zoom";
|
|
567
|
+
splitByParagraph?: boolean;
|
|
607
568
|
}>,
|
|
608
569
|
source: string,
|
|
609
570
|
aspectRatio?: AspectRatio,
|
|
@@ -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 }) => {
|