@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
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ParsedElement,
|
|
3
|
+
ParsedParagraph,
|
|
4
|
+
ParsedSlide,
|
|
5
|
+
ParsedTextRun,
|
|
6
|
+
} from "./pptx-parser.js";
|
|
2
7
|
|
|
3
8
|
/** Escape HTML special characters. */
|
|
4
9
|
function esc(text: string): string {
|
|
@@ -47,6 +52,13 @@ export function buildFullBleedImageSlideHtml(
|
|
|
47
52
|
</div>`;
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
/** Render a rasterized source page without changing its layout or text. */
|
|
56
|
+
export function buildFullPageImageSlideHtml(imageUrl: string): string {
|
|
57
|
+
return `<div class="fmd-slide" style="position: relative; width: 100%; height: 100%; overflow: hidden; background: hsl(var(--background));">
|
|
58
|
+
<img src="${esc(imageUrl)}" alt="" style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;" />
|
|
59
|
+
</div>`;
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
/** Wrap text in formatting tags based on run properties. */
|
|
51
63
|
function formatRun(run: ParsedTextRun): string {
|
|
52
64
|
let text = esc(run.content);
|
|
@@ -63,7 +75,11 @@ const DEFAULT_IMPORT_FONT = "'Poppins', sans-serif";
|
|
|
63
75
|
function cssFontFamily(themeFont: string | undefined): string {
|
|
64
76
|
if (!themeFont) return DEFAULT_IMPORT_FONT;
|
|
65
77
|
const safeName = themeFont.replace(/["']/g, "").trim();
|
|
66
|
-
|
|
78
|
+
if (!safeName) return DEFAULT_IMPORT_FONT;
|
|
79
|
+
if (safeName.toLowerCase().startsWith("poppins")) {
|
|
80
|
+
return "'Poppins', sans-serif";
|
|
81
|
+
}
|
|
82
|
+
return `'${safeName}', sans-serif`;
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
/**
|
|
@@ -107,9 +123,13 @@ function groupIntoParagraphs(texts: ParsedTextRun[]): ParsedTextRun[][] {
|
|
|
107
123
|
*/
|
|
108
124
|
export function convertToSlideHtml(
|
|
109
125
|
slide: ParsedSlide,
|
|
110
|
-
|
|
126
|
+
imageUrls?: string | Record<string, string>,
|
|
111
127
|
themeFont?: string,
|
|
112
128
|
): string {
|
|
129
|
+
if (slide.elements?.length) {
|
|
130
|
+
return buildFidelitySlide(slide, imageUrls, themeFont);
|
|
131
|
+
}
|
|
132
|
+
|
|
113
133
|
const paragraphs = groupIntoParagraphs(slide.texts);
|
|
114
134
|
const fontFamily = cssFontFamily(themeFont);
|
|
115
135
|
|
|
@@ -117,7 +137,12 @@ export function convertToSlideHtml(
|
|
|
117
137
|
// has no room to show it, which is how imports used to silently drop
|
|
118
138
|
// photos from otherwise short/title-shaped slides.
|
|
119
139
|
if (slide.images.length > 0) {
|
|
120
|
-
return buildImageSlide(
|
|
140
|
+
return buildImageSlide(
|
|
141
|
+
paragraphs,
|
|
142
|
+
slide,
|
|
143
|
+
typeof imageUrls === "string" ? imageUrls : undefined,
|
|
144
|
+
fontFamily,
|
|
145
|
+
);
|
|
121
146
|
}
|
|
122
147
|
|
|
123
148
|
if (slide.layoutHint === "title" || paragraphs.length <= 2) {
|
|
@@ -127,6 +152,194 @@ export function convertToSlideHtml(
|
|
|
127
152
|
return buildContentSlide(paragraphs, slide, fontFamily);
|
|
128
153
|
}
|
|
129
154
|
|
|
155
|
+
const DEFAULT_SLIDE_WIDTH_EMU = 9144000;
|
|
156
|
+
const DEFAULT_SLIDE_HEIGHT_EMU = 5143500;
|
|
157
|
+
const CSS_PX_PER_POINT = 96 / 72;
|
|
158
|
+
const DEFAULT_PPTX_BACKGROUND = "#000000"; // guard:allow-raw-color - preserve PPTX black when no background is declared
|
|
159
|
+
const DEFAULT_PPTX_FOREGROUND = "#ffffff"; // guard:allow-raw-color - preserve PPTX white when no run color is declared
|
|
160
|
+
|
|
161
|
+
function buildFidelitySlide(
|
|
162
|
+
slide: ParsedSlide,
|
|
163
|
+
imageUrls: string | Record<string, string> | undefined,
|
|
164
|
+
themeFont: string | undefined,
|
|
165
|
+
): string {
|
|
166
|
+
const widthEmu = slide.widthEmu || DEFAULT_SLIDE_WIDTH_EMU;
|
|
167
|
+
const heightEmu = slide.heightEmu || DEFAULT_SLIDE_HEIGHT_EMU;
|
|
168
|
+
const background = slide.backgroundColor ?? DEFAULT_PPTX_BACKGROUND;
|
|
169
|
+
const gridStyle = slide.backgroundGrid
|
|
170
|
+
? `background-image:linear-gradient(to right, ${esc(slide.backgroundGrid.color)} 0 ${Math.max(0.5, toSlidePxX(slide.backgroundGrid.lineWidthEmu, widthEmu))}px, transparent ${Math.max(0.5, toSlidePxX(slide.backgroundGrid.lineWidthEmu, widthEmu))}px),linear-gradient(to bottom, ${esc(slide.backgroundGrid.color)} 0 ${Math.max(0.5, toSlidePxY(slide.backgroundGrid.lineWidthEmu, heightEmu))}px, transparent ${Math.max(0.5, toSlidePxY(slide.backgroundGrid.lineWidthEmu, heightEmu))}px);background-size:${toSlidePxX(slide.backgroundGrid.stepXEmu, widthEmu)}px ${toSlidePxY(slide.backgroundGrid.stepYEmu, heightEmu)}px;background-position:${toSlidePxX(slide.backgroundGrid.offsetXEmu, widthEmu)}px ${toSlidePxY(slide.backgroundGrid.offsetYEmu, heightEmu)}px;background-repeat:repeat;`
|
|
171
|
+
: "";
|
|
172
|
+
const elements = slide.elements ?? [];
|
|
173
|
+
const html = elements
|
|
174
|
+
.map((element, index) =>
|
|
175
|
+
buildFidelityElement(
|
|
176
|
+
element,
|
|
177
|
+
index,
|
|
178
|
+
widthEmu,
|
|
179
|
+
heightEmu,
|
|
180
|
+
imageUrls,
|
|
181
|
+
themeFont,
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
.join("\n");
|
|
185
|
+
|
|
186
|
+
return `<div class="fmd-slide fmd-imported-pptx" data-imported-pptx="true" data-slide-width-emu="${widthEmu}" data-slide-height-emu="${heightEmu}" style="position: relative; width: 100%; height: 100%; overflow: hidden; background: ${esc(background)};${gridStyle} font-family: ${cssFontFamily(themeFont)};">${html}
|
|
187
|
+
</div>`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildFidelityElement(
|
|
191
|
+
element: ParsedElement,
|
|
192
|
+
index: number,
|
|
193
|
+
widthEmu: number,
|
|
194
|
+
heightEmu: number,
|
|
195
|
+
imageUrls: string | Record<string, string> | undefined,
|
|
196
|
+
themeFont: string | undefined,
|
|
197
|
+
): string {
|
|
198
|
+
const position = `position: absolute; left: ${toSlidePxX(element.x, widthEmu)}px; top: ${toSlidePxY(element.y, heightEmu)}px; width: ${toSlidePxX(element.width, widthEmu)}px; height: ${toSlidePxY(element.height, heightEmu)}px; z-index: ${index}; box-sizing: border-box;`;
|
|
199
|
+
const rotation = element.rotation
|
|
200
|
+
? ` transform: rotate(${element.rotation}deg); transform-origin: center center;`
|
|
201
|
+
: "";
|
|
202
|
+
const objectId = ` data-slide-object-id="${esc(element.id)}"`;
|
|
203
|
+
|
|
204
|
+
if (element.kind === "image") {
|
|
205
|
+
const url = imageUrlForElement(element, imageUrls);
|
|
206
|
+
const imageStyle = imageRenderStyle(element);
|
|
207
|
+
return `<div class="fmd-pptx-image" data-pptx-element-kind="image" data-pptx-image-name="${esc(element.image?.name ?? "image")}"${objectId} style="${position}${rotation} overflow: hidden;">${url ? `<img src="${esc(url)}" alt="" style="${imageStyle}" />` : `<div class="fmd-img-placeholder" style="width:100%;height:100%;">Imported image: ${esc(element.image?.name ?? "image")}</div>`}</div>`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const decoration = shapeDecoration(element, widthEmu);
|
|
211
|
+
if (element.kind === "shape") {
|
|
212
|
+
return `<div class="fmd-pptx-shape" data-pptx-element-kind="shape"${objectId} style="${position}${rotation}${decoration}"></div>`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const textStyle = textBoxStyle(element, widthEmu, heightEmu, themeFont);
|
|
216
|
+
const defaultFontWeight = element.placeholderType === "title" ? 700 : 400;
|
|
217
|
+
const paragraphs = (element.paragraphs ?? [])
|
|
218
|
+
.map((paragraph, paragraphIndex) =>
|
|
219
|
+
buildFidelityParagraph(
|
|
220
|
+
paragraph,
|
|
221
|
+
paragraphIndex,
|
|
222
|
+
widthEmu,
|
|
223
|
+
themeFont,
|
|
224
|
+
defaultFontWeight,
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
.join("\n");
|
|
228
|
+
return `<div class="fmd-pptx-text" data-pptx-element-kind="text"${objectId} style="${position}${rotation}${decoration}${textStyle}">${paragraphs}</div>`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function toSlidePxX(valueEmu: number, slideWidthEmu: number): number {
|
|
232
|
+
return Math.round((valueEmu / slideWidthEmu) * 960 * 1000) / 1000;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function toSlidePxY(valueEmu: number, slideHeightEmu: number): number {
|
|
236
|
+
return Math.round((valueEmu / slideHeightEmu) * 540 * 1000) / 1000;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function imageUrlForElement(
|
|
240
|
+
element: ParsedElement,
|
|
241
|
+
imageUrls: string | Record<string, string> | undefined,
|
|
242
|
+
): string | undefined {
|
|
243
|
+
if (typeof imageUrls === "string") return imageUrls;
|
|
244
|
+
return imageUrls?.[element.id];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function imageRenderStyle(element: ParsedElement): string {
|
|
248
|
+
const crop = element.image?.crop;
|
|
249
|
+
if (!crop) return "display:block;width:100%;height:100%;object-fit:fill;";
|
|
250
|
+
const visibleWidth = Math.max(0.001, 1 - crop.left - crop.right);
|
|
251
|
+
const visibleHeight = Math.max(0.001, 1 - crop.top - crop.bottom);
|
|
252
|
+
return `display:block;position:absolute;left:${(-crop.left / visibleWidth) * 100}%;top:${(-crop.top / visibleHeight) * 100}%;width:${(1 / visibleWidth) * 100}%;height:${(1 / visibleHeight) * 100}%;object-fit:fill;`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function shapeDecoration(element: ParsedElement, widthEmu: number): string {
|
|
256
|
+
const fill = element.fill ? `background: ${esc(element.fill)};` : "";
|
|
257
|
+
const line = element.lineColor
|
|
258
|
+
? `border: ${Math.max(1, toSlidePxX(element.lineWidth ?? 12700, widthEmu))}px solid ${esc(element.lineColor)};`
|
|
259
|
+
: "";
|
|
260
|
+
const radius = element.shapeType === "roundRect" ? "border-radius: 6px;" : "";
|
|
261
|
+
return `${fill}${line}${radius}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function textBoxStyle(
|
|
265
|
+
element: ParsedElement,
|
|
266
|
+
widthEmu: number,
|
|
267
|
+
heightEmu: number,
|
|
268
|
+
themeFont: string | undefined,
|
|
269
|
+
): string {
|
|
270
|
+
const padding = element.padding;
|
|
271
|
+
const left = padding ? toSlidePxX(padding.left, widthEmu) : 0;
|
|
272
|
+
const right = padding ? toSlidePxX(padding.right, widthEmu) : 0;
|
|
273
|
+
const top = padding ? toSlidePxY(padding.top, heightEmu) : 0;
|
|
274
|
+
const bottom = padding ? toSlidePxY(padding.bottom, heightEmu) : 0;
|
|
275
|
+
const align = element.paragraphs?.[0]?.alignment ?? "left";
|
|
276
|
+
const vertical =
|
|
277
|
+
element.verticalAlign === "middle"
|
|
278
|
+
? "justify-content:center;"
|
|
279
|
+
: element.verticalAlign === "bottom"
|
|
280
|
+
? "justify-content:flex-end;"
|
|
281
|
+
: "justify-content:flex-start;";
|
|
282
|
+
return `display:flex;flex-direction:column;${vertical}padding:${top}px ${right}px ${bottom}px ${left}px;font-family:${cssFontFamily(themeFont)};text-align:${align};overflow:visible;`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function buildFidelityParagraph(
|
|
286
|
+
paragraph: ParsedParagraph,
|
|
287
|
+
paragraphIndex: number,
|
|
288
|
+
widthEmu: number,
|
|
289
|
+
themeFont: string | undefined,
|
|
290
|
+
defaultFontWeight: number,
|
|
291
|
+
): string {
|
|
292
|
+
const firstRun = paragraph.runs[0];
|
|
293
|
+
const fontSize = (firstRun?.fontSize ?? 18) * CSS_PX_PER_POINT;
|
|
294
|
+
const lineHeight = paragraph.lineSpacing ?? 1.2;
|
|
295
|
+
const bullet = paragraph.bulletChar
|
|
296
|
+
? `<span aria-hidden="true" style="display:inline-block;width:${fontSize * 0.75}px;min-width:${fontSize * 0.75}px;margin-right:${fontSize * 0.65}px;color:${esc(paragraph.bulletColor ?? firstRun?.color ?? DEFAULT_PPTX_FOREGROUND)};font-family:${cssFontFamily(paragraph.bulletFontFamily ?? themeFont)};font-size:${(paragraph.bulletSize ?? firstRun?.fontSize ?? 18) * CSS_PX_PER_POINT}px;">${esc(paragraph.bulletChar)}</span>`
|
|
297
|
+
: "";
|
|
298
|
+
const marginLeft = paragraph.marginLeftEmu
|
|
299
|
+
? toSlidePxX(paragraph.marginLeftEmu, widthEmu)
|
|
300
|
+
: 0;
|
|
301
|
+
const indent = paragraph.indentEmu
|
|
302
|
+
? toSlidePxX(paragraph.indentEmu, widthEmu)
|
|
303
|
+
: 0;
|
|
304
|
+
const spacingBefore = paragraph.spaceBeforePt ?? 0;
|
|
305
|
+
const spacingAfter = paragraph.spaceAfterPt ?? 0;
|
|
306
|
+
const bulletMargin = paragraph.bulletChar ? `margin-left:${indent}px;` : "";
|
|
307
|
+
const text = paragraph.runs
|
|
308
|
+
.map((run) => formatFidelityRun(run, themeFont, defaultFontWeight))
|
|
309
|
+
.join("");
|
|
310
|
+
return `<p data-pptx-paragraph="${paragraphIndex}" style="display:block;flex:0 0 auto;text-align:${paragraph.alignment ?? "left"};white-space:pre-wrap;margin:${spacingBefore * CSS_PX_PER_POINT}px 0 ${spacingAfter * CSS_PX_PER_POINT}px;line-height:${lineHeight};font-size:${fontSize}px;min-height:${fontSize * lineHeight}px;padding-left:${marginLeft}px;text-indent:${paragraph.bulletChar ? 0 : indent}px;">${bullet.replace("display:inline-block;", `display:inline-block;${bulletMargin}`)}${text}</p>`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function formatFidelityRun(
|
|
314
|
+
run: ParsedTextRun,
|
|
315
|
+
themeFont: string | undefined,
|
|
316
|
+
defaultFontWeight = 400,
|
|
317
|
+
): string {
|
|
318
|
+
const styles = [
|
|
319
|
+
`font-size:${(run.fontSize ?? 18) * CSS_PX_PER_POINT}px`,
|
|
320
|
+
`font-family:${cssFontFamily(run.fontFamily ?? themeFont)}`,
|
|
321
|
+
`color:${esc(run.color ?? DEFAULT_PPTX_FOREGROUND)}`,
|
|
322
|
+
`font-weight:${run.bold ? 700 : fontWeightForFamily(run.fontFamily, defaultFontWeight)}`,
|
|
323
|
+
`font-style:${run.italic ? "italic" : "normal"}`,
|
|
324
|
+
`text-decoration:${run.underline ? "underline" : "none"}`,
|
|
325
|
+
].join(";");
|
|
326
|
+
return `<span style="${styles};">${esc(run.content)}</span>`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function fontWeightForFamily(
|
|
330
|
+
fontFamily: string | undefined,
|
|
331
|
+
fallback: number,
|
|
332
|
+
): number {
|
|
333
|
+
const normalized = fontFamily?.toLowerCase() ?? "";
|
|
334
|
+
if (!normalized) return fallback;
|
|
335
|
+
if (/(?:semi|demi)bold|semibold/.test(normalized)) return 600;
|
|
336
|
+
if (/extra[- ]?bold|heavy/.test(normalized)) return 800;
|
|
337
|
+
if (/bold/.test(normalized)) return 700;
|
|
338
|
+
if (/medium/.test(normalized)) return 500;
|
|
339
|
+
if (/light|thin/.test(normalized)) return 300;
|
|
340
|
+
return 400;
|
|
341
|
+
}
|
|
342
|
+
|
|
130
343
|
function buildTitleSlide(
|
|
131
344
|
paragraphs: ParsedTextRun[][],
|
|
132
345
|
slide: ParsedSlide,
|
|
@@ -166,7 +379,7 @@ function buildContentSlide(
|
|
|
166
379
|
})
|
|
167
380
|
.join("\n");
|
|
168
381
|
|
|
169
|
-
bulletsHtml = `\n <div style="display: flex; flex-direction: column; gap: 20px;">
|
|
382
|
+
bulletsHtml = `\n <div class="fmd-animation-container" style="display: flex; flex-direction: column; gap: 20px;">
|
|
170
383
|
${bulletItems}
|
|
171
384
|
</div>`;
|
|
172
385
|
}
|
|
@@ -232,15 +445,20 @@ function buildOverlayImageSlide(
|
|
|
232
445
|
const headingHtml = headingPara.map(formatRun).join(" ") || "Slide";
|
|
233
446
|
|
|
234
447
|
const captionParas = paragraphs.slice(1);
|
|
235
|
-
const captionHtml = captionParas
|
|
236
|
-
|
|
237
|
-
|
|
448
|
+
const captionHtml = captionParas.length
|
|
449
|
+
? `<div class="fmd-animation-container" style="display: flex; flex-direction: column; gap: 8px;">${captionParas
|
|
450
|
+
.map(
|
|
451
|
+
(para) =>
|
|
452
|
+
`<p style="font-size: 18px; color: rgba(255,255,255,0.75); /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.5; margin: 0;">${para.map(formatRun).join(" ")}</p>`,
|
|
453
|
+
)
|
|
454
|
+
.join("\n")}</div>`
|
|
455
|
+
: "";
|
|
238
456
|
|
|
239
457
|
return `<div class="fmd-slide" style="position: relative; width: 100%; height: 100%; overflow: hidden;">
|
|
240
458
|
<img src="${esc(imageUrl)}" alt="" style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;" />
|
|
241
459
|
<div style="position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.35) 55%, rgba(0,0,0,0) 80%);"></div>
|
|
242
460
|
<div style="position: absolute; left: 0; right: 0; bottom: 0; padding: 56px 70px; font-family: ${fontFamily};">
|
|
243
|
-
<h2 style="font-size: 40px; font-weight: 900; color: #fff; line-height: 1.15; letter-spacing: -1px; margin: 0 0 ${captionHtml ? "12px" : "0"} 0;">${headingHtml}</h2>${captionHtml ? `\n
|
|
461
|
+
<h2 style="font-size: 40px; font-weight: 900; color: #fff; /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.15; letter-spacing: -1px; margin: 0 0 ${captionHtml ? "12px" : "0"} 0;">${headingHtml}</h2>${captionHtml ? `\n ${captionHtml}` : ""}
|
|
244
462
|
</div>
|
|
245
463
|
</div>`;
|
|
246
464
|
}
|
|
@@ -256,9 +474,14 @@ function buildStackedImageSlide(
|
|
|
256
474
|
const headingText = headingPara.map(formatRun).join(" ") || "Slide";
|
|
257
475
|
|
|
258
476
|
const captionParas = paragraphs.slice(1);
|
|
259
|
-
const captionText = captionParas
|
|
260
|
-
|
|
261
|
-
|
|
477
|
+
const captionText = captionParas.length
|
|
478
|
+
? `<div class="fmd-animation-container" style="display: flex; flex-direction: column; gap: 8px;">${captionParas
|
|
479
|
+
.map(
|
|
480
|
+
(para) =>
|
|
481
|
+
`<p style="font-size: 16px; color: rgba(255,255,255,0.7); /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.5; margin: 0;">${para.map(formatRun).join(" ")}</p>`,
|
|
482
|
+
)
|
|
483
|
+
.join("\n")}</div>`
|
|
484
|
+
: "";
|
|
262
485
|
|
|
263
486
|
const imageName = slide.images[0]?.name ?? "image";
|
|
264
487
|
// Size the box to the image's own placed aspect ratio instead of a fixed
|
|
@@ -278,7 +501,7 @@ function buildStackedImageSlide(
|
|
|
278
501
|
|
|
279
502
|
return `<div class="fmd-slide" style="padding: 64px 90px; display: flex; flex-direction: column; justify-content: flex-start; font-family: ${fontFamily};">
|
|
280
503
|
${imageHtml}
|
|
281
|
-
<h2 style="font-size: 32px; font-weight: 900; color: #fff; line-height: 1.2; letter-spacing: -0.5px; margin: 0 0 12px 0;">${headingText}</h2>${captionText ? `\n
|
|
504
|
+
<h2 style="font-size: 32px; font-weight: 900; color: #fff; /* guard:allow-raw-color - standalone imported slide HTML uses fixed contrast colors */ line-height: 1.2; letter-spacing: -0.5px; margin: 0 0 12px 0;">${headingText}</h2>${captionText ? `\n ${captionText}` : ""}
|
|
282
505
|
</div>`;
|
|
283
506
|
}
|
|
284
507
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { uploadFile } from "@agent-native/core/file-upload";
|
|
2
|
+
|
|
3
|
+
import { storeLocalImportedAsset } from "../../lib/import-asset-storage.js";
|
|
4
|
+
import type { ParsedElement, ParsedSlide } from "./pptx-parser.js";
|
|
5
|
+
|
|
6
|
+
const BROWSER_RENDERABLE_IMAGE_MIME_TYPES = new Set([
|
|
7
|
+
"image/png",
|
|
8
|
+
"image/jpeg",
|
|
9
|
+
"image/gif",
|
|
10
|
+
"image/webp",
|
|
11
|
+
"image/svg+xml",
|
|
12
|
+
"image/bmp",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export async function uploadPptxSlideImages(args: {
|
|
16
|
+
slide: ParsedSlide;
|
|
17
|
+
slideIndex: number;
|
|
18
|
+
ownerEmail: string;
|
|
19
|
+
}): Promise<{ urls: Record<string, string>; imageSkippedCount: number }> {
|
|
20
|
+
const imageElements = (args.slide.elements ?? []).filter(
|
|
21
|
+
(
|
|
22
|
+
element,
|
|
23
|
+
): element is ParsedElement & {
|
|
24
|
+
kind: "image";
|
|
25
|
+
image: NonNullable<ParsedElement["image"]>;
|
|
26
|
+
} => element.kind === "image" && Boolean(element.image),
|
|
27
|
+
);
|
|
28
|
+
const urls: Record<string, string> = {};
|
|
29
|
+
|
|
30
|
+
for (const [imageIndex, element] of imageElements.entries()) {
|
|
31
|
+
const image = element.image;
|
|
32
|
+
if (!BROWSER_RENDERABLE_IMAGE_MIME_TYPES.has(image.mimeType)) continue;
|
|
33
|
+
const filename = `pptx-import-${Date.now()}-s${args.slideIndex}-i${imageIndex}-${image.name}`;
|
|
34
|
+
let url: string | undefined;
|
|
35
|
+
try {
|
|
36
|
+
const result = await uploadFile({
|
|
37
|
+
data: Buffer.from(image.data),
|
|
38
|
+
filename,
|
|
39
|
+
mimeType: image.mimeType,
|
|
40
|
+
ownerEmail: args.ownerEmail,
|
|
41
|
+
recordAsset: false,
|
|
42
|
+
});
|
|
43
|
+
url = result?.url;
|
|
44
|
+
} catch {
|
|
45
|
+
url = undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!url) {
|
|
49
|
+
url =
|
|
50
|
+
(await storeLocalImportedAsset({
|
|
51
|
+
email: args.ownerEmail,
|
|
52
|
+
filename,
|
|
53
|
+
mimeType: image.mimeType,
|
|
54
|
+
data: image.data,
|
|
55
|
+
})) ?? undefined;
|
|
56
|
+
}
|
|
57
|
+
if (url) urls[element.id] = url;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
urls,
|
|
62
|
+
imageSkippedCount: Math.max(
|
|
63
|
+
0,
|
|
64
|
+
imageElements.length - Object.keys(urls).length,
|
|
65
|
+
),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -9,9 +9,11 @@ import {
|
|
|
9
9
|
import { nanoid } from "nanoid";
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
MAX_FIG_REFERENCE_FILE_BYTES,
|
|
13
|
+
MAX_REFERENCE_FILE_BYTES,
|
|
12
14
|
SLIDES_REFERENCE_FILE_ERROR_LABEL,
|
|
13
15
|
isSlidesReferenceFileExtension,
|
|
14
|
-
} from "../../shared/upload-types";
|
|
16
|
+
} from "../../shared/upload-types.js";
|
|
15
17
|
import { tenantUploadDir } from "../lib/tenant-files.js";
|
|
16
18
|
import {
|
|
17
19
|
isHostedSlidesRuntime,
|
|
@@ -23,8 +25,10 @@ import {
|
|
|
23
25
|
withSlidesRequestContext,
|
|
24
26
|
} from "./request-auth-context.js";
|
|
25
27
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
+
export {
|
|
29
|
+
MAX_FIG_REFERENCE_FILE_BYTES,
|
|
30
|
+
MAX_REFERENCE_FILE_BYTES,
|
|
31
|
+
} from "../../shared/upload-types.js";
|
|
28
32
|
const FIG_LOCAL_COPY_SIGNATURE = new Uint8Array([
|
|
29
33
|
0x66, 0x69, 0x67, 0x2d, 0x6b, 0x69, 0x77, 0x69,
|
|
30
34
|
]);
|
|
@@ -2,10 +2,13 @@ import path from "path";
|
|
|
2
2
|
|
|
3
3
|
import type { AgentChatAttachment } from "@agent-native/core/server";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
isSlidesReferenceFileExtension,
|
|
7
|
+
MAX_REFERENCE_FILE_BYTES,
|
|
8
|
+
} from "../../shared/upload-types.js";
|
|
6
9
|
import { saveUploadedReferenceFile } from "../handlers/uploads.js";
|
|
7
10
|
|
|
8
|
-
const MAX_CHAT_UPLOAD_BYTES =
|
|
11
|
+
const MAX_CHAT_UPLOAD_BYTES = MAX_REFERENCE_FILE_BYTES;
|
|
9
12
|
const MAX_INLINE_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
10
13
|
|
|
11
14
|
function decodeDataUrl(data: string | undefined): {
|
|
@@ -117,7 +120,7 @@ export async function prepareSlidesChatAttachments(args: {
|
|
|
117
120
|
"File handling rules:",
|
|
118
121
|
"- If the request refers to the current or visible deck, call `view-screen` first to confirm the active deckId, then pass that deckId to import or slide-edit actions.",
|
|
119
122
|
'- PPTX files: call `import-pptx --filePath "<path>" --deckId <deckId>` when updating the visible deck, or omit deckId only when the user explicitly wants a new deck.',
|
|
120
|
-
'- PDF and DOCX files: call `import-file --filePath "<path>" --format auto --deckId <deckId>` and use the returned extracted text as source material before creating slides.
|
|
123
|
+
'- PDF and DOCX files: call `import-file --filePath "<path>" --format auto --deckId <deckId>` and use the returned extracted text as source material before creating editable slides. For a visual PDF that the user wants preserved, beautified, or restyled from its original layout, pass `--importIntoDeck true` so each page is imported source-faithfully first; the source text is persisted in slide notes for later inspection.',
|
|
121
124
|
'- Figma `.fig` files: call `import-file --filePath "<path>" --format fig` to start Builder design-system indexing. Do not create a local design system directly from the upload.',
|
|
122
125
|
"- For deck-generation requests, start mutating promptly: create or update the first slide as soon as source material is extracted, then continue slide-by-slide with add-slide/update-slide.",
|
|
123
126
|
'- Image files with an embeddable URL can be inserted directly into slide HTML as `<img src="...">` or used as visual references.',
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* and preview them without the app having sent anything yet.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
defineTransactionalEmail,
|
|
10
|
+
getTransactionalEmail,
|
|
11
|
+
} from "@agent-native/core/email-catalog";
|
|
9
12
|
|
|
10
13
|
import { renderDeckCommentEmail } from "./comment-notifications.js";
|
|
11
14
|
|
|
@@ -14,7 +17,9 @@ export const SLIDES_DECK_COMMENT_EMAIL_ID = "slides.deck-comment";
|
|
|
14
17
|
let registered = false;
|
|
15
18
|
|
|
16
19
|
export function registerSlidesEmails(): void {
|
|
17
|
-
|
|
20
|
+
// Nitro can re-evaluate this module during dev HMR while the core registry
|
|
21
|
+
// remains alive. Treat the existing Slides-owned entry as already registered.
|
|
22
|
+
if (registered || getTransactionalEmail(SLIDES_DECK_COMMENT_EMAIL_ID)) return;
|
|
18
23
|
registered = true;
|
|
19
24
|
|
|
20
25
|
defineTransactionalEmail({
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function formatGoogleOAuthError(error: unknown): string {
|
|
2
|
+
const rawMessage =
|
|
3
|
+
error instanceof Error ? error.message : String(error || "Unknown error");
|
|
4
|
+
const message = rawMessage.replace(/\s+/g, " ").trim();
|
|
5
|
+
const normalized = message.toLowerCase();
|
|
6
|
+
|
|
7
|
+
if (
|
|
8
|
+
normalized.includes("insufficient permission") ||
|
|
9
|
+
normalized.includes("insufficient_scope")
|
|
10
|
+
) {
|
|
11
|
+
return "Google Docs access was denied. Connect again and approve the requested Google Drive file permission.";
|
|
12
|
+
}
|
|
13
|
+
if (
|
|
14
|
+
normalized.includes("redirect_uri_mismatch") ||
|
|
15
|
+
normalized.includes("redirect uri")
|
|
16
|
+
) {
|
|
17
|
+
return "Google rejected the redirect URI. Add the current Slides callback URL to the Google OAuth client and try again.";
|
|
18
|
+
}
|
|
19
|
+
if (
|
|
20
|
+
normalized.includes("access_denied") ||
|
|
21
|
+
normalized.includes("user denied") ||
|
|
22
|
+
normalized === "denied"
|
|
23
|
+
) {
|
|
24
|
+
return "Google access was canceled or denied. Connect Google again and approve access.";
|
|
25
|
+
}
|
|
26
|
+
if (
|
|
27
|
+
normalized.includes("invalid_grant") ||
|
|
28
|
+
normalized.includes("connection expired") ||
|
|
29
|
+
normalized.includes("token has been expired")
|
|
30
|
+
) {
|
|
31
|
+
return "The Google connection expired. Connect Google again.";
|
|
32
|
+
}
|
|
33
|
+
if (
|
|
34
|
+
normalized.includes("invalid_client") ||
|
|
35
|
+
normalized.includes("unauthorized_client")
|
|
36
|
+
) {
|
|
37
|
+
return "Google rejected the OAuth client configuration. Check the Slides Google OAuth credentials and authorized redirect URI.";
|
|
38
|
+
}
|
|
39
|
+
if (
|
|
40
|
+
normalized.includes("unverified") ||
|
|
41
|
+
normalized.includes("not verified") ||
|
|
42
|
+
normalized.includes("test user") ||
|
|
43
|
+
normalized.includes("sensitive app") ||
|
|
44
|
+
normalized.includes("access blocked")
|
|
45
|
+
) {
|
|
46
|
+
return "Google blocked this app because its OAuth consent screen is unverified or this account is not an allowed test user.";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return message.slice(0, 240) || "Google OAuth failed. Try connecting again.";
|
|
50
|
+
}
|
|
@@ -17,6 +17,7 @@ const USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
|
17
17
|
export const GOOGLE_DOCS_PROVIDER = "google-docs";
|
|
18
18
|
export const GOOGLE_DOCS_SCOPES = [
|
|
19
19
|
"https://www.googleapis.com/auth/drive.file",
|
|
20
|
+
"https://www.googleapis.com/auth/presentations.readonly",
|
|
20
21
|
"https://www.googleapis.com/auth/userinfo.email",
|
|
21
22
|
"https://www.googleapis.com/auth/userinfo.profile",
|
|
22
23
|
];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
decryptSecretValue,
|
|
7
|
+
encryptSecretValue,
|
|
8
|
+
} from "@agent-native/core/secrets/crypto";
|
|
9
|
+
import { getConfiguredAppBasePath } from "@agent-native/core/server";
|
|
10
|
+
|
|
11
|
+
import { isHostedSlidesRuntime, tenantFileKey } from "./tenant-files.js";
|
|
12
|
+
|
|
13
|
+
const IMPORT_ASSET_PREFIX = "slides-import-asset:v1:";
|
|
14
|
+
|
|
15
|
+
export interface LocalImportedAssetDescriptor {
|
|
16
|
+
kind: "slides-import-asset";
|
|
17
|
+
version: 1;
|
|
18
|
+
ownerKey: string;
|
|
19
|
+
id: string;
|
|
20
|
+
filename: string;
|
|
21
|
+
mimeType: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isLocalImportAssetFallbackAvailable(): boolean {
|
|
25
|
+
return !isHostedSlidesRuntime() && process.env.NODE_ENV !== "production";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function storeLocalImportedAsset(args: {
|
|
29
|
+
email: string;
|
|
30
|
+
filename: string;
|
|
31
|
+
mimeType: string;
|
|
32
|
+
data: Uint8Array;
|
|
33
|
+
}): Promise<string | null> {
|
|
34
|
+
if (!isLocalImportAssetFallbackAvailable()) return null;
|
|
35
|
+
|
|
36
|
+
const id = randomUUID();
|
|
37
|
+
const safeFilename = path
|
|
38
|
+
.basename(args.filename)
|
|
39
|
+
.replace(/[^a-zA-Z0-9_.-]/g, "-");
|
|
40
|
+
const directory = path.join(
|
|
41
|
+
process.cwd(),
|
|
42
|
+
"data",
|
|
43
|
+
"import-assets",
|
|
44
|
+
tenantFileKey(args.email),
|
|
45
|
+
);
|
|
46
|
+
await mkdir(directory, { recursive: true });
|
|
47
|
+
await writeFile(path.join(directory, `${id}-${safeFilename}`), args.data);
|
|
48
|
+
|
|
49
|
+
const descriptor: LocalImportedAssetDescriptor = {
|
|
50
|
+
kind: "slides-import-asset",
|
|
51
|
+
version: 1,
|
|
52
|
+
ownerKey: tenantFileKey(args.email),
|
|
53
|
+
id,
|
|
54
|
+
filename: safeFilename,
|
|
55
|
+
mimeType: args.mimeType,
|
|
56
|
+
};
|
|
57
|
+
const token = `${IMPORT_ASSET_PREFIX}${encryptSecretValue(
|
|
58
|
+
JSON.stringify(descriptor),
|
|
59
|
+
)}`;
|
|
60
|
+
return `${getConfiguredAppBasePath()}/api/import-assets/${encodeURIComponent(token)}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function decodeLocalImportedAssetToken(
|
|
64
|
+
token: string,
|
|
65
|
+
): LocalImportedAssetDescriptor {
|
|
66
|
+
if (!token.startsWith(IMPORT_ASSET_PREFIX)) {
|
|
67
|
+
throw new Error("Invalid imported asset token");
|
|
68
|
+
}
|
|
69
|
+
const descriptor = JSON.parse(
|
|
70
|
+
decryptSecretValue(token.slice(IMPORT_ASSET_PREFIX.length)),
|
|
71
|
+
) as LocalImportedAssetDescriptor;
|
|
72
|
+
if (
|
|
73
|
+
descriptor?.kind !== "slides-import-asset" ||
|
|
74
|
+
descriptor.version !== 1 ||
|
|
75
|
+
typeof descriptor.ownerKey !== "string" ||
|
|
76
|
+
!/^[a-f0-9]{24}$/.test(descriptor.ownerKey) ||
|
|
77
|
+
typeof descriptor.id !== "string" ||
|
|
78
|
+
!/^[0-9a-f-]{36}$/.test(descriptor.id) ||
|
|
79
|
+
typeof descriptor.filename !== "string" ||
|
|
80
|
+
descriptor.filename !== path.basename(descriptor.filename) ||
|
|
81
|
+
typeof descriptor.mimeType !== "string"
|
|
82
|
+
) {
|
|
83
|
+
throw new Error("Invalid imported asset descriptor");
|
|
84
|
+
}
|
|
85
|
+
return descriptor;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Read a local development import asset after rechecking its owner scope. */
|
|
89
|
+
export async function readLocalImportedAsset(args: {
|
|
90
|
+
token: string;
|
|
91
|
+
email: string;
|
|
92
|
+
}): Promise<{ data: Uint8Array; mimeType: string } | null> {
|
|
93
|
+
if (!isLocalImportAssetFallbackAvailable()) return null;
|
|
94
|
+
|
|
95
|
+
let descriptor: LocalImportedAssetDescriptor;
|
|
96
|
+
try {
|
|
97
|
+
descriptor = decodeLocalImportedAssetToken(decodeURIComponent(args.token));
|
|
98
|
+
} catch {
|
|
99
|
+
// coercion-ok: malformed tokens represent an absent local asset, not a successful read.
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (descriptor.ownerKey !== tenantFileKey(args.email)) return null;
|
|
103
|
+
|
|
104
|
+
const directory = path.resolve(
|
|
105
|
+
process.cwd(),
|
|
106
|
+
"data",
|
|
107
|
+
"import-assets",
|
|
108
|
+
descriptor.ownerKey,
|
|
109
|
+
);
|
|
110
|
+
const filepath = path.resolve(
|
|
111
|
+
directory,
|
|
112
|
+
`${descriptor.id}-${descriptor.filename}`,
|
|
113
|
+
);
|
|
114
|
+
if (!filepath.startsWith(directory + path.sep)) return null;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
return {
|
|
118
|
+
data: await readFile(filepath),
|
|
119
|
+
mimeType: descriptor.mimeType,
|
|
120
|
+
};
|
|
121
|
+
} catch {
|
|
122
|
+
// coercion-ok: a missing local asset is an absent export image and is skipped by the caller.
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { IMPORT_ASSET_PREFIX };
|