@puredesktop/puredesktop-ui-bridge 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +96 -0
- package/src/agents/runtime/agentLiveReducer.test.ts +72 -0
- package/src/agents/runtime/agentLiveReducer.ts +139 -0
- package/src/agents/runtime/agentRunEventTypes.ts +115 -0
- package/src/agents/runtime/contextWindowUsage.test.ts +38 -0
- package/src/agents/runtime/contextWindowUsage.ts +25 -0
- package/src/agents/runtime/index.ts +24 -0
- package/src/agents/runtime/mapAgentMessages.ts +54 -0
- package/src/agents/runtime/mapAgentSnapshot.ts +71 -0
- package/src/agents/runtime/mergeUsageTurn.ts +23 -0
- package/src/agents/runtime/pendingToolCalls.test.ts +85 -0
- package/src/agents/runtime/pendingToolCalls.ts +14 -0
- package/src/agents/runtime/toolRequiresApproval.ts +20 -0
- package/src/agents/runtime/unresolvedToolCalls.ts +42 -0
- package/src/bridge/agentModelTiers.ts +50 -0
- package/src/bridge/agentToolHandlers.ts +23 -0
- package/src/bridge/agentToolHelpers.test.ts +27 -0
- package/src/bridge/agentToolHelpers.ts +20 -0
- package/src/bridge/agentTypes.ts +294 -0
- package/src/bridge/agents.d.mts +113 -0
- package/src/bridge/agents.mjs +233 -0
- package/src/bridge/agents.ts +62 -0
- package/src/bridge/appSettings.d.mts +9 -0
- package/src/bridge/appSettings.mjs +18 -0
- package/src/bridge/appSettings.ts +4 -0
- package/src/bridge/applyThemeFromHost.d.mts +7 -0
- package/src/bridge/applyThemeFromHost.mjs +33 -0
- package/src/bridge/assets.d.mts +66 -0
- package/src/bridge/assets.mjs +121 -0
- package/src/bridge/catalog.d.mts +5 -0
- package/src/bridge/catalog.mjs +24 -0
- package/src/bridge/catalog.ts +1 -0
- package/src/bridge/client.d.mts +19 -0
- package/src/bridge/client.mjs +147 -0
- package/src/bridge/collectionAssetAdapter.ts +63 -0
- package/src/bridge/collectionAssets.d.mts +1 -0
- package/src/bridge/collectionAssets.mjs +2 -0
- package/src/bridge/collectionAssets.ts +1 -0
- package/src/bridge/collectionDocumentHtml.test.ts +71 -0
- package/src/bridge/collectionDocumentHtml.ts +118 -0
- package/src/bridge/collectionImagePaste.test.ts +32 -0
- package/src/bridge/collectionImagePaste.ts +130 -0
- package/src/bridge/context.mjs +12 -0
- package/src/bridge/credentials.mjs +22 -0
- package/src/bridge/dialog.d.mts +5 -0
- package/src/bridge/dialog.mjs +13 -0
- package/src/bridge/events.d.mts +14 -0
- package/src/bridge/events.mjs +12 -0
- package/src/bridge/filePreviewKind.test.ts +43 -0
- package/src/bridge/filePreviewKind.ts +76 -0
- package/src/bridge/fs.d.mts +41 -0
- package/src/bridge/fs.mjs +82 -0
- package/src/bridge/fs.test.ts +16 -0
- package/src/bridge/fs.ts +10 -0
- package/src/bridge/index.ts +12 -0
- package/src/bridge/inlinePreview.d.mts +35 -0
- package/src/bridge/inlinePreview.mjs +99 -0
- package/src/bridge/inlinePreview.test.ts +56 -0
- package/src/bridge/inlinePreview.ts +13 -0
- package/src/bridge/methods.d.mts +115 -0
- package/src/bridge/methods.mjs +132 -0
- package/src/bridge/os.d.mts +11 -0
- package/src/bridge/os.mjs +26 -0
- package/src/bridge/plugins.d.mts +9 -0
- package/src/bridge/plugins.mjs +31 -0
- package/src/bridge/plugins.ts +5 -0
- package/src/bridge/preferences.d.mts +7 -0
- package/src/bridge/preferences.mjs +17 -0
- package/src/bridge/preferences.ts +13 -0
- package/src/bridge/printDocument.test.ts +47 -0
- package/src/bridge/printDocument.ts +75 -0
- package/src/bridge/pureRender/adapters.test.ts +144 -0
- package/src/bridge/pureRender/adapters.ts +175 -0
- package/src/bridge/pureRender/assets.test.ts +44 -0
- package/src/bridge/pureRender/base.css +76 -0
- package/src/bridge/pureRender/compiler.test.ts +279 -0
- package/src/bridge/pureRender/compiler.ts +660 -0
- package/src/bridge/pureRender/document.test.ts +283 -0
- package/src/bridge/pureRender/document.ts +35 -0
- package/src/bridge/pureRender/extraction.test.ts +432 -0
- package/src/bridge/pureRender/extraction.ts +1023 -0
- package/src/bridge/pureRender/flow.css +3 -0
- package/src/bridge/pureRender/index.ts +66 -0
- package/src/bridge/pureRender/normalize.test.ts +442 -0
- package/src/bridge/pureRender/normalize.ts +682 -0
- package/src/bridge/pureRender/presets.ts +365 -0
- package/src/bridge/pureRender/profiles/book.css +19 -0
- package/src/bridge/pureRender/profiles/manuscript.css +11 -0
- package/src/bridge/pureRender/profiles/writer.css +10 -0
- package/src/bridge/pureRender/reset.css +5 -0
- package/src/bridge/pureRender/samples.ts +94 -0
- package/src/bridge/pureRender/styles.ts +164 -0
- package/src/bridge/pureRender/theme.test.ts +53 -0
- package/src/bridge/pureRender/theme.ts +258 -0
- package/src/bridge/pureRender/tokens/default.css +36 -0
- package/src/bridge/pureRender/tokens/default.json +32 -0
- package/src/bridge/pureRender/types.ts +264 -0
- package/src/bridge/pureRender/visualLayoutInspection.test.ts +57 -0
- package/src/bridge/pureRender/visualLayoutInspection.ts +161 -0
- package/src/bridge/react/useDebouncedEffect.ts +21 -0
- package/src/bridge/react/useIpc.tsx +75 -0
- package/src/bridge/react/usePlatformAgentRunLiveState.tsx +78 -0
- package/src/bridge/react/usePlatformAgentSessionDrive.tsx +729 -0
- package/src/bridge/react/usePlatformAgentSessions.tsx +60 -0
- package/src/bridge/react/usePlatformAgentTools.tsx +82 -0
- package/src/bridge/react/usePlatformAppManifest.tsx +32 -0
- package/src/bridge/react/usePlatformBridge.tsx +56 -0
- package/src/bridge/react/usePlatformDeepLink.tsx +57 -0
- package/src/bridge/react/usePlatformFileBinaryDataUrl.tsx +54 -0
- package/src/bridge/react/usePlatformFilePreview.tsx +92 -0
- package/src/bridge/react/usePlatformPreferences.tsx +77 -0
- package/src/bridge/react/usePlatformThemeDocumentAttrs.ts +35 -0
- package/src/bridge/react/usePlatformThemeFromBridge.tsx +16 -0
- package/src/bridge/react/usePlatformViewportResource.test.tsx +106 -0
- package/src/bridge/react/usePlatformViewportResource.tsx +72 -0
- package/src/bridge/react/useScheduledTask.tsx +59 -0
- package/src/bridge/render.d.mts +31 -0
- package/src/bridge/render.mjs +62 -0
- package/src/bridge/render.ts +8 -0
- package/src/bridge/screenAnnotation.test.ts +103 -0
- package/src/bridge/screenAnnotation.ts +269 -0
- package/src/bridge/theme.d.mts +3 -0
- package/src/bridge/theme.mjs +9 -0
- package/src/bridge/types.ts +439 -0
- package/src/bridge/watch.d.mts +9 -0
- package/src/bridge/watch.mjs +34 -0
- package/src/bridge/watch.ts +1 -0
- package/src/bridge/window.d.mts +44 -0
- package/src/bridge/window.mjs +33 -0
- package/src/chrome/index.ts +1 -0
- package/src/components/accessibility/AccessibilityReviewDialog.test.tsx +104 -0
- package/src/components/accessibility/AccessibilityReviewDialog.tsx +350 -0
- package/src/components/accessibility/accessibilityReview.test.ts +101 -0
- package/src/components/accessibility/accessibilityReview.ts +639 -0
- package/src/components/accessibility/index.ts +16 -0
- package/src/components/agent-feedback/ScreenAnnotationAgentDialog.tsx +509 -0
- package/src/components/agent-feedback/index.ts +5 -0
- package/src/components/agents/AgentComposer.tsx +34 -0
- package/src/components/agents/AgentContextUsageBar.tsx +127 -0
- package/src/components/agents/AgentDrawerPanel.tsx +82 -0
- package/src/components/agents/AgentMarkdown.tsx +12 -0
- package/src/components/agents/AgentMessageBubble.tsx +82 -0
- package/src/components/agents/AgentMessageList.tsx +47 -0
- package/src/components/agents/AgentModelMenu.tsx +55 -0
- package/src/components/agents/AgentThinkingBlock.tsx +66 -0
- package/src/components/agents/AgentToolApprovalActions.tsx +65 -0
- package/src/components/agents/AgentToolCallCard.tsx +26 -0
- package/src/components/agents/AgentToolPendingList.tsx +39 -0
- package/src/components/agents/AgentToolPendingReview.tsx +118 -0
- package/src/components/agents/agentPanelStyles.ts +79 -0
- package/src/components/agents/agentTypes.ts +96 -0
- package/src/components/agents/formatToolCallArguments.test.ts +16 -0
- package/src/components/agents/formatToolCallArguments.ts +9 -0
- package/src/components/agents/index.ts +40 -0
- package/src/components/assets/AssetLibraryDialog.tsx +5 -0
- package/src/components/assets/asset-library/AssetLibraryDialog.tsx +43 -0
- package/src/components/assets/asset-library/AssetLibraryWorkspace.tsx +54 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailActions.tsx +68 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailHeader.tsx +56 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailPane.tsx +90 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailPreview.tsx +67 -0
- package/src/components/assets/asset-library/detail/AssetLibraryInlinePreview.tsx +87 -0
- package/src/components/assets/asset-library/detail/AssetLibraryInlineSourceField.tsx +29 -0
- package/src/components/assets/asset-library/detail/AssetLibraryMetadataFields.tsx +34 -0
- package/src/components/assets/asset-library/detail/AssetLibraryUsagePanel.tsx +46 -0
- package/src/components/assets/asset-library/detail/AssetLibraryVersionsPanel.tsx +145 -0
- package/src/components/assets/asset-library/helpers/assetKind.ts +74 -0
- package/src/components/assets/asset-library/helpers.ts +41 -0
- package/src/components/assets/asset-library/hooks/useAssetDetailDraft.ts +81 -0
- package/src/components/assets/asset-library/hooks/useAssetLibraryDialog.ts +233 -0
- package/src/components/assets/asset-library/hooks/useAssetLibraryMutations.ts +266 -0
- package/src/components/assets/asset-library/hooks/useAssetLibrarySelectionSync.ts +24 -0
- package/src/components/assets/asset-library/hooks/useAssetLibraryVersions.ts +145 -0
- package/src/components/assets/asset-library/hooks/useInlineAssetPreview.ts +44 -0
- package/src/components/assets/asset-library/index.ts +19 -0
- package/src/components/assets/asset-library/layout/assetLibraryLayout.ts +201 -0
- package/src/components/assets/asset-library/shared/fieldStyles.ts +62 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryFilterPills.tsx +89 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryList.tsx +91 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryListRow.tsx +99 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryListStatus.tsx +24 -0
- package/src/components/assets/asset-library/sidebar/AssetLibrarySidebar.tsx +48 -0
- package/src/components/assets/asset-library/sidebar/AssetLibrarySidebarActions.tsx +38 -0
- package/src/components/assets/asset-library/sidebar/AssetLibrarySidebarToolbar.tsx +78 -0
- package/src/components/assets/asset-library/sidebar/AssetListThumbnail.tsx +86 -0
- package/src/components/assets/asset-library/types.ts +128 -0
- package/src/components/assets/asset-library/utils/assetLibraryUtils.ts +181 -0
- package/src/components/assets/asset-library/utils/pathBasename.ts +5 -0
- package/src/components/assets/asset-library/utils/sanitizeUploadFileName.ts +7 -0
- package/src/components/assets/asset-library/utils/uploadCollectionAsset.ts +52 -0
- package/src/components/chrome/OverlayActionCapsule.test.tsx +82 -0
- package/src/components/chrome/OverlayActionCapsule.tsx +90 -0
- package/src/components/chrome/PanelTabStrip.tsx +113 -0
- package/src/components/chrome/PlatformIcon.tsx +27 -0
- package/src/components/chrome/PureAppWordmark.tsx +89 -0
- package/src/components/chrome/ShellWindowControls.tsx +51 -0
- package/src/components/chrome/ThemeModeButton.tsx +16 -0
- package/src/components/chrome/Titlebar.tsx +79 -0
- package/src/components/chrome/WorkspaceTabStrip.tsx +300 -0
- package/src/components/chrome/index.ts +31 -0
- package/src/components/chrome/workspaceTabTypes.ts +29 -0
- package/src/components/common/buttons/Button.tsx +241 -0
- package/src/components/common/buttons/ChoiceChip.tsx +73 -0
- package/src/components/common/buttons/CleanButton.tsx +34 -0
- package/src/components/common/buttons/IconButton.tsx +89 -0
- package/src/components/common/buttons/QuietAction.tsx +29 -0
- package/src/components/common/buttons/SegmentedControl.tsx +67 -0
- package/src/components/common/buttons/TextActionButton.tsx +55 -0
- package/src/components/common/buttons/TextLink.tsx +38 -0
- package/src/components/common/canvas/CanvasElementFrame.test.tsx +79 -0
- package/src/components/common/canvas/CanvasElementFrame.tsx +182 -0
- package/src/components/common/canvas/canvasGeometry.test.ts +66 -0
- package/src/components/common/canvas/canvasGeometry.ts +133 -0
- package/src/components/common/canvas/index.ts +27 -0
- package/src/components/common/canvas/useCanvasDragResize.test.tsx +152 -0
- package/src/components/common/canvas/useCanvasDragResize.ts +161 -0
- package/src/components/common/chat/ChatAssistantMessage.tsx +81 -0
- package/src/components/common/chat/ChatBox.tsx +189 -0
- package/src/components/common/chat/ChatMarkdown.tsx +109 -0
- package/src/components/common/chat/ChatModelMenu.tsx +258 -0
- package/src/components/common/chat/ChatStatusRow.tsx +24 -0
- package/src/components/common/chat/ChatThinkingIndicator.tsx +54 -0
- package/src/components/common/chat/ChatThread.tsx +59 -0
- package/src/components/common/chat/ChatToolRow.tsx +141 -0
- package/src/components/common/chat/ChatUserBubble.tsx +45 -0
- package/src/components/common/chat/chatUtils.ts +7 -0
- package/src/components/common/chat/index.ts +24 -0
- package/src/components/common/connections/ConnectionSetup.tsx +529 -0
- package/src/components/common/connections/index.ts +26 -0
- package/src/components/common/containers/AppFrame.test.tsx +47 -0
- package/src/components/common/containers/AppFrame.tsx +65 -0
- package/src/components/common/containers/AppHeader.tsx +60 -0
- package/src/components/common/containers/AutoFillGrid.tsx +37 -0
- package/src/components/common/containers/Collapsible.tsx +43 -0
- package/src/components/common/containers/CollapsibleGrid.tsx +174 -0
- package/src/components/common/containers/MotionScope.tsx +32 -0
- package/src/components/common/containers/PageHeader.tsx +35 -0
- package/src/components/common/containers/PanelToolbarHeader.tsx +113 -0
- package/src/components/common/containers/QuietDisclosure.tsx +91 -0
- package/src/components/common/containers/QuietSurface.tsx +41 -0
- package/src/components/common/containers/ScrollPanel.tsx +33 -0
- package/src/components/common/containers/StatusBar.tsx +42 -0
- package/src/components/common/containers/SurfacePanel.tsx +66 -0
- package/src/components/common/containers/ToolbarStrip.tsx +80 -0
- package/src/components/common/containers/flex.ts +117 -0
- package/src/components/common/containers/useOpenTransition.ts +33 -0
- package/src/components/common/context/ContextSelector.tsx +269 -0
- package/src/components/common/context/index.ts +7 -0
- package/src/components/common/desk/ActionReader.tsx +270 -0
- package/src/components/common/desk/ActivityLedger.tsx +143 -0
- package/src/components/common/desk/CreationDesk.tsx +139 -0
- package/src/components/common/desk/DailyBrief.tsx +160 -0
- package/src/components/common/desk/DeskCard.tsx +157 -0
- package/src/components/common/desk/index.ts +30 -0
- package/src/components/common/dropdown/ContextMenu.tsx +87 -0
- package/src/components/common/dropdown/MenuButtonDropdown.tsx +121 -0
- package/src/components/common/dropdown/MenuDropdown.tsx +63 -0
- package/src/components/common/dropdown/MenuDropdownEntry.tsx +51 -0
- package/src/components/common/dropdown/MenuDropdownItem.tsx +82 -0
- package/src/components/common/dropdown/MenuDropdownSubmenu.tsx +145 -0
- package/src/components/common/dropdown/MenuPanel.tsx +69 -0
- package/src/components/common/dropdown/MenuPortal.tsx +162 -0
- package/src/components/common/dropdown/MenuPortalLayer.tsx +6 -0
- package/src/components/common/dropdown/SelectMenu.tsx +243 -0
- package/src/components/common/dropdown/SelectMenuControl.tsx +23 -0
- package/src/components/common/dropdown/SelectMenuOptions.tsx +38 -0
- package/src/components/common/dropdown/menuPortalConstants.ts +11 -0
- package/src/components/common/dropdown/menuTypes.ts +71 -0
- package/src/components/common/dropdown/menuViewportPosition.test.ts +139 -0
- package/src/components/common/dropdown/menuViewportPosition.ts +122 -0
- package/src/components/common/dropdown/selectMenuLayout.test.ts +42 -0
- package/src/components/common/dropdown/selectMenuLayout.ts +123 -0
- package/src/components/common/dropdown/useMenuAnchorPosition.ts +109 -0
- package/src/components/common/dropdown/useSelectMenuAnchor.ts +30 -0
- package/src/components/common/evidence/StructuredEvidenceView.test.tsx +77 -0
- package/src/components/common/evidence/StructuredEvidenceView.tsx +312 -0
- package/src/components/common/evidence/index.ts +13 -0
- package/src/components/common/feedback/Badge.tsx +33 -0
- package/src/components/common/feedback/CapabilityPills.tsx +36 -0
- package/src/components/common/feedback/EmptyState.tsx +132 -0
- package/src/components/common/feedback/InfoHint.tsx +67 -0
- package/src/components/common/feedback/InlineBanner.tsx +82 -0
- package/src/components/common/feedback/PlatformChip.tsx +104 -0
- package/src/components/common/feedback/QuietPill.tsx +75 -0
- package/src/components/common/feedback/WorkProgressPanel.tsx +156 -0
- package/src/components/common/feedback/index.ts +33 -0
- package/src/components/common/inputs/BareInput.tsx +31 -0
- package/src/components/common/inputs/BareTextarea.tsx +10 -0
- package/src/components/common/inputs/Checkbox.tsx +13 -0
- package/src/components/common/inputs/FieldControlShell.tsx +49 -0
- package/src/components/common/inputs/FormField.tsx +40 -0
- package/src/components/common/inputs/HybridSearchField.tsx +280 -0
- package/src/components/common/inputs/InlineComposer.tsx +197 -0
- package/src/components/common/inputs/PaletteCirclePicker.tsx +26 -0
- package/src/components/common/inputs/PaletteDropdown.tsx +413 -0
- package/src/components/common/inputs/Radio.tsx +19 -0
- package/src/components/common/inputs/SearchField.tsx +128 -0
- package/src/components/common/inputs/SelectField.tsx +169 -0
- package/src/components/common/inputs/SurfacePalettePicker.tsx +54 -0
- package/src/components/common/inputs/TextAreaField.tsx +22 -0
- package/src/components/common/inputs/TextField.tsx +51 -0
- package/src/components/common/inputs/ToggleSwitch.tsx +110 -0
- package/src/components/common/inputs/WorkspacePicker.tsx +349 -0
- package/src/components/common/inputs/fieldStyles.ts +137 -0
- package/src/components/common/layout/PanelGroup.tsx +479 -0
- package/src/components/common/layout/index.ts +31 -0
- package/src/components/common/layout/panelLayoutConstants.ts +7 -0
- package/src/components/common/layout/panelLayoutPersistence.ts +35 -0
- package/src/components/common/layout/panelLayoutState.test.ts +38 -0
- package/src/components/common/layout/panelLayoutState.ts +174 -0
- package/src/components/common/layout/panelLayoutTypes.ts +19 -0
- package/src/components/common/layout/tabs/Tabs.tsx +136 -0
- package/src/components/common/lists/listRowStyles.ts +32 -0
- package/src/components/common/lists/quiet-ledger/QuietActivityList.tsx +64 -0
- package/src/components/common/lists/quiet-ledger/QuietLedgerRow.tsx +119 -0
- package/src/components/common/lists/quiet-ledger/index.ts +9 -0
- package/src/components/common/lists/reorderable/DragHandle.tsx +65 -0
- package/src/components/common/lists/reorderable/ReorderableList.tsx +280 -0
- package/src/components/common/lists/reorderable/ReorderableListRow.tsx +102 -0
- package/src/components/common/lists/reorderable/ReorderableSections.tsx +344 -0
- package/src/components/common/lists/reorderable/ReorderableSectionsDemo.tsx +94 -0
- package/src/components/common/lists/reorderable/index.ts +15 -0
- package/src/components/common/lists/reorderable/reorderableUtils.test.ts +28 -0
- package/src/components/common/lists/reorderable/reorderableUtils.ts +23 -0
- package/src/components/common/lists/reorderable/types.ts +40 -0
- package/src/components/common/lists/reorderable/useReorderableDragState.ts +49 -0
- package/src/components/common/media/ImageReferencePicker.tsx +242 -0
- package/src/components/common/menubar/MenuBar.tsx +78 -0
- package/src/components/common/menubar/MenuBarTrigger.tsx +70 -0
- package/src/components/common/narrative/NarrativeMention.tsx +74 -0
- package/src/components/common/navigation/PathBreadcrumbs.tsx +78 -0
- package/src/components/common/navigation/UnderlineTabs.tsx +83 -0
- package/src/components/common/overlays/InstallAppPromptDialog.tsx +59 -0
- package/src/components/common/overlays/Modal.tsx +285 -0
- package/src/components/common/overlays/ModalChromeHeader.tsx +105 -0
- package/src/components/common/overlays/ObjectActionStrip.tsx +80 -0
- package/src/components/common/people/AutoUpdateReviewList.tsx +182 -0
- package/src/components/common/people/EntityList.tsx +122 -0
- package/src/components/common/people/EntityPortrait.tsx +64 -0
- package/src/components/common/people/LinkedDossierList.tsx +99 -0
- package/src/components/common/people/OpportunityList.tsx +94 -0
- package/src/components/common/people/PeopleSourceList.tsx +145 -0
- package/src/components/common/people/RelationshipDossier.tsx +168 -0
- package/src/components/common/people/RelationshipMap.tsx +362 -0
- package/src/components/common/people/index.ts +40 -0
- package/src/components/common/preview/FileAudioPreview.tsx +89 -0
- package/src/components/common/preview/FileImagePreview.tsx +88 -0
- package/src/components/common/preview/FilePdfPreview.tsx +87 -0
- package/src/components/common/preview/FileTextPreview.tsx +96 -0
- package/src/components/common/preview/FileVideoPreview.tsx +87 -0
- package/src/components/common/preview/PlatformFilePreview.tsx +166 -0
- package/src/components/common/preview/previewLayout.ts +27 -0
- package/src/components/common/render/Each.tsx +42 -0
- package/src/components/common/research/EvidenceDossier.tsx +894 -0
- package/src/components/common/research/index.ts +7 -0
- package/src/components/common/tiles/CatalogCard.tsx +116 -0
- package/src/components/common/tiles/Tile.tsx +45 -0
- package/src/components/common/tiles/TileGrid.tsx +13 -0
- package/src/components/common/typography/Heading.tsx +27 -0
- package/src/components/common/typography/Kicker.tsx +30 -0
- package/src/components/common/typography/Text.tsx +80 -0
- package/src/components/credentials/CredentialsPanel.tsx +169 -0
- package/src/components/credentials/credentialsTypes.ts +14 -0
- package/src/components/data/DataChart.tsx +913 -0
- package/src/components/data/dataTable.ts +99 -0
- package/src/components/editor/EditorCommentPromptDialog.tsx +124 -0
- package/src/components/editor/EditorLinkPromptDialog.tsx +65 -0
- package/src/components/print-design/PrintDesignPanel.test.tsx +1446 -0
- package/src/components/print-design/PrintDesignPanel.tsx +3043 -0
- package/src/components/print-design/index.ts +9 -0
- package/src/components/proofing/PagedProofDialog.tsx +453 -0
- package/src/components/proofing/index.ts +4 -0
- package/src/editor/DocumentEditor.tsx +28 -0
- package/src/editor/EditorViewModeToggle.tsx +35 -0
- package/src/editor/index.ts +97 -0
- package/src/editor/toolbarOverlays.ts +8 -0
- package/src/index.ts +209 -0
- package/src/theme/ThemeProvider.tsx +113 -0
- package/src/theme/appAccents.ts +87 -0
- package/src/theme/buildThemeBridgePayload.ts +23 -0
- package/src/theme/composePlatformTheme.test.ts +30 -0
- package/src/theme/composePlatformTheme.ts +23 -0
- package/src/theme/platformFont.d.mts +7 -0
- package/src/theme/platformFont.mjs +20 -0
- package/src/theme/platformFont.ts +6 -0
- package/src/theme/reportsShellChromeSelectors.ts +136 -0
- package/src/theme/styled.d.ts +5 -0
- package/src/theme/surfacePalettes.ts +178 -0
- package/src/theme/surfaceRoleSelectors.ts +113 -0
- package/src/theme/themeVars.ts +13 -0
- package/src/theme/themes/dark.ts +59 -0
- package/src/theme/themes/light.ts +60 -0
- package/src/theme/themes/palette.ts +44 -0
- package/src/theme/themes/shared.ts +68 -0
- package/src/theme/tokens.ts +114 -0
- package/src/theme/utils/GlobalStyles.ts +88 -0
- package/src/theme/utils/appFrameDocumentStyles.d.mts +5 -0
- package/src/theme/utils/appFrameDocumentStyles.mjs +89 -0
- package/src/theme/utils/appFrameDocumentStyles.ts +5 -0
- package/src/theme/utils/applyTheme.ts +50 -0
- package/src/theme/utils/cssVariables.ts +49 -0
- package/src/theme/utils/useInjectThemeIntoIframe.ts +40 -0
- package/src/utils/extensionIconMap.ts +123 -0
- package/src/utils/generalUtils.ts +157 -0
- package/src/writing/DraftAssistantDialog.tsx +279 -0
- package/src/writing/WritingStyleManager.tsx +416 -0
- package/src/writing/condenseWritingStyle.ts +103 -0
- package/src/writing/draftTextWithAgent.ts +134 -0
- package/src/writing/index.ts +39 -0
- package/src/writing/styleStore.ts +32 -0
- package/src/writing/writingStyle.test.ts +122 -0
- package/src/writing/writingStyle.ts +217 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
callAgentRunModel,
|
|
3
|
+
createAgentSession,
|
|
4
|
+
deleteAgentSession,
|
|
5
|
+
startAgentRun,
|
|
6
|
+
} from '../bridge/agents.js'
|
|
7
|
+
|
|
8
|
+
interface AgentSessionRecord {
|
|
9
|
+
sessionId: string
|
|
10
|
+
config: { modelId: string }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface AgentRunSnapshot {
|
|
14
|
+
runId: string
|
|
15
|
+
state: string
|
|
16
|
+
messages: Array<{ role: string; content: string }>
|
|
17
|
+
error?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const MAX_TURNS = 4
|
|
21
|
+
|
|
22
|
+
const SYSTEM_PROMPT = [
|
|
23
|
+
'You distill writing-style guidance into a compact, reusable style guide.',
|
|
24
|
+
'Capture VOICE only — tone, register, sentence rhythm, word choice, and structural habits.',
|
|
25
|
+
'Output 4–8 short imperative bullet lines, each starting with "- ".',
|
|
26
|
+
'If the guidance lists words or phrases to PREFER or to AVOID, keep them verbatim as their own "- Prefer: …" and "- Avoid: …" bullets (these are in addition to the 4–8 voice bullets).',
|
|
27
|
+
'Do NOT include the sample text, its facts, or its subject matter.',
|
|
28
|
+
'Return plain text only — no preamble, no headings, no commentary.',
|
|
29
|
+
].join('\n')
|
|
30
|
+
|
|
31
|
+
function latestAssistant(snapshot: AgentRunSnapshot): string {
|
|
32
|
+
return (
|
|
33
|
+
[...snapshot.messages]
|
|
34
|
+
.reverse()
|
|
35
|
+
.find(message => message.role === 'assistant')
|
|
36
|
+
?.content.trim() ?? ''
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Distill verbose style guidance (presets + voice samples) into a short,
|
|
42
|
+
* reusable style guide — the book's "style skill". Sent once and reused for
|
|
43
|
+
* every chapter draft/edit instead of the full samples, to save tokens.
|
|
44
|
+
* Falls back to the original guidance if the model is unavailable.
|
|
45
|
+
*/
|
|
46
|
+
export async function condenseWritingStyleWithAgent(
|
|
47
|
+
fullGuidance: string,
|
|
48
|
+
): Promise<string> {
|
|
49
|
+
const trimmed = fullGuidance.trim()
|
|
50
|
+
if (!trimmed) return ''
|
|
51
|
+
|
|
52
|
+
let session: AgentSessionRecord | null = null
|
|
53
|
+
try {
|
|
54
|
+
session = (await createAgentSession({
|
|
55
|
+
scope: 'app',
|
|
56
|
+
appSlug: 'book',
|
|
57
|
+
title: 'Condense writing style',
|
|
58
|
+
config: {
|
|
59
|
+
sessionPrompt: SYSTEM_PROMPT,
|
|
60
|
+
toolPolicy: {
|
|
61
|
+
allowedTargetApps: ['__purebook_style_no_tools__'],
|
|
62
|
+
defaultRequiresApproval: true,
|
|
63
|
+
},
|
|
64
|
+
} as never,
|
|
65
|
+
})) as AgentSessionRecord
|
|
66
|
+
|
|
67
|
+
let snapshot = (await startAgentRun({
|
|
68
|
+
session: { scope: 'app', sessionId: session.sessionId, appSlug: 'book' },
|
|
69
|
+
message: trimmed,
|
|
70
|
+
})) as AgentRunSnapshot
|
|
71
|
+
|
|
72
|
+
for (let turn = 0; turn < MAX_TURNS; turn += 1) {
|
|
73
|
+
if (snapshot.state === 'ready') {
|
|
74
|
+
snapshot = (await callAgentRunModel({
|
|
75
|
+
runId: snapshot.runId,
|
|
76
|
+
sessionId: session.sessionId,
|
|
77
|
+
})) as AgentRunSnapshot
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (snapshot.state === 'awaiting_user') {
|
|
84
|
+
const condensed = latestAssistant(snapshot)
|
|
85
|
+
if (condensed) return condensed
|
|
86
|
+
}
|
|
87
|
+
return trimmed
|
|
88
|
+
} catch {
|
|
89
|
+
return trimmed
|
|
90
|
+
} finally {
|
|
91
|
+
if (session) {
|
|
92
|
+
try {
|
|
93
|
+
await deleteAgentSession({
|
|
94
|
+
scope: 'app',
|
|
95
|
+
sessionId: session.sessionId,
|
|
96
|
+
appSlug: 'book',
|
|
97
|
+
})
|
|
98
|
+
} catch {
|
|
99
|
+
/* best-effort cleanup */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
callAgentRunModel,
|
|
3
|
+
createAgentSession,
|
|
4
|
+
deleteAgentSession,
|
|
5
|
+
startAgentRun,
|
|
6
|
+
} from '../bridge/agents.js'
|
|
7
|
+
|
|
8
|
+
interface AgentSessionRecord {
|
|
9
|
+
sessionId: string
|
|
10
|
+
config: { modelId: string }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface AgentRunSnapshot {
|
|
14
|
+
runId: string
|
|
15
|
+
state: string
|
|
16
|
+
messages: Array<{ role: string; content: string }>
|
|
17
|
+
error?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const MAX_TURNS = 4
|
|
21
|
+
|
|
22
|
+
export type DraftTextMode = 'write' | 'rewrite'
|
|
23
|
+
|
|
24
|
+
export interface DraftTextInput {
|
|
25
|
+
mode: DraftTextMode
|
|
26
|
+
instruction: string
|
|
27
|
+
selectionText?: string
|
|
28
|
+
styleGuidance?: string
|
|
29
|
+
/**
|
|
30
|
+
* Grounding/reference material (e.g. a manuscript's abstract and reference
|
|
31
|
+
* list). Used for ACCURACY — facts, terminology, claims — not for voice.
|
|
32
|
+
*/
|
|
33
|
+
context?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function systemPrompt(mode: DraftTextMode): string {
|
|
37
|
+
return [
|
|
38
|
+
mode === 'rewrite'
|
|
39
|
+
? 'You rewrite a passage of a book to the author’s instruction, preserving its meaning.'
|
|
40
|
+
: 'You draft a passage of book prose from the author’s instruction.',
|
|
41
|
+
'Return clean HTML only — paragraphs <p>, sub-headings <h2>, lists where useful.',
|
|
42
|
+
'No markdown, no code fences, no <h1>, no preamble, no commentary.',
|
|
43
|
+
'Do not invent precise statistics, dates, quotations, or citations.',
|
|
44
|
+
].join('\n')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function message(input: DraftTextInput): string {
|
|
48
|
+
return [
|
|
49
|
+
input.mode === 'rewrite' && input.selectionText
|
|
50
|
+
? `Passage to rewrite:\n"""${input.selectionText.slice(0, 6000)}"""`
|
|
51
|
+
: input.selectionText
|
|
52
|
+
? `Nearby text for context (do not repeat):\n"""${input.selectionText.slice(0, 2000)}"""`
|
|
53
|
+
: '',
|
|
54
|
+
`Instruction: ${input.instruction.trim() || (input.mode === 'rewrite' ? 'Improve this passage.' : 'Draft this.')}`,
|
|
55
|
+
input.context?.trim()
|
|
56
|
+
? `\nReference material — use it for facts, terminology, and claims; do NOT copy its wording as voice and do NOT invent beyond it:\n"""${input.context.trim().slice(0, 4000)}"""`
|
|
57
|
+
: '',
|
|
58
|
+
input.styleGuidance ? `\n${input.styleGuidance}` : '',
|
|
59
|
+
'',
|
|
60
|
+
'Return the HTML now.',
|
|
61
|
+
]
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
.join('\n')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function cleanHtml(raw: string): string {
|
|
67
|
+
let value = raw.trim()
|
|
68
|
+
value = value.replace(/^```(?:html)?\s*/i, '').replace(/\s*```$/i, '')
|
|
69
|
+
value = value.replace(/<h1[\s\S]*?<\/h1>/gi, '')
|
|
70
|
+
return value.trim()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function latestAssistant(snapshot: AgentRunSnapshot): string {
|
|
74
|
+
return (
|
|
75
|
+
[...snapshot.messages]
|
|
76
|
+
.reverse()
|
|
77
|
+
.find(entry => entry.role === 'assistant')
|
|
78
|
+
?.content.trim() ?? ''
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Draft or rewrite a passage via the agent. Returns HTML (empty on failure). */
|
|
83
|
+
export async function draftTextWithAgent(
|
|
84
|
+
input: DraftTextInput,
|
|
85
|
+
): Promise<string> {
|
|
86
|
+
let session: AgentSessionRecord | null = null
|
|
87
|
+
try {
|
|
88
|
+
session = (await createAgentSession({
|
|
89
|
+
scope: 'app',
|
|
90
|
+
appSlug: 'book',
|
|
91
|
+
title: 'Draft passage',
|
|
92
|
+
config: {
|
|
93
|
+
sessionPrompt: systemPrompt(input.mode),
|
|
94
|
+
toolPolicy: {
|
|
95
|
+
allowedTargetApps: ['__purebook_draft_text_no_tools__'],
|
|
96
|
+
defaultRequiresApproval: true,
|
|
97
|
+
},
|
|
98
|
+
} as never,
|
|
99
|
+
})) as AgentSessionRecord
|
|
100
|
+
|
|
101
|
+
let snapshot = (await startAgentRun({
|
|
102
|
+
session: { scope: 'app', sessionId: session.sessionId, appSlug: 'book' },
|
|
103
|
+
message: message(input),
|
|
104
|
+
})) as AgentRunSnapshot
|
|
105
|
+
|
|
106
|
+
for (let turn = 0; turn < MAX_TURNS; turn += 1) {
|
|
107
|
+
if (snapshot.state === 'ready') {
|
|
108
|
+
snapshot = (await callAgentRunModel({
|
|
109
|
+
runId: snapshot.runId,
|
|
110
|
+
sessionId: session.sessionId,
|
|
111
|
+
})) as AgentRunSnapshot
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (snapshot.state === 'awaiting_user') {
|
|
118
|
+
return cleanHtml(latestAssistant(snapshot))
|
|
119
|
+
}
|
|
120
|
+
throw new Error(snapshot.error || `The model stopped at ${snapshot.state}.`)
|
|
121
|
+
} finally {
|
|
122
|
+
if (session) {
|
|
123
|
+
try {
|
|
124
|
+
await deleteAgentSession({
|
|
125
|
+
scope: 'app',
|
|
126
|
+
sessionId: session.sessionId,
|
|
127
|
+
appSlug: 'book',
|
|
128
|
+
})
|
|
129
|
+
} catch {
|
|
130
|
+
/* best-effort cleanup */
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable writing-style + AI drafting toolkit.
|
|
3
|
+
*
|
|
4
|
+
* App-agnostic: the host owns persistence of the style library (pass `styles`
|
|
5
|
+
* and `onStylesChange`); everything else — the named-style manager, the
|
|
6
|
+
* "distill into skill" flow, and the draft/redraft dialog — lives here so any
|
|
7
|
+
* app can adopt it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
DraftAssistantDialog,
|
|
12
|
+
type DraftAssistantDialogProps,
|
|
13
|
+
} from './DraftAssistantDialog.js'
|
|
14
|
+
export {
|
|
15
|
+
WritingStyleManager,
|
|
16
|
+
type WritingStyleManagerProps,
|
|
17
|
+
} from './WritingStyleManager.js'
|
|
18
|
+
export {
|
|
19
|
+
draftTextWithAgent,
|
|
20
|
+
type DraftTextInput,
|
|
21
|
+
type DraftTextMode,
|
|
22
|
+
} from './draftTextWithAgent.js'
|
|
23
|
+
export { condenseWritingStyleWithAgent } from './condenseWritingStyle.js'
|
|
24
|
+
export {
|
|
25
|
+
loadSharedWritingStyles,
|
|
26
|
+
saveSharedWritingStyles,
|
|
27
|
+
} from './styleStore.js'
|
|
28
|
+
export {
|
|
29
|
+
EMPTY_WRITING_STYLE,
|
|
30
|
+
buildStyleGuidance,
|
|
31
|
+
createStyleSource,
|
|
32
|
+
createWritingStyle,
|
|
33
|
+
normalizeWritingStyle,
|
|
34
|
+
styleHasContent,
|
|
35
|
+
styleRawGuidance,
|
|
36
|
+
type BookWritingStyle,
|
|
37
|
+
type StyleSource,
|
|
38
|
+
type WritingStyle,
|
|
39
|
+
} from './writingStyle.js'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { bridge, PLATFORM_BRIDGE_METHODS } from '../bridge/client.mjs'
|
|
2
|
+
import { normalizeWritingStyle, type BookWritingStyle } from './writingStyle.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared writing-style library — ONE set of named styles across every app.
|
|
6
|
+
*
|
|
7
|
+
* Stored as a single JSON file in the suite data dir (`~/PureScience/`), so a
|
|
8
|
+
* style created (and distilled) in one app is available in all of them. The
|
|
9
|
+
* bridge requires an appSlug but does not use it to scope the path, so every
|
|
10
|
+
* app that reads/writes this filename shares the same library.
|
|
11
|
+
*/
|
|
12
|
+
const STORAGE_SLUG = 'writing'
|
|
13
|
+
const STORAGE_FILE = 'writing-styles.json'
|
|
14
|
+
|
|
15
|
+
export async function loadSharedWritingStyles(): Promise<BookWritingStyle> {
|
|
16
|
+
try {
|
|
17
|
+
const result = (await bridge.call(PLATFORM_BRIDGE_METHODS.STORAGE_READ_JSON, [
|
|
18
|
+
{ appSlug: STORAGE_SLUG, fileName: STORAGE_FILE },
|
|
19
|
+
])) as { value?: unknown } | null
|
|
20
|
+
return normalizeWritingStyle(result?.value)
|
|
21
|
+
} catch {
|
|
22
|
+
return normalizeWritingStyle(null)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function saveSharedWritingStyles(
|
|
27
|
+
style: BookWritingStyle,
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
await bridge.call(PLATFORM_BRIDGE_METHODS.STORAGE_WRITE_JSON, [
|
|
30
|
+
{ appSlug: STORAGE_SLUG, fileName: STORAGE_FILE, value: style },
|
|
31
|
+
])
|
|
32
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
buildStyleGuidance,
|
|
4
|
+
createWritingStyle,
|
|
5
|
+
normalizeWritingStyle,
|
|
6
|
+
styleHasContent,
|
|
7
|
+
type WritingStyle,
|
|
8
|
+
} from './writingStyle.js'
|
|
9
|
+
|
|
10
|
+
function styleWith(overrides: Partial<WritingStyle>): WritingStyle {
|
|
11
|
+
return { ...createWritingStyle('Test'), ...overrides }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('normalizeWritingStyle', () => {
|
|
15
|
+
it('returns an empty library for junk input', () => {
|
|
16
|
+
expect(normalizeWritingStyle(undefined)).toEqual({ styles: [] })
|
|
17
|
+
expect(normalizeWritingStyle({})).toEqual({ styles: [] })
|
|
18
|
+
expect(normalizeWritingStyle({ styles: 'nope' })).toEqual({ styles: [] })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('keeps current-shape styles and drops empty ones', () => {
|
|
22
|
+
const { styles } = normalizeWritingStyle({
|
|
23
|
+
styles: [
|
|
24
|
+
{ id: 'a', name: 'Docs', sources: [{ id: 's', label: 'x', text: 'hi' }] },
|
|
25
|
+
{ id: 'b', name: 'Empty', sources: [], notes: '', skill: '' },
|
|
26
|
+
{ id: 'c', name: 'Notes only', notes: 'be terse' },
|
|
27
|
+
],
|
|
28
|
+
})
|
|
29
|
+
expect(styles.map(style => style.name)).toEqual(['Docs', 'Notes only'])
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('migrates legacy { examples } samples into styles', () => {
|
|
33
|
+
const { styles } = normalizeWritingStyle({
|
|
34
|
+
presets: ['academic'],
|
|
35
|
+
examples: [{ id: 'e1', label: 'My essays', text: 'A sample paragraph.' }],
|
|
36
|
+
})
|
|
37
|
+
expect(styles).toHaveLength(1)
|
|
38
|
+
expect(styles[0].name).toBe('My essays')
|
|
39
|
+
expect(styles[0].sources[0].text).toBe('A sample paragraph.')
|
|
40
|
+
// Legacy generic presets are intentionally discarded.
|
|
41
|
+
expect(styles[0]).not.toHaveProperty('presets')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('disables a skill flag when there is no skill text', () => {
|
|
45
|
+
const { styles } = normalizeWritingStyle({
|
|
46
|
+
styles: [{ id: 'a', name: 'X', notes: 'n', skill: '', skillEnabled: true }],
|
|
47
|
+
})
|
|
48
|
+
expect(styles[0].skillEnabled).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('styleHasContent', () => {
|
|
53
|
+
it('is false for an empty style and true once it has sources/notes/skill', () => {
|
|
54
|
+
expect(styleHasContent(createWritingStyle())).toBe(false)
|
|
55
|
+
expect(styleHasContent(styleWith({ notes: 'be terse' }))).toBe(true)
|
|
56
|
+
expect(styleHasContent(styleWith({ skill: '- be terse' }))).toBe(true)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('buildStyleGuidance', () => {
|
|
61
|
+
it('returns undefined when nothing is selected', () => {
|
|
62
|
+
expect(buildStyleGuidance([])).toBeUndefined()
|
|
63
|
+
expect(buildStyleGuidance([createWritingStyle()])).toBeUndefined()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('uses the distilled skill when enabled', () => {
|
|
67
|
+
const guidance = buildStyleGuidance([
|
|
68
|
+
styleWith({
|
|
69
|
+
name: 'Docs',
|
|
70
|
+
notes: 'raw notes that should be hidden',
|
|
71
|
+
skill: '- short sentences\n- active voice',
|
|
72
|
+
skillEnabled: true,
|
|
73
|
+
}),
|
|
74
|
+
])
|
|
75
|
+
expect(guidance).toContain('Style — Docs:')
|
|
76
|
+
expect(guidance).toContain('short sentences')
|
|
77
|
+
expect(guidance).not.toContain('raw notes that should be hidden')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('falls back to raw notes + voice-only samples when no skill is enabled', () => {
|
|
81
|
+
const guidance = buildStyleGuidance([
|
|
82
|
+
styleWith({
|
|
83
|
+
name: 'Fiction',
|
|
84
|
+
notes: 'lyrical',
|
|
85
|
+
sources: [{ id: 's', label: 'novel', text: 'Once upon a time.' }],
|
|
86
|
+
skill: '- ignored because disabled',
|
|
87
|
+
skillEnabled: false,
|
|
88
|
+
}),
|
|
89
|
+
])
|
|
90
|
+
expect(guidance).toContain('Guidance: lyrical')
|
|
91
|
+
expect(guidance).toContain('Match the VOICE')
|
|
92
|
+
expect(guidance).toContain('Once upon a time.')
|
|
93
|
+
expect(guidance).not.toContain('ignored because disabled')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('emits prefer/avoid word lists, normalising separators', () => {
|
|
97
|
+
const guidance = buildStyleGuidance([
|
|
98
|
+
styleWith({
|
|
99
|
+
name: 'Docs',
|
|
100
|
+
include: 'reader, cohort\nmodule',
|
|
101
|
+
exclude: 'utilise, leverage',
|
|
102
|
+
}),
|
|
103
|
+
])
|
|
104
|
+
expect(guidance).toContain('Prefer these words and phrases')
|
|
105
|
+
expect(guidance).toContain('reader, cohort, module')
|
|
106
|
+
expect(guidance).toContain('Avoid these words and phrases')
|
|
107
|
+
expect(guidance).toContain('utilise, leverage')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('counts a words-only style as having content', () => {
|
|
111
|
+
expect(styleHasContent(styleWith({ include: 'reader' }))).toBe(true)
|
|
112
|
+
expect(styleHasContent(styleWith({ exclude: 'utilise' }))).toBe(true)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('migrates/keeps include and exclude through normalize', () => {
|
|
116
|
+
const { styles } = normalizeWritingStyle({
|
|
117
|
+
styles: [{ id: 'a', name: 'X', include: 'foo', exclude: 'bar' }],
|
|
118
|
+
})
|
|
119
|
+
expect(styles[0].include).toBe('foo')
|
|
120
|
+
expect(styles[0].exclude).toBe('bar')
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing-style guidance for book generation and in-editor drafting.
|
|
3
|
+
*
|
|
4
|
+
* There are no built-in generic presets. Every style is authored and named by
|
|
5
|
+
* the user. A style carries:
|
|
6
|
+
* - SOURCES: the author's own writing samples, mirrored for VOICE only and
|
|
7
|
+
* never lifted as content.
|
|
8
|
+
* - NOTES: free-text guidance the author writes themselves (their own rules).
|
|
9
|
+
* - SKILL: a compact, distilled style guide generated from the sources+notes
|
|
10
|
+
* (see condenseWritingStyleWithAgent). It is editable and, when enabled, is
|
|
11
|
+
* sent to the agent instead of the raw sources+notes to save tokens.
|
|
12
|
+
*
|
|
13
|
+
* Styles are saved app-wide (book app settings) and reused across book creation
|
|
14
|
+
* and the editor drafter. Which styles apply is chosen per book / per draft.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface StyleSource {
|
|
18
|
+
id: string
|
|
19
|
+
label: string
|
|
20
|
+
text: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface WritingStyle {
|
|
24
|
+
id: string
|
|
25
|
+
name: string
|
|
26
|
+
sources: StyleSource[]
|
|
27
|
+
notes: string
|
|
28
|
+
/** Words/phrases to prefer (whitelist). Free text — comma or newline separated. */
|
|
29
|
+
include: string
|
|
30
|
+
/** Words/phrases to avoid (blacklist). Free text — comma or newline separated. */
|
|
31
|
+
exclude: string
|
|
32
|
+
/** Distilled compact style guide; editable. Empty until distilled. */
|
|
33
|
+
skill: string
|
|
34
|
+
/** When true and a skill exists, the skill is used instead of raw sources. */
|
|
35
|
+
skillEnabled: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BookWritingStyle {
|
|
39
|
+
styles: WritingStyle[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const EMPTY_WRITING_STYLE: BookWritingStyle = { styles: [] }
|
|
43
|
+
|
|
44
|
+
function randomId(prefix: string): string {
|
|
45
|
+
return `${prefix}-${Math.random().toString(36).slice(2)}-${Date.now()}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createWritingStyle(name = 'New style'): WritingStyle {
|
|
49
|
+
return {
|
|
50
|
+
id: randomId('style'),
|
|
51
|
+
name,
|
|
52
|
+
sources: [],
|
|
53
|
+
notes: '',
|
|
54
|
+
include: '',
|
|
55
|
+
exclude: '',
|
|
56
|
+
skill: '',
|
|
57
|
+
skillEnabled: false,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createStyleSource(label = 'Sample'): StyleSource {
|
|
62
|
+
return { id: randomId('src'), label, text: '' }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function coerceSource(item: unknown): StyleSource | null {
|
|
66
|
+
if (!item || typeof item !== 'object') return null
|
|
67
|
+
const entry = item as Record<string, unknown>
|
|
68
|
+
const text = typeof entry.text === 'string' ? entry.text : ''
|
|
69
|
+
return {
|
|
70
|
+
id: typeof entry.id === 'string' && entry.id ? entry.id : randomId('src'),
|
|
71
|
+
label:
|
|
72
|
+
typeof entry.label === 'string' && entry.label.trim()
|
|
73
|
+
? entry.label.trim()
|
|
74
|
+
: 'Sample',
|
|
75
|
+
text,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function coerceStyle(item: unknown): WritingStyle | null {
|
|
80
|
+
if (!item || typeof item !== 'object') return null
|
|
81
|
+
const entry = item as Record<string, unknown>
|
|
82
|
+
|
|
83
|
+
// Current shape: a style with its own sources.
|
|
84
|
+
let sources = Array.isArray(entry.sources)
|
|
85
|
+
? entry.sources
|
|
86
|
+
.map(coerceSource)
|
|
87
|
+
.filter((source): source is StyleSource => source !== null)
|
|
88
|
+
: []
|
|
89
|
+
|
|
90
|
+
// Legacy migration: a flat StyleExample { label, text } becomes one source.
|
|
91
|
+
if (sources.length === 0 && typeof entry.text === 'string' && entry.text.trim()) {
|
|
92
|
+
sources = [
|
|
93
|
+
{
|
|
94
|
+
id: typeof entry.id === 'string' && entry.id ? entry.id : randomId('src'),
|
|
95
|
+
label:
|
|
96
|
+
typeof entry.label === 'string' && entry.label.trim()
|
|
97
|
+
? entry.label.trim()
|
|
98
|
+
: 'Sample',
|
|
99
|
+
text: entry.text,
|
|
100
|
+
},
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const name =
|
|
105
|
+
typeof entry.name === 'string' && entry.name.trim()
|
|
106
|
+
? entry.name.trim()
|
|
107
|
+
: typeof entry.label === 'string' && entry.label.trim()
|
|
108
|
+
? entry.label.trim()
|
|
109
|
+
: 'Untitled style'
|
|
110
|
+
const notes = typeof entry.notes === 'string' ? entry.notes : ''
|
|
111
|
+
const include = typeof entry.include === 'string' ? entry.include : ''
|
|
112
|
+
const exclude = typeof entry.exclude === 'string' ? entry.exclude : ''
|
|
113
|
+
const skill = typeof entry.skill === 'string' ? entry.skill : ''
|
|
114
|
+
|
|
115
|
+
// Keep a style only if it has something to say.
|
|
116
|
+
if (
|
|
117
|
+
sources.length === 0 &&
|
|
118
|
+
!notes.trim() &&
|
|
119
|
+
!include.trim() &&
|
|
120
|
+
!exclude.trim() &&
|
|
121
|
+
!skill.trim()
|
|
122
|
+
) {
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
id: typeof entry.id === 'string' && entry.id ? entry.id : randomId('style'),
|
|
128
|
+
name,
|
|
129
|
+
sources,
|
|
130
|
+
notes,
|
|
131
|
+
include,
|
|
132
|
+
exclude,
|
|
133
|
+
skill,
|
|
134
|
+
skillEnabled: entry.skillEnabled === true && skill.trim().length > 0,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function normalizeWritingStyle(value: unknown): BookWritingStyle {
|
|
139
|
+
const record =
|
|
140
|
+
value && typeof value === 'object' ? (value as Record<string, unknown>) : {}
|
|
141
|
+
// Current shape: { styles }. Legacy shape: { presets, examples }.
|
|
142
|
+
const source = Array.isArray(record.styles)
|
|
143
|
+
? record.styles
|
|
144
|
+
: Array.isArray(record.examples)
|
|
145
|
+
? record.examples
|
|
146
|
+
: []
|
|
147
|
+
const styles = source
|
|
148
|
+
.map(coerceStyle)
|
|
149
|
+
.filter((style): style is WritingStyle => style !== null)
|
|
150
|
+
return { styles }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Raw guidance for ONE style: the author's notes plus their voice samples,
|
|
155
|
+
* explicitly marked voice-only. Used both as the distillation input and as the
|
|
156
|
+
* applied guidance when no skill is enabled.
|
|
157
|
+
*/
|
|
158
|
+
export function styleRawGuidance(style: WritingStyle): string {
|
|
159
|
+
const lines: string[] = []
|
|
160
|
+
if (style.notes.trim()) lines.push(`Guidance: ${style.notes.trim()}`)
|
|
161
|
+
if (style.include.trim()) {
|
|
162
|
+
lines.push(
|
|
163
|
+
`Prefer these words and phrases where they fit naturally: ${normalizeWordList(style.include)}.`,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
if (style.exclude.trim()) {
|
|
167
|
+
lines.push(`Avoid these words and phrases entirely: ${normalizeWordList(style.exclude)}.`)
|
|
168
|
+
}
|
|
169
|
+
const samples = style.sources
|
|
170
|
+
.map(source => source.text.trim())
|
|
171
|
+
.filter(Boolean)
|
|
172
|
+
if (samples.length > 0) {
|
|
173
|
+
lines.push(
|
|
174
|
+
'Match the VOICE of these writing samples — sentence rhythm, tone, and word choice only. Do NOT reuse their wording, facts, or subject matter as content:',
|
|
175
|
+
)
|
|
176
|
+
samples.forEach((text, index) => {
|
|
177
|
+
lines.push(`Sample ${index + 1}: """${text.slice(0, 1200)}"""`)
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
return lines.join('\n')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Flatten a comma/newline-separated word list into a single comma-separated line. */
|
|
184
|
+
function normalizeWordList(value: string): string {
|
|
185
|
+
return value
|
|
186
|
+
.split(/[\n,]/)
|
|
187
|
+
.map(word => word.trim())
|
|
188
|
+
.filter(Boolean)
|
|
189
|
+
.join(', ')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** True when a style contributes anything to the prompt. */
|
|
193
|
+
export function styleHasContent(style: WritingStyle): boolean {
|
|
194
|
+
return (
|
|
195
|
+
style.skill.trim().length > 0 ||
|
|
196
|
+
style.notes.trim().length > 0 ||
|
|
197
|
+
style.include.trim().length > 0 ||
|
|
198
|
+
style.exclude.trim().length > 0 ||
|
|
199
|
+
style.sources.some(source => source.text.trim().length > 0)
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Build the style-guidance prompt block from the SELECTED styles. For each
|
|
205
|
+
* style, prefer its distilled skill when enabled; otherwise fall back to the
|
|
206
|
+
* raw notes + voice samples.
|
|
207
|
+
*/
|
|
208
|
+
export function buildStyleGuidance(styles: WritingStyle[]): string | undefined {
|
|
209
|
+
const blocks: string[] = []
|
|
210
|
+
styles.forEach(style => {
|
|
211
|
+
const useSkill = style.skillEnabled && style.skill.trim().length > 0
|
|
212
|
+
const body = useSkill ? style.skill.trim() : styleRawGuidance(style)
|
|
213
|
+
if (body.trim()) blocks.push(`Style — ${style.name}:\n${body.trim()}`)
|
|
214
|
+
})
|
|
215
|
+
if (blocks.length === 0) return undefined
|
|
216
|
+
return ['Writing style guidance:', ...blocks].join('\n\n')
|
|
217
|
+
}
|