@agimon-ai/doompi-author 0.0.1-alpha.2
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/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/adapters/authorApi.cjs +2 -0
- package/dist/adapters/authorApi.cjs.map +1 -0
- package/dist/adapters/authorBridgeApi.cjs +2 -0
- package/dist/adapters/authorBridgeApi.cjs.map +1 -0
- package/dist/adapters/authorDocumentApi.cjs +2 -0
- package/dist/adapters/authorDocumentApi.cjs.map +1 -0
- package/dist/adapters/pi/authorBridgeClient.cjs +2 -0
- package/dist/adapters/pi/authorBridgeClient.cjs.map +1 -0
- package/dist/adapters/pi/authorTools.cjs +2 -0
- package/dist/adapters/pi/authorTools.cjs.map +1 -0
- package/dist/adapters/pi/extension.cjs +2 -0
- package/dist/adapters/pi/extension.cjs.map +1 -0
- package/dist/adapters/structuredDocuments/common.cjs +2 -0
- package/dist/adapters/structuredDocuments/common.cjs.map +1 -0
- package/dist/adapters/structuredDocuments/csv.cjs +7 -0
- package/dist/adapters/structuredDocuments/csv.cjs.map +1 -0
- package/dist/adapters/structuredDocuments/index.cjs +2 -0
- package/dist/adapters/structuredDocuments/index.cjs.map +1 -0
- package/dist/adapters/structuredDocuments/markdownSlides.cjs +6 -0
- package/dist/adapters/structuredDocuments/markdownSlides.cjs.map +1 -0
- package/dist/adapters/structuredDocuments/ooxmlArchive.cjs +2 -0
- package/dist/adapters/structuredDocuments/ooxmlArchive.cjs.map +1 -0
- package/dist/adapters/structuredDocuments/pptx.cjs +2 -0
- package/dist/adapters/structuredDocuments/pptx.cjs.map +1 -0
- package/dist/adapters/structuredDocuments/xlsx.cjs +2 -0
- package/dist/adapters/structuredDocuments/xlsx.cjs.map +1 -0
- package/dist/adapters/structuredDocuments/xmlText.cjs +2 -0
- package/dist/adapters/structuredDocuments/xmlText.cjs.map +1 -0
- package/dist/adapters/webAuthorChannel.cjs +2 -0
- package/dist/adapters/webAuthorChannel.cjs.map +1 -0
- package/dist/commands/doomAuthorCommand.cjs +2 -0
- package/dist/commands/doomAuthorCommand.cjs.map +1 -0
- package/dist/container/index.cjs +2 -0
- package/dist/container/index.cjs.map +1 -0
- package/dist/extensions/pi.cjs +1 -0
- package/dist/extensions/pi.d.cts +2 -0
- package/dist/extensions/pi.d.mts +2 -0
- package/dist/extensions/pi.mjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.cts +6 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.cts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.mts +6 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.mts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.cts +7 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.cts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.mts +7 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.mts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.cts +12 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.cts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.mts +12 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.mts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.cts +12 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.cts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.mts +12 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.mts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.cts +9 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.cts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.mts +9 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.mts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.cts +4 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.cts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.mts +4 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.mts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.cts +9 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.cts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.mts +9 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.mts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.cts +8 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.cts.map +1 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.mts +8 -0
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.cts +15 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.mts +15 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.cts +6 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.mts +6 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.cts +11 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.mts +11 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.cts +18 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.mts +18 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.cts +9 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.mts +9 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.cts +19 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.mts +19 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/common.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/common.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.cts +7 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.mts +7 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.mjs +7 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.cts +13 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.mts +13 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.cts +7 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.mts +7 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.mjs +6 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.cts +13 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.mts +13 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.cts +7 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.mts +7 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.cts +7 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.mts +7 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xmlText.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xmlText.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.cts +6 -0
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.mts +6 -0
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/commands/doomAuthorCommand.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/commands/doomAuthorCommand.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/container/index.d.cts +5 -0
- package/dist/packages/minor/doompi-author/src/container/index.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/container/index.d.mts +5 -0
- package/dist/packages/minor/doompi-author/src/container/index.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/container/index.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/container/index.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.cts +16 -0
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.mts +16 -0
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.cts +31 -0
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.mts +31 -0
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.cts +10 -0
- package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.mts +10 -0
- package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/authorMode.d.cts +19 -0
- package/dist/packages/minor/doompi-author/src/services/authorMode.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/authorMode.d.mts +19 -0
- package/dist/packages/minor/doompi-author/src/services/authorMode.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/authorMode.mjs +3 -0
- package/dist/packages/minor/doompi-author/src/services/authorMode.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/extensionService.d.cts +7 -0
- package/dist/packages/minor/doompi-author/src/services/extensionService.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/extensionService.d.mts +7 -0
- package/dist/packages/minor/doompi-author/src/services/extensionService.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/services/extensionService.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/services/extensionService.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/author.d.cts +39 -0
- package/dist/packages/minor/doompi-author/src/types/author.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/author.d.mts +39 -0
- package/dist/packages/minor/doompi-author/src/types/author.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/author.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/types/author.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/authorApi.d.cts +23 -0
- package/dist/packages/minor/doompi-author/src/types/authorApi.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/authorApi.d.mts +23 -0
- package/dist/packages/minor/doompi-author/src/types/authorApi.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/authorApi.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/types/authorApi.mjs.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/extension.d.cts +16 -0
- package/dist/packages/minor/doompi-author/src/types/extension.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/extension.d.mts +16 -0
- package/dist/packages/minor/doompi-author/src/types/extension.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.cts +60 -0
- package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.mts +60 -0
- package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/webAuthor.d.cts +67 -0
- package/dist/packages/minor/doompi-author/src/types/webAuthor.d.cts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/webAuthor.d.mts +67 -0
- package/dist/packages/minor/doompi-author/src/types/webAuthor.d.mts.map +1 -0
- package/dist/packages/minor/doompi-author/src/types/webAuthor.mjs +2 -0
- package/dist/packages/minor/doompi-author/src/types/webAuthor.mjs.map +1 -0
- package/dist/schemas/authorTools.cjs +2 -0
- package/dist/schemas/authorTools.cjs.map +1 -0
- package/dist/services/authorBridgeState.cjs +2 -0
- package/dist/services/authorBridgeState.cjs.map +1 -0
- package/dist/services/authorMode.cjs +3 -0
- package/dist/services/authorMode.cjs.map +1 -0
- package/dist/services/extensionService.cjs +2 -0
- package/dist/services/extensionService.cjs.map +1 -0
- package/dist/sessionApi.cjs +1 -0
- package/dist/sessionApi.d.cts +7 -0
- package/dist/sessionApi.d.mts +7 -0
- package/dist/sessionApi.mjs +1 -0
- package/dist/types/author.cjs +2 -0
- package/dist/types/author.cjs.map +1 -0
- package/dist/types/authorApi.cjs +2 -0
- package/dist/types/authorApi.cjs.map +1 -0
- package/dist/types/webAuthor.cjs +2 -0
- package/dist/types/webAuthor.cjs.map +1 -0
- package/dist/webHub.cjs +1 -0
- package/dist/webHub.d.cts +2 -0
- package/dist/webHub.d.mts +2 -0
- package/dist/webHub.mjs +1 -0
- package/llms.txt +11 -0
- package/package.json +140 -0
- package/src/exports/webClient.ts +76 -0
- package/src/prompts/doompi-use-author/SKILL.md +17 -0
- package/src/types/author.ts +45 -0
- package/src/types/structuredDocuments.ts +63 -0
- package/src/types/webAuthor.ts +85 -0
- package/src/web/api/authorBroker.ts +14 -0
- package/src/web/api/authorBrowserBridge.ts +225 -0
- package/src/web/api/authorFiles.ts +219 -0
- package/src/web/api/authorMedia.ts +19 -0
- package/src/web/api/authorRuntime.ts +164 -0
- package/src/web/components/AuthorDocumentPanel.tsx +232 -0
- package/src/web/components/AuthorGridOverlay.tsx +81 -0
- package/src/web/components/AuthorMediaPreview.tsx +41 -0
- package/src/web/components/AuthorMediaView.tsx +356 -0
- package/src/web/components/AuthorPanel.tsx +105 -0
- package/src/web/components/AuthorRegionDrafts.tsx +102 -0
- package/src/web/components/AuthorRequestLog.tsx +179 -0
- package/src/web/components/AuthorStructuredView.tsx +121 -0
- package/src/web/components/AuthorTextView.tsx +97 -0
- package/src/web/components/AuthorToolPalette.tsx +103 -0
- package/src/web/components/AuthorVideoControls.tsx +111 -0
- package/src/web/components/DescribeAuthorToolsToolCard.tsx +50 -0
- package/src/web/components/OpenAuthoringFileToolCard.tsx +66 -0
- package/src/web/components/UseAuthorToolsToolCard.tsx +50 -0
- package/src/web/index.ts +68 -0
- package/src/web/lib/authorGrid.ts +124 -0
- package/src/web/lib/authorRegions.ts +30 -0
- package/src/web/lib/authorViewportTypes.ts +202 -0
- package/src/web/lib/describeAuthorToolsToolRender.ts +20 -0
- package/src/web/lib/useAuthorToolsToolRender.ts +20 -0
- package/src/web/stores/authorCapture.ts +322 -0
- package/src/web/stores/authorGridTools.ts +316 -0
- package/src/web/stores/authorProfiles.ts +130 -0
- package/src/web/stores/authorRequestLifecycle.ts +82 -0
- package/src/web/stores/authorStore.ts +60 -0
- package/src/web/stores/authorWorkspaceStore.ts +525 -0
- package/src/web/tsconfig.json +22 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import { defineGlobalStore } from '@agimon-ai/doompi-web-contracts';
|
|
2
|
+
import type {
|
|
3
|
+
AuthorAnnotation,
|
|
4
|
+
AuthorCrop,
|
|
5
|
+
AuthorDocumentInput,
|
|
6
|
+
AuthorDraftRevision,
|
|
7
|
+
AuthorFocusedDocument,
|
|
8
|
+
AuthorRegionCandidate,
|
|
9
|
+
AuthorRegionDraft,
|
|
10
|
+
AuthorRequestRecord,
|
|
11
|
+
AuthorRequestStatus,
|
|
12
|
+
AuthorToolMode,
|
|
13
|
+
} from '../lib/authorViewportTypes.ts';
|
|
14
|
+
|
|
15
|
+
export const AUTHOR_REGION_LIMIT = 16;
|
|
16
|
+
export const AUTHOR_HISTORY_RECORD_LIMIT = 100;
|
|
17
|
+
export const AUTHOR_HISTORY_BYTE_LIMIT = 512 * 1024;
|
|
18
|
+
const TERMINAL_REQUEST_STATUSES: ReadonlySet<AuthorRequestStatus> = new Set(['COMPLETE', 'FAILED', 'CANCELLED']);
|
|
19
|
+
|
|
20
|
+
export interface AuthorWorkspaceDocument extends AuthorDocumentInput {
|
|
21
|
+
path: string;
|
|
22
|
+
annotations: readonly AuthorAnnotation[];
|
|
23
|
+
revisions: readonly AuthorDraftRevision[];
|
|
24
|
+
crop?: AuthorCrop;
|
|
25
|
+
saveRequest: number;
|
|
26
|
+
version: number;
|
|
27
|
+
savedVersion: number;
|
|
28
|
+
savingVersion?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AuthorSessionWorkspace {
|
|
32
|
+
generation: number;
|
|
33
|
+
focusedDocument?: AuthorFocusedDocument;
|
|
34
|
+
activeTool: AuthorToolMode;
|
|
35
|
+
candidate?: AuthorRegionCandidate;
|
|
36
|
+
videoSeekRequest?: { path: string; generation: number; timeSeconds: number; sequence: number };
|
|
37
|
+
regions: readonly AuthorRegionDraft[];
|
|
38
|
+
requests: readonly AuthorRequestRecord[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AuthorWorkspaceState {
|
|
42
|
+
documents: Readonly<Record<string, AuthorWorkspaceDocument>>;
|
|
43
|
+
sessions: Readonly<Record<string, AuthorSessionWorkspace>>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const EMPTY_SESSION: AuthorSessionWorkspace = { generation: 0, activeTool: 'select', regions: [], requests: [] };
|
|
47
|
+
|
|
48
|
+
export function normalizeAuthorPath(path: string): string {
|
|
49
|
+
const parts: string[] = [];
|
|
50
|
+
for (const part of path.trim().replaceAll('\\', '/').split('/')) {
|
|
51
|
+
if (part === '' || part === '.') continue;
|
|
52
|
+
if (part === '..') parts.pop();
|
|
53
|
+
else parts.push(part);
|
|
54
|
+
}
|
|
55
|
+
return parts.join('/');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function authorDocumentKey(sessionId: string, path: string): string {
|
|
59
|
+
return `${sessionId}\n${normalizeAuthorPath(path)}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const authorWorkspace = defineGlobalStore<AuthorWorkspaceState>({ documents: {}, sessions: {} });
|
|
63
|
+
|
|
64
|
+
export function authorDocument(sessionId: string | null, path: string): AuthorWorkspaceDocument | undefined {
|
|
65
|
+
if (sessionId === null) return undefined;
|
|
66
|
+
return authorWorkspace.store.state.documents[authorDocumentKey(sessionId, path)];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function authorSessionWorkspace(sessionId: string | null): AuthorSessionWorkspace {
|
|
70
|
+
if (sessionId === null) return EMPTY_SESSION;
|
|
71
|
+
return authorWorkspace.store.state.sessions[sessionId] ?? EMPTY_SESSION;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function putAuthorDocument(sessionId: string, input: AuthorDocumentInput): AuthorWorkspaceDocument {
|
|
75
|
+
const path = normalizeAuthorPath(input.path);
|
|
76
|
+
if (path === '') throw new Error('Author document path must not be empty');
|
|
77
|
+
const key = authorDocumentKey(sessionId, path);
|
|
78
|
+
const staleThumbnails: string[] = [];
|
|
79
|
+
let result!: AuthorWorkspaceDocument;
|
|
80
|
+
authorWorkspace.update((state) => {
|
|
81
|
+
const previous = state.documents[key];
|
|
82
|
+
const sourceChanged =
|
|
83
|
+
previous?.sourceSha256 !== undefined &&
|
|
84
|
+
input.sourceSha256 !== undefined &&
|
|
85
|
+
previous.sourceSha256 !== input.sourceSha256;
|
|
86
|
+
const baselineVersion = sourceChanged ? previous.version + 1 : (previous?.version ?? 0);
|
|
87
|
+
result = {
|
|
88
|
+
...input,
|
|
89
|
+
path,
|
|
90
|
+
annotations: sourceChanged ? [] : (previous?.annotations ?? []),
|
|
91
|
+
revisions: sourceChanged ? [] : (previous?.revisions ?? []),
|
|
92
|
+
saveRequest: previous?.saveRequest ?? 0,
|
|
93
|
+
version: baselineVersion,
|
|
94
|
+
savedVersion: sourceChanged ? baselineVersion : (previous?.savedVersion ?? 0),
|
|
95
|
+
...(sourceChanged || previous?.crop === undefined ? {} : { crop: previous.crop }),
|
|
96
|
+
};
|
|
97
|
+
const session = state.sessions[sessionId];
|
|
98
|
+
let sessions = state.sessions;
|
|
99
|
+
if (sourceChanged && session?.focusedDocument?.path === path) {
|
|
100
|
+
staleThumbnails.push(
|
|
101
|
+
...(session.candidate?.thumbnailUrl === undefined ? [] : [session.candidate.thumbnailUrl]),
|
|
102
|
+
...session.regions.flatMap((region) => (region.thumbnailUrl === undefined ? [] : [region.thumbnailUrl])),
|
|
103
|
+
);
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
const generation = session.generation + 1;
|
|
106
|
+
sessions = {
|
|
107
|
+
...sessions,
|
|
108
|
+
[sessionId]: {
|
|
109
|
+
...session,
|
|
110
|
+
generation,
|
|
111
|
+
focusedDocument: {
|
|
112
|
+
path,
|
|
113
|
+
generation,
|
|
114
|
+
revision: baselineVersion,
|
|
115
|
+
sourceSha256: input.sourceSha256,
|
|
116
|
+
focusedAt: now,
|
|
117
|
+
},
|
|
118
|
+
regions: [],
|
|
119
|
+
candidate: undefined,
|
|
120
|
+
requests: session.requests.map((request) =>
|
|
121
|
+
request.documentPath === path &&
|
|
122
|
+
(request.status === 'REQUESTED' || request.status === 'CHANGING' || request.status === 'CHANGED')
|
|
123
|
+
? {
|
|
124
|
+
...request,
|
|
125
|
+
status: 'FAILED',
|
|
126
|
+
currentOperation: undefined,
|
|
127
|
+
error: 'The document source changed before the request completed.',
|
|
128
|
+
updatedAt: now,
|
|
129
|
+
}
|
|
130
|
+
: request,
|
|
131
|
+
),
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return { documents: { ...state.documents, [key]: result }, sessions };
|
|
136
|
+
});
|
|
137
|
+
staleThumbnails.forEach(revokeThumbnail);
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Claims focus and returns a generation token that makes cleanup race-safe. */
|
|
142
|
+
export function focusAuthorDocument(sessionId: string, path: string, revision: number, sourceSha256?: string): number {
|
|
143
|
+
const normalized = normalizeAuthorPath(path);
|
|
144
|
+
const staleThumbnails: string[] = [];
|
|
145
|
+
let generation = 0;
|
|
146
|
+
authorWorkspace.update((state) => {
|
|
147
|
+
const session = state.sessions[sessionId] ?? EMPTY_SESSION;
|
|
148
|
+
generation = session.generation + 1;
|
|
149
|
+
const keepRegions = session.focusedDocument?.path === normalized;
|
|
150
|
+
if (!keepRegions) {
|
|
151
|
+
staleThumbnails.push(
|
|
152
|
+
...(session.candidate?.thumbnailUrl === undefined ? [] : [session.candidate.thumbnailUrl]),
|
|
153
|
+
...session.regions.flatMap((region) => (region.thumbnailUrl === undefined ? [] : [region.thumbnailUrl])),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
...state,
|
|
158
|
+
sessions: {
|
|
159
|
+
...state.sessions,
|
|
160
|
+
[sessionId]: {
|
|
161
|
+
...session,
|
|
162
|
+
generation,
|
|
163
|
+
focusedDocument: {
|
|
164
|
+
path: normalized,
|
|
165
|
+
generation,
|
|
166
|
+
revision,
|
|
167
|
+
sourceSha256,
|
|
168
|
+
focusedAt: Date.now(),
|
|
169
|
+
},
|
|
170
|
+
regions: keepRegions ? session.regions : [],
|
|
171
|
+
candidate: keepRegions ? session.candidate : undefined,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
staleThumbnails.forEach(revokeThumbnail);
|
|
177
|
+
return generation;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function syncAuthorDocumentFocus(
|
|
181
|
+
sessionId: string,
|
|
182
|
+
generation: number,
|
|
183
|
+
revision: number,
|
|
184
|
+
sourceSha256?: string,
|
|
185
|
+
): void {
|
|
186
|
+
updateSession(sessionId, (session) => {
|
|
187
|
+
const focused = session.focusedDocument;
|
|
188
|
+
if (focused === undefined || focused.generation !== generation) return session;
|
|
189
|
+
if (focused.revision === revision && focused.sourceSha256 === sourceSha256) return session;
|
|
190
|
+
return { ...session, focusedDocument: { ...focused, revision, sourceSha256 } };
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function releaseAuthorDocumentFocus(sessionId: string, generation: number): void {
|
|
195
|
+
const staleThumbnails: string[] = [];
|
|
196
|
+
updateSession(sessionId, (session) => {
|
|
197
|
+
if (session.focusedDocument?.generation !== generation) return session;
|
|
198
|
+
staleThumbnails.push(
|
|
199
|
+
...(session.candidate?.thumbnailUrl === undefined ? [] : [session.candidate.thumbnailUrl]),
|
|
200
|
+
...session.regions.flatMap((region) => (region.thumbnailUrl === undefined ? [] : [region.thumbnailUrl])),
|
|
201
|
+
);
|
|
202
|
+
return { ...session, focusedDocument: undefined, candidate: undefined, regions: [] };
|
|
203
|
+
});
|
|
204
|
+
staleThumbnails.forEach(revokeThumbnail);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function seekAuthorVideo(sessionId: string, timeSeconds: number): boolean {
|
|
208
|
+
const workspace = authorSessionWorkspace(sessionId);
|
|
209
|
+
const focused = workspace.focusedDocument;
|
|
210
|
+
if (
|
|
211
|
+
!focused ||
|
|
212
|
+
workspace.candidate ||
|
|
213
|
+
!Number.isFinite(timeSeconds) ||
|
|
214
|
+
timeSeconds < 0 ||
|
|
215
|
+
authorDocument(sessionId, focused.path)?.kind !== 'video'
|
|
216
|
+
)
|
|
217
|
+
return false;
|
|
218
|
+
updateSession(sessionId, (session) => ({
|
|
219
|
+
...session,
|
|
220
|
+
activeTool: 'select',
|
|
221
|
+
videoSeekRequest: {
|
|
222
|
+
path: focused.path,
|
|
223
|
+
generation: focused.generation,
|
|
224
|
+
timeSeconds,
|
|
225
|
+
sequence: (session.videoSeekRequest?.sequence ?? 0) + 1,
|
|
226
|
+
},
|
|
227
|
+
}));
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function setAuthorToolMode(sessionId: string, activeTool: AuthorToolMode): void {
|
|
232
|
+
updateSession(sessionId, (session) => (session.activeTool === activeTool ? session : { ...session, activeTool }));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function setAuthorRegionCandidate(sessionId: string, candidate: AuthorRegionCandidate | undefined): void {
|
|
236
|
+
let staleThumbnail: string | undefined;
|
|
237
|
+
updateSession(sessionId, (session) => {
|
|
238
|
+
const focused = session.focusedDocument;
|
|
239
|
+
if (candidate !== undefined) {
|
|
240
|
+
if (focused === undefined || focused.path !== normalizeAuthorPath(candidate.documentPath)) {
|
|
241
|
+
throw new Error('The Author selection does not belong to the focused document.');
|
|
242
|
+
}
|
|
243
|
+
if (focused.revision !== candidate.revision || focused.sourceSha256 !== candidate.sourceSha256) {
|
|
244
|
+
throw new Error('The Author selection is stale for the focused document.');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
staleThumbnail = session.candidate?.thumbnailUrl;
|
|
248
|
+
return { ...session, candidate: candidate === undefined ? undefined : structuredClone(candidate) };
|
|
249
|
+
});
|
|
250
|
+
if (staleThumbnail !== undefined && staleThumbnail !== candidate?.thumbnailUrl) revokeThumbnail(staleThumbnail);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function commitAuthorRegion(sessionId: string, comment: string): string {
|
|
254
|
+
const id = crypto.randomUUID();
|
|
255
|
+
const session = authorSessionWorkspace(sessionId);
|
|
256
|
+
if (session.candidate === undefined) throw new Error('Select a document region before adding a comment.');
|
|
257
|
+
addAuthorRegion(sessionId, { ...session.candidate, id, comment });
|
|
258
|
+
updateSession(sessionId, (current) => ({ ...current, candidate: undefined }));
|
|
259
|
+
return id;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function addAuthorRegion(sessionId: string, region: AuthorRegionDraft): void {
|
|
263
|
+
if (region.comment.trim() === '') throw new Error('Every Author region requires a comment.');
|
|
264
|
+
updateSession(sessionId, (session) => {
|
|
265
|
+
const focused = session.focusedDocument;
|
|
266
|
+
if (focused === undefined || focused.path !== normalizeAuthorPath(region.documentPath)) {
|
|
267
|
+
throw new Error('The Author region does not belong to the focused document.');
|
|
268
|
+
}
|
|
269
|
+
if (focused.revision !== region.revision || focused.sourceSha256 !== region.sourceSha256) {
|
|
270
|
+
throw new Error('The Author region is stale for the focused document.');
|
|
271
|
+
}
|
|
272
|
+
if (session.regions.length >= AUTHOR_REGION_LIMIT)
|
|
273
|
+
throw new Error(`Author requests support at most ${AUTHOR_REGION_LIMIT} regions.`);
|
|
274
|
+
if (session.regions.some((candidate) => candidate.id === region.id))
|
|
275
|
+
throw new Error(`Author region '${region.id}' already exists.`);
|
|
276
|
+
return { ...session, regions: [...session.regions, copyRegion(region)] };
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function removeAuthorRegion(sessionId: string, regionId: string): void {
|
|
281
|
+
let thumbnail: string | undefined;
|
|
282
|
+
updateSession(sessionId, (session) => {
|
|
283
|
+
const region = session.regions.find((candidate) => candidate.id === regionId);
|
|
284
|
+
thumbnail = region?.thumbnailUrl;
|
|
285
|
+
const regions = session.regions.filter((candidate) => candidate.id !== regionId);
|
|
286
|
+
return regions.length === session.regions.length ? session : { ...session, regions };
|
|
287
|
+
});
|
|
288
|
+
if (thumbnail !== undefined) revokeThumbnail(thumbnail);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function updateAuthorRegionComment(sessionId: string, regionId: string, comment: string): void {
|
|
292
|
+
if (comment.trim() === '') throw new Error('Every Author region requires a comment.');
|
|
293
|
+
updateSession(sessionId, (session) => ({
|
|
294
|
+
...session,
|
|
295
|
+
regions: session.regions.map((region) => (region.id === regionId ? { ...region, comment } : region)),
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function putAuthorRequest(sessionId: string, record: AuthorRequestRecord): void {
|
|
300
|
+
if (record.requestText.trim() === '') throw new Error('An Author request requires verbatim request text.');
|
|
301
|
+
if (record.regions.length === 0 || record.regions.some((region) => region.comment.trim() === '')) {
|
|
302
|
+
throw new Error('An Author request requires at least one commented region.');
|
|
303
|
+
}
|
|
304
|
+
updateSession(sessionId, (session) => {
|
|
305
|
+
if (session.requests.some((request) => request.id === record.id))
|
|
306
|
+
throw new Error(`Author request '${record.id}' already exists.`);
|
|
307
|
+
return { ...session, requests: boundHistory([...session.requests, copyRequest(record)]) };
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function updateAuthorRequest(
|
|
312
|
+
sessionId: string,
|
|
313
|
+
requestId: string,
|
|
314
|
+
update: (record: AuthorRequestRecord) => AuthorRequestRecord,
|
|
315
|
+
): void {
|
|
316
|
+
updateSession(sessionId, (session) => ({
|
|
317
|
+
...session,
|
|
318
|
+
requests: boundHistory(
|
|
319
|
+
session.requests.map((request) => (request.id === requestId ? copyRequest(update(request)) : request)),
|
|
320
|
+
),
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function reviseAuthorDocument(sessionId: string, path: string, content: string): void {
|
|
325
|
+
updateDocument(sessionId, path, (document) => {
|
|
326
|
+
if (document.content === content) return document;
|
|
327
|
+
const version = document.version + 1;
|
|
328
|
+
return {
|
|
329
|
+
...document,
|
|
330
|
+
content,
|
|
331
|
+
version,
|
|
332
|
+
revisions: [...document.revisions, { revision: version, content }],
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function reviseAuthorFragment(sessionId: string, path: string, fragmentId: string, text: string): void {
|
|
338
|
+
updateDocument(sessionId, path, (document) => {
|
|
339
|
+
const fragment = document.fragments?.find((candidate) => candidate.id === fragmentId);
|
|
340
|
+
if (fragment === undefined || fragment.text === text) return document;
|
|
341
|
+
const version = document.version + 1;
|
|
342
|
+
return {
|
|
343
|
+
...document,
|
|
344
|
+
fragments: document.fragments?.map((fragment) => (fragment.id === fragmentId ? { ...fragment, text } : fragment)),
|
|
345
|
+
version,
|
|
346
|
+
revisions: [...document.revisions, { revision: version, content: text }],
|
|
347
|
+
};
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function addAuthorAnnotation(sessionId: string, path: string, annotation: AuthorAnnotation): void {
|
|
352
|
+
updateDocument(sessionId, path, (document) => ({ ...document, annotations: [...document.annotations, annotation] }));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function setAuthorCrop(sessionId: string, path: string, crop: AuthorCrop | undefined): void {
|
|
356
|
+
updateDocument(sessionId, path, (document) => {
|
|
357
|
+
const version = document.version + 1;
|
|
358
|
+
return {
|
|
359
|
+
...document,
|
|
360
|
+
...(crop === undefined ? { crop: undefined } : { crop }),
|
|
361
|
+
version,
|
|
362
|
+
revisions: [...document.revisions, { revision: version, content: JSON.stringify(crop ?? null) }],
|
|
363
|
+
};
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function requestAuthorSave(sessionId: string, path: string): void {
|
|
368
|
+
updateDocument(sessionId, path, (document) => ({
|
|
369
|
+
...document,
|
|
370
|
+
saveRequest: document.saveRequest + 1,
|
|
371
|
+
savingVersion: document.version,
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function failAuthorSave(sessionId: string, path: string, savingVersion: number): void {
|
|
376
|
+
updateDocument(sessionId, path, (document) =>
|
|
377
|
+
document.savingVersion === savingVersion ? { ...document, savingVersion: undefined } : document,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function completeAuthorSave(
|
|
382
|
+
sessionId: string,
|
|
383
|
+
path: string,
|
|
384
|
+
sourceSha256: string,
|
|
385
|
+
savedVersion: number,
|
|
386
|
+
savedFragments: AuthorDocumentInput['fragments'],
|
|
387
|
+
): void {
|
|
388
|
+
updateDocument(sessionId, path, (document) => {
|
|
389
|
+
if (savedVersion < document.savedVersion) return document;
|
|
390
|
+
return {
|
|
391
|
+
...document,
|
|
392
|
+
sourceSha256,
|
|
393
|
+
savedVersion,
|
|
394
|
+
savingVersion: document.savingVersion === savedVersion ? undefined : document.savingVersion,
|
|
395
|
+
originalFragments: savedFragments?.map((fragment) => ({ ...fragment })),
|
|
396
|
+
...(document.version === savedVersion ? { crop: undefined } : {}),
|
|
397
|
+
revisions: document.revisions.filter((revision) => revision.revision > savedVersion),
|
|
398
|
+
};
|
|
399
|
+
});
|
|
400
|
+
const now = Date.now();
|
|
401
|
+
updateSession(sessionId, (session) => ({
|
|
402
|
+
...session,
|
|
403
|
+
requests: boundHistory(
|
|
404
|
+
session.requests.map((request) =>
|
|
405
|
+
request.documentPath === normalizeAuthorPath(path) &&
|
|
406
|
+
request.status === 'CHANGED' &&
|
|
407
|
+
request.revision <= savedVersion
|
|
408
|
+
? { ...request, status: 'COMPLETE', currentOperation: undefined, updatedAt: now }
|
|
409
|
+
: request,
|
|
410
|
+
),
|
|
411
|
+
),
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function dropAuthorSession(sessionId: string): void {
|
|
416
|
+
const prefix = `${sessionId}\n`;
|
|
417
|
+
const session = authorWorkspace.store.state.sessions[sessionId];
|
|
418
|
+
if (session?.candidate?.thumbnailUrl !== undefined) revokeThumbnail(session.candidate.thumbnailUrl);
|
|
419
|
+
session?.regions.forEach((region) => {
|
|
420
|
+
if (region.thumbnailUrl !== undefined) revokeThumbnail(region.thumbnailUrl);
|
|
421
|
+
});
|
|
422
|
+
authorWorkspace.update((state) => {
|
|
423
|
+
const sessions = { ...state.sessions };
|
|
424
|
+
delete sessions[sessionId];
|
|
425
|
+
return {
|
|
426
|
+
documents: Object.fromEntries(Object.entries(state.documents).filter(([key]) => !key.startsWith(prefix))),
|
|
427
|
+
sessions,
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function updateDocument(
|
|
433
|
+
sessionId: string,
|
|
434
|
+
path: string,
|
|
435
|
+
update: (document: AuthorWorkspaceDocument) => AuthorWorkspaceDocument,
|
|
436
|
+
): void {
|
|
437
|
+
const key = authorDocumentKey(sessionId, path);
|
|
438
|
+
const staleThumbnails: string[] = [];
|
|
439
|
+
authorWorkspace.update((state) => {
|
|
440
|
+
const document = state.documents[key];
|
|
441
|
+
if (document === undefined) return state;
|
|
442
|
+
const next = update(document);
|
|
443
|
+
const session = state.sessions[sessionId];
|
|
444
|
+
const focused = session?.focusedDocument;
|
|
445
|
+
const identityChanged =
|
|
446
|
+
focused?.path === document.path &&
|
|
447
|
+
(focused.revision !== next.version || focused.sourceSha256 !== next.sourceSha256);
|
|
448
|
+
if (identityChanged) {
|
|
449
|
+
staleThumbnails.push(
|
|
450
|
+
...(session.candidate?.thumbnailUrl === undefined ? [] : [session.candidate.thumbnailUrl]),
|
|
451
|
+
...session.regions.flatMap((region) => (region.thumbnailUrl === undefined ? [] : [region.thumbnailUrl])),
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const sessions = identityChanged
|
|
455
|
+
? {
|
|
456
|
+
...state.sessions,
|
|
457
|
+
[sessionId]: {
|
|
458
|
+
...session,
|
|
459
|
+
focusedDocument: { ...focused, revision: next.version, sourceSha256: next.sourceSha256 },
|
|
460
|
+
candidate: undefined,
|
|
461
|
+
regions: [],
|
|
462
|
+
},
|
|
463
|
+
}
|
|
464
|
+
: state.sessions;
|
|
465
|
+
return { documents: { ...state.documents, [key]: next }, sessions };
|
|
466
|
+
});
|
|
467
|
+
staleThumbnails.forEach(revokeThumbnail);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function updateSession(sessionId: string, update: (session: AuthorSessionWorkspace) => AuthorSessionWorkspace): void {
|
|
471
|
+
authorWorkspace.update((state) => {
|
|
472
|
+
const session = state.sessions[sessionId] ?? EMPTY_SESSION;
|
|
473
|
+
const next = update(session);
|
|
474
|
+
return next === session ? state : { ...state, sessions: { ...state.sessions, [sessionId]: next } };
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function copyRegion(region: AuthorRegionDraft): AuthorRegionDraft {
|
|
479
|
+
const anchor = 'rect' in region.anchor ? { ...region.anchor, rect: { ...region.anchor.rect } } : { ...region.anchor };
|
|
480
|
+
return {
|
|
481
|
+
...region,
|
|
482
|
+
anchor,
|
|
483
|
+
viewport: { ...region.viewport },
|
|
484
|
+
voiceGrid: region.voiceGrid && { ...region.voiceGrid },
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function boundedHistoryText(value: string | undefined): string | undefined {
|
|
489
|
+
if (value === undefined) return undefined;
|
|
490
|
+
const encoder = new TextEncoder();
|
|
491
|
+
if (encoder.encode(value).byteLength <= 16 * 1024) return value;
|
|
492
|
+
let end = value.length;
|
|
493
|
+
while (end > 0 && encoder.encode(value.slice(0, end)).byteLength > 16 * 1024) end -= 256;
|
|
494
|
+
return value.slice(0, end);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function copyRequest(record: AuthorRequestRecord): AuthorRequestRecord {
|
|
498
|
+
return {
|
|
499
|
+
...record,
|
|
500
|
+
before: boundedHistoryText(record.before),
|
|
501
|
+
after: boundedHistoryText(record.after),
|
|
502
|
+
regions: record.regions.map(copyRegion),
|
|
503
|
+
pendingRegions: record.pendingRegions?.map(copyRegion),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function boundHistory(records: readonly AuthorRequestRecord[]): readonly AuthorRequestRecord[] {
|
|
508
|
+
const next = [...records];
|
|
509
|
+
const bytes = (): number => new TextEncoder().encode(JSON.stringify(next)).byteLength;
|
|
510
|
+
while (next.length > AUTHOR_HISTORY_RECORD_LIMIT || bytes() > AUTHOR_HISTORY_BYTE_LIMIT) {
|
|
511
|
+
const terminal = next.findIndex((record) => TERMINAL_REQUEST_STATUSES.has(record.status));
|
|
512
|
+
if (terminal < 0) throw new Error('Author request history is full while active requests are still running.');
|
|
513
|
+
next.splice(terminal, 1);
|
|
514
|
+
}
|
|
515
|
+
return next;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function revokeThumbnail(url: string): void {
|
|
519
|
+
if (!url.startsWith('blob:')) return;
|
|
520
|
+
try {
|
|
521
|
+
URL.revokeObjectURL(url);
|
|
522
|
+
} catch {
|
|
523
|
+
// Thumbnail cleanup must not make session teardown fail.
|
|
524
|
+
}
|
|
525
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noImplicitAny": true,
|
|
10
|
+
"noUnusedLocals": true,
|
|
11
|
+
"noUnusedParameters": true,
|
|
12
|
+
"noImplicitReturns": true,
|
|
13
|
+
"noFallthroughCasesInSwitch": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"allowImportingTsExtensions": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true
|
|
20
|
+
},
|
|
21
|
+
"include": [".", "../types", "../exports/webClient.ts", "../../tests/web"]
|
|
22
|
+
}
|