@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,81 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
AUTHOR_GRID_COLUMNS,
|
|
4
|
+
AUTHOR_GRID_SIZE,
|
|
5
|
+
clearAuthorGridGeometry,
|
|
6
|
+
updateAuthorGridGeometry,
|
|
7
|
+
} from '../lib/authorGrid.ts';
|
|
8
|
+
import type { AuthorWorkspaceDocument } from '../stores/authorWorkspaceStore.ts';
|
|
9
|
+
|
|
10
|
+
export function autonomousVoiceGridVisible(statuses: Readonly<Record<string, string>>): boolean {
|
|
11
|
+
return statuses['doom-voice']?.trimStart().startsWith('voice auto:') === true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function AuthorGridOverlay({
|
|
15
|
+
sessionId,
|
|
16
|
+
document,
|
|
17
|
+
visible,
|
|
18
|
+
}: {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
document: AuthorWorkspaceDocument;
|
|
21
|
+
visible: boolean;
|
|
22
|
+
}) {
|
|
23
|
+
const host = useRef<HTMLDivElement>(null);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const element = host.current?.parentElement;
|
|
26
|
+
if (!visible || element === null || element === undefined) {
|
|
27
|
+
clearAuthorGridGeometry(sessionId);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const publish = () => {
|
|
31
|
+
const bounds = element.getBoundingClientRect();
|
|
32
|
+
updateAuthorGridGeometry(sessionId, {
|
|
33
|
+
documentPath: document.path,
|
|
34
|
+
revision: document.version,
|
|
35
|
+
sourceSha256: document.sourceSha256,
|
|
36
|
+
viewport: {
|
|
37
|
+
width: bounds.width,
|
|
38
|
+
height: bounds.height,
|
|
39
|
+
originX: bounds.left,
|
|
40
|
+
originY: bounds.top,
|
|
41
|
+
scrollX: element.scrollLeft,
|
|
42
|
+
scrollY: element.scrollTop,
|
|
43
|
+
zoom: window.devicePixelRatio,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
publish();
|
|
48
|
+
const observer = new ResizeObserver(publish);
|
|
49
|
+
observer.observe(element);
|
|
50
|
+
element.addEventListener('scroll', publish, { passive: true, capture: true });
|
|
51
|
+
window.addEventListener('focus', publish);
|
|
52
|
+
return () => {
|
|
53
|
+
observer.disconnect();
|
|
54
|
+
element.removeEventListener('scroll', publish, true);
|
|
55
|
+
window.removeEventListener('focus', publish);
|
|
56
|
+
clearAuthorGridGeometry(sessionId);
|
|
57
|
+
};
|
|
58
|
+
}, [document.path, document.sourceSha256, document.version, sessionId, visible]);
|
|
59
|
+
if (!visible) return null;
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
ref={host}
|
|
63
|
+
data-testid="author-grid"
|
|
64
|
+
aria-hidden="true"
|
|
65
|
+
className="pointer-events-none absolute inset-0 z-20 grid grid-cols-8 grid-rows-8"
|
|
66
|
+
>
|
|
67
|
+
{Array.from({ length: AUTHOR_GRID_SIZE * AUTHOR_GRID_SIZE }, (_, index) => {
|
|
68
|
+
const column = index % AUTHOR_GRID_SIZE;
|
|
69
|
+
const row = Math.floor(index / AUTHOR_GRID_SIZE);
|
|
70
|
+
return (
|
|
71
|
+
<div key={index} className="relative border border-doom-red/25">
|
|
72
|
+
<span className="absolute left-1 top-0.5 rounded bg-doom-deep/80 px-1 text-[8px] font-bold text-doom-red">
|
|
73
|
+
{AUTHOR_GRID_COLUMNS[column]}
|
|
74
|
+
{row + 1}
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
})}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MediaPreview } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { authorSessionFileUrl } from '../api/authorFiles.ts';
|
|
4
|
+
import { loadAuthorMedia } from '../api/authorMedia.ts';
|
|
5
|
+
|
|
6
|
+
/** Viewing an attachment does not require enabling authoring or its editing tools. */
|
|
7
|
+
export function AuthorMediaPreview({ sessionId, path }: { sessionId: string; path: string }) {
|
|
8
|
+
const source = authorSessionFileUrl(sessionId, path);
|
|
9
|
+
const [loaded, setLoaded] = useState<{ source: string; url?: string; error?: string }>();
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
let dispose: (() => void) | undefined;
|
|
13
|
+
void loadAuthorMedia(source, controller.signal)
|
|
14
|
+
.then((asset) => {
|
|
15
|
+
if (controller.signal.aborted) {
|
|
16
|
+
asset.dispose();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
dispose = () => asset.dispose();
|
|
20
|
+
setLoaded({ source, url: asset.url });
|
|
21
|
+
})
|
|
22
|
+
.catch((error: unknown) => {
|
|
23
|
+
if (!controller.signal.aborted)
|
|
24
|
+
setLoaded({ source, error: error instanceof Error ? error.message : String(error) });
|
|
25
|
+
});
|
|
26
|
+
return () => {
|
|
27
|
+
controller.abort();
|
|
28
|
+
dispose?.();
|
|
29
|
+
};
|
|
30
|
+
}, [source]);
|
|
31
|
+
const current = loaded?.source === source ? loaded : undefined;
|
|
32
|
+
return (
|
|
33
|
+
<div className="min-h-0 flex-1 overflow-auto p-4">
|
|
34
|
+
{current?.url === undefined ? (
|
|
35
|
+
<p role={current?.error ? 'alert' : 'status'}>{current?.error ?? 'Loading media...'}</p>
|
|
36
|
+
) : (
|
|
37
|
+
<MediaPreview src={current.url} path={path} data-testid="author-media-preview" />
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MediaPreview,
|
|
3
|
+
type MediaPlaybackState,
|
|
4
|
+
type MediaPreviewController,
|
|
5
|
+
type PdfPreviewController,
|
|
6
|
+
} from '@agimon-ai/doompi-web-components';
|
|
7
|
+
import { useEffect, useRef, useState, type PointerEvent } from 'react';
|
|
8
|
+
import { AuthorVideoControls } from './AuthorVideoControls.tsx';
|
|
9
|
+
import { loadAuthorMedia } from '../api/authorMedia.ts';
|
|
10
|
+
import { registerAuthorGridResolver } from '../lib/authorGrid.ts';
|
|
11
|
+
import { normalizedAuthorRectangle } from '../lib/authorRegions.ts';
|
|
12
|
+
import type { AuthorDisplayedRegion, AuthorNativeAnchor, AuthorToolMode } from '../lib/authorViewportTypes.ts';
|
|
13
|
+
import {
|
|
14
|
+
authorSessionWorkspace,
|
|
15
|
+
setAuthorRegionCandidate,
|
|
16
|
+
setAuthorToolMode,
|
|
17
|
+
type AuthorSessionWorkspace,
|
|
18
|
+
type AuthorWorkspaceDocument,
|
|
19
|
+
} from '../stores/authorWorkspaceStore.ts';
|
|
20
|
+
|
|
21
|
+
/** Marks source media. Native playback and page controls remain usable in select mode. */
|
|
22
|
+
export function AuthorMediaView({
|
|
23
|
+
sessionId,
|
|
24
|
+
document: source,
|
|
25
|
+
activeTool,
|
|
26
|
+
displayedRegions,
|
|
27
|
+
pendingCandidate = false,
|
|
28
|
+
seekRequest,
|
|
29
|
+
}: {
|
|
30
|
+
sessionId: string;
|
|
31
|
+
document: AuthorWorkspaceDocument;
|
|
32
|
+
activeTool: AuthorToolMode;
|
|
33
|
+
displayedRegions: readonly AuthorDisplayedRegion[];
|
|
34
|
+
pendingCandidate?: boolean;
|
|
35
|
+
seekRequest?: AuthorSessionWorkspace['videoSeekRequest'];
|
|
36
|
+
}) {
|
|
37
|
+
const host = useRef<HTMLDivElement>(null);
|
|
38
|
+
const image = useRef<HTMLImageElement>(null);
|
|
39
|
+
const video = useRef<MediaPreviewController>(null);
|
|
40
|
+
const pdf = useRef<PdfPreviewController>(null);
|
|
41
|
+
const start = useRef<
|
|
42
|
+
{ x: number; y: number; element: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement } | undefined
|
|
43
|
+
>(undefined);
|
|
44
|
+
const [playback, setPlayback] = useState<MediaPlaybackState>({ playing: false, currentTime: 0, duration: 0 });
|
|
45
|
+
const [frameReady, setFrameReady] = useState(false);
|
|
46
|
+
const [selection, setSelection] = useState<{
|
|
47
|
+
path: string;
|
|
48
|
+
rect: NonNullable<ReturnType<typeof normalizedAuthorRectangle>>;
|
|
49
|
+
}>();
|
|
50
|
+
const [error, setError] = useState<string>();
|
|
51
|
+
const [media, setMedia] = useState<{ source: string; url: string }>();
|
|
52
|
+
const mediaUrl = media?.source === source.mediaUrl ? media?.url : undefined;
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (source.mediaUrl === undefined) return;
|
|
55
|
+
const input = source.mediaUrl;
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
let dispose: (() => void) | undefined;
|
|
58
|
+
setError(undefined);
|
|
59
|
+
void loadAuthorMedia(input, controller.signal)
|
|
60
|
+
.then((asset) => {
|
|
61
|
+
if (controller.signal.aborted) {
|
|
62
|
+
asset.dispose();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
dispose = () => asset.dispose();
|
|
66
|
+
setMedia({ source: input, url: asset.url });
|
|
67
|
+
})
|
|
68
|
+
.catch((cause: unknown) => {
|
|
69
|
+
if (!controller.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause));
|
|
70
|
+
});
|
|
71
|
+
return () => {
|
|
72
|
+
controller.abort();
|
|
73
|
+
dispose?.();
|
|
74
|
+
};
|
|
75
|
+
}, [source.mediaUrl]);
|
|
76
|
+
const seek = (seconds: number) => {
|
|
77
|
+
if (authorSessionWorkspace(sessionId).candidate || !Number.isFinite(seconds)) return;
|
|
78
|
+
const player = video.current;
|
|
79
|
+
if (!player) return;
|
|
80
|
+
player.pause();
|
|
81
|
+
setAuthorToolMode(sessionId, 'select');
|
|
82
|
+
setSelection(undefined);
|
|
83
|
+
if (player.getState().currentTime === seconds) return;
|
|
84
|
+
setFrameReady(false);
|
|
85
|
+
player.seek(seconds);
|
|
86
|
+
};
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (
|
|
89
|
+
seekRequest?.path === source.path &&
|
|
90
|
+
seekRequest.generation === authorSessionWorkspace(sessionId).focusedDocument?.generation
|
|
91
|
+
) {
|
|
92
|
+
seek(seekRequest.timeSeconds);
|
|
93
|
+
}
|
|
94
|
+
}, [seekRequest]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!pendingCandidate && start.current === undefined) setSelection(undefined);
|
|
97
|
+
}, [pendingCandidate]);
|
|
98
|
+
useEffect(
|
|
99
|
+
() =>
|
|
100
|
+
registerAuthorGridResolver(sessionId, (cell, geometry) => {
|
|
101
|
+
const target = image.current;
|
|
102
|
+
if (source.kind !== 'image' || target === null || target.naturalWidth === 0 || target.naturalHeight === 0)
|
|
103
|
+
return undefined;
|
|
104
|
+
const { originX = 0, originY = 0, width, height } = geometry.viewport;
|
|
105
|
+
const cellBounds = {
|
|
106
|
+
left: originX + cell.rect.x * width,
|
|
107
|
+
top: originY + cell.rect.y * height,
|
|
108
|
+
right: originX + (cell.rect.x + cell.rect.width) * width,
|
|
109
|
+
bottom: originY + (cell.rect.y + cell.rect.height) * height,
|
|
110
|
+
};
|
|
111
|
+
const bounds = target.getBoundingClientRect();
|
|
112
|
+
const left = Math.max(bounds.left, cellBounds.left);
|
|
113
|
+
const top = Math.max(bounds.top, cellBounds.top);
|
|
114
|
+
const right = Math.min(bounds.right, cellBounds.right);
|
|
115
|
+
const bottom = Math.min(bounds.bottom, cellBounds.bottom);
|
|
116
|
+
if (right <= left || bottom <= top || bounds.width <= 0 || bounds.height <= 0) return undefined;
|
|
117
|
+
return {
|
|
118
|
+
anchor: {
|
|
119
|
+
kind: 'image-rect',
|
|
120
|
+
rect: {
|
|
121
|
+
x: (left - bounds.left) / bounds.width,
|
|
122
|
+
y: (top - bounds.top) / bounds.height,
|
|
123
|
+
width: (right - left) / bounds.width,
|
|
124
|
+
height: (bottom - top) / bounds.height,
|
|
125
|
+
},
|
|
126
|
+
naturalWidth: target.naturalWidth,
|
|
127
|
+
naturalHeight: target.naturalHeight,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}),
|
|
131
|
+
[sessionId, source.kind, source.path, source.version],
|
|
132
|
+
);
|
|
133
|
+
const mark = async (event: PointerEvent<HTMLDivElement>) => {
|
|
134
|
+
const drag = start.current;
|
|
135
|
+
start.current = undefined;
|
|
136
|
+
if (!drag) return;
|
|
137
|
+
const bounds = drag.element.getBoundingClientRect();
|
|
138
|
+
const rect = normalizedAuthorRectangle(bounds, {
|
|
139
|
+
left: drag.x,
|
|
140
|
+
top: drag.y,
|
|
141
|
+
right: event.clientX,
|
|
142
|
+
bottom: event.clientY,
|
|
143
|
+
});
|
|
144
|
+
if (!rect) return;
|
|
145
|
+
setSelection({ path: source.path, rect });
|
|
146
|
+
const focused = authorSessionWorkspace(sessionId).focusedDocument;
|
|
147
|
+
if (!focused || focused.path !== source.path) return;
|
|
148
|
+
let anchor: AuthorNativeAnchor;
|
|
149
|
+
let blob: Blob | null = null;
|
|
150
|
+
let thumbnailUrl: string | undefined;
|
|
151
|
+
try {
|
|
152
|
+
if (drag.element instanceof HTMLImageElement) {
|
|
153
|
+
const image = drag.element;
|
|
154
|
+
if (!image.naturalWidth || !image.naturalHeight) throw new Error('Image is not ready.');
|
|
155
|
+
anchor = { kind: 'image-rect', rect, naturalWidth: image.naturalWidth, naturalHeight: image.naturalHeight };
|
|
156
|
+
const canvas = document.createElement('canvas');
|
|
157
|
+
const scale = Math.min(1, 1600 / Math.max(image.naturalWidth, image.naturalHeight));
|
|
158
|
+
canvas.width = Math.max(1, Math.round(image.naturalWidth * scale));
|
|
159
|
+
canvas.height = Math.max(1, Math.round(image.naturalHeight * scale));
|
|
160
|
+
const context = canvas.getContext('2d');
|
|
161
|
+
if (!context) throw new Error('Canvas is unavailable.');
|
|
162
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
163
|
+
blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, 'image/png'));
|
|
164
|
+
} else if (source.kind === 'pdf') {
|
|
165
|
+
const page = pdf.current?.getState();
|
|
166
|
+
if (!page?.sourceWidth) throw new Error('PDF page is not ready.');
|
|
167
|
+
anchor = { kind: 'pdf-page-rect', page: page.page, rect };
|
|
168
|
+
blob = await pdf.current!.capturePage();
|
|
169
|
+
} else {
|
|
170
|
+
const frame = await video.current?.captureFrame();
|
|
171
|
+
if (!frame) throw new Error('Video frame is not ready.');
|
|
172
|
+
anchor = {
|
|
173
|
+
kind: 'video-time-rect',
|
|
174
|
+
rect,
|
|
175
|
+
timeSeconds: frame.timeSeconds,
|
|
176
|
+
intrinsicWidth: frame.width,
|
|
177
|
+
intrinsicHeight: frame.height,
|
|
178
|
+
};
|
|
179
|
+
blob = frame.blob;
|
|
180
|
+
}
|
|
181
|
+
if (!blob) throw new Error('Unable to capture selected media.');
|
|
182
|
+
if (authorSessionWorkspace(sessionId).focusedDocument?.generation !== focused.generation) return;
|
|
183
|
+
thumbnailUrl = URL.createObjectURL(blob);
|
|
184
|
+
setAuthorRegionCandidate(sessionId, {
|
|
185
|
+
documentPath: source.path,
|
|
186
|
+
revision: source.version,
|
|
187
|
+
sourceSha256: source.sourceSha256,
|
|
188
|
+
anchor,
|
|
189
|
+
viewport: { width: bounds.width, height: bounds.height },
|
|
190
|
+
thumbnailUrl,
|
|
191
|
+
createdAt: Date.now(),
|
|
192
|
+
});
|
|
193
|
+
setError(undefined);
|
|
194
|
+
} catch (reason) {
|
|
195
|
+
if (thumbnailUrl) URL.revokeObjectURL(thumbnailUrl);
|
|
196
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const selected = selection?.path === source.path ? selection.rect : undefined;
|
|
200
|
+
const selectionOverlay =
|
|
201
|
+
selected === undefined ? null : (
|
|
202
|
+
<div
|
|
203
|
+
data-testid="author-media-selection"
|
|
204
|
+
className="pointer-events-none absolute border-2 border-doom-red bg-doom-red/10"
|
|
205
|
+
style={{
|
|
206
|
+
left: `${selected.x * 100}%`,
|
|
207
|
+
top: `${selected.y * 100}%`,
|
|
208
|
+
width: `${selected.width * 100}%`,
|
|
209
|
+
height: `${selected.height * 100}%`,
|
|
210
|
+
}}
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
213
|
+
return (
|
|
214
|
+
<div
|
|
215
|
+
ref={host}
|
|
216
|
+
className="min-h-0 flex-1 overflow-auto p-4"
|
|
217
|
+
style={{
|
|
218
|
+
touchAction: activeTool === 'select' ? 'auto' : 'none',
|
|
219
|
+
cursor: activeTool === 'select' ? undefined : 'crosshair',
|
|
220
|
+
}}
|
|
221
|
+
onPointerMove={(event) => {
|
|
222
|
+
const drag = start.current;
|
|
223
|
+
if (drag === undefined) return;
|
|
224
|
+
const rect = normalizedAuthorRectangle(drag.element.getBoundingClientRect(), {
|
|
225
|
+
left: drag.x,
|
|
226
|
+
top: drag.y,
|
|
227
|
+
right: event.clientX,
|
|
228
|
+
bottom: event.clientY,
|
|
229
|
+
});
|
|
230
|
+
setSelection(rect === null ? undefined : { path: source.path, rect });
|
|
231
|
+
}}
|
|
232
|
+
onPointerDownCapture={(event) => {
|
|
233
|
+
if (activeTool === 'select' || event.button !== 0 || pendingCandidate) return;
|
|
234
|
+
const element = event.target;
|
|
235
|
+
if (
|
|
236
|
+
!(
|
|
237
|
+
element instanceof HTMLImageElement ||
|
|
238
|
+
element instanceof HTMLVideoElement ||
|
|
239
|
+
element instanceof HTMLCanvasElement
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
return;
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
setSelection(undefined);
|
|
245
|
+
if (element instanceof HTMLVideoElement) {
|
|
246
|
+
if (video.current?.isFrameReady?.() === false) {
|
|
247
|
+
setError('Wait for the selected frame to finish loading.');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
video.current?.pause();
|
|
251
|
+
}
|
|
252
|
+
start.current = { x: event.clientX, y: event.clientY, element };
|
|
253
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
254
|
+
}}
|
|
255
|
+
onPointerUp={(event) => void mark(event)}
|
|
256
|
+
onPointerCancel={() => {
|
|
257
|
+
start.current = undefined;
|
|
258
|
+
setSelection(undefined);
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
{mediaUrl === undefined ? (
|
|
262
|
+
<p className="text-doom-dim">{error === undefined ? 'Loading media...' : 'Media unavailable.'}</p>
|
|
263
|
+
) : source.kind === 'image' ? (
|
|
264
|
+
<div className="relative inline-block max-w-full">
|
|
265
|
+
<img ref={image} src={mediaUrl} alt={source.path} draggable={false} className="block max-w-full" />
|
|
266
|
+
{selectionOverlay}
|
|
267
|
+
{displayedRegions.flatMap(({ ordinal, region }) => {
|
|
268
|
+
if (region.anchor.kind !== 'image-rect') return [];
|
|
269
|
+
const { rect } = region.anchor;
|
|
270
|
+
return [
|
|
271
|
+
<div
|
|
272
|
+
key={region.id}
|
|
273
|
+
data-author-region={ordinal}
|
|
274
|
+
className="pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10"
|
|
275
|
+
style={{
|
|
276
|
+
left: `${String(rect.x * 100)}%`,
|
|
277
|
+
top: `${String(rect.y * 100)}%`,
|
|
278
|
+
width: `${String(rect.width * 100)}%`,
|
|
279
|
+
height: `${String(rect.height * 100)}%`,
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
<span className="absolute -left-2 -top-2 flex h-4 min-w-4 items-center justify-center rounded-full bg-doom-yellow px-1 text-[9px] font-bold text-doom-deep">
|
|
283
|
+
{ordinal}
|
|
284
|
+
</span>
|
|
285
|
+
</div>,
|
|
286
|
+
];
|
|
287
|
+
})}
|
|
288
|
+
</div>
|
|
289
|
+
) : (
|
|
290
|
+
<div className="relative w-fit max-w-full">
|
|
291
|
+
<MediaPreview
|
|
292
|
+
src={mediaUrl}
|
|
293
|
+
path={source.path}
|
|
294
|
+
kind={source.kind === 'pdf' ? 'pdf' : source.kind === 'video' ? 'video' : 'download'}
|
|
295
|
+
controllerRef={video}
|
|
296
|
+
pdfControllerRef={pdf}
|
|
297
|
+
data-testid="author-media"
|
|
298
|
+
videoControls={false}
|
|
299
|
+
onPlaybackStateChange={(state) => {
|
|
300
|
+
setPlayback(state);
|
|
301
|
+
setFrameReady(video.current?.isFrameReady() ?? false);
|
|
302
|
+
if (state.playing) setSelection(undefined);
|
|
303
|
+
}}
|
|
304
|
+
/>
|
|
305
|
+
{source.kind === 'video' ? selectionOverlay : null}
|
|
306
|
+
{source.kind === 'video'
|
|
307
|
+
? displayedRegions.map(({ ordinal, region }) => {
|
|
308
|
+
if (
|
|
309
|
+
region.anchor.kind !== 'video-time-rect' ||
|
|
310
|
+
playback.playing ||
|
|
311
|
+
Math.abs(region.anchor.timeSeconds - playback.currentTime) > 0.1
|
|
312
|
+
)
|
|
313
|
+
return null;
|
|
314
|
+
const { rect } = region.anchor;
|
|
315
|
+
return (
|
|
316
|
+
<div
|
|
317
|
+
key={region.id}
|
|
318
|
+
data-author-region={ordinal}
|
|
319
|
+
className="pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10"
|
|
320
|
+
style={{
|
|
321
|
+
left: `${rect.x * 100}%`,
|
|
322
|
+
top: `${rect.y * 100}%`,
|
|
323
|
+
width: `${rect.width * 100}%`,
|
|
324
|
+
height: `${rect.height * 100}%`,
|
|
325
|
+
}}
|
|
326
|
+
>
|
|
327
|
+
<span className="bg-doom-yellow text-doom-deep">{ordinal}</span>
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
})
|
|
331
|
+
: null}
|
|
332
|
+
</div>
|
|
333
|
+
)}
|
|
334
|
+
{source.kind === 'video' ? (
|
|
335
|
+
<AuthorVideoControls
|
|
336
|
+
playback={playback}
|
|
337
|
+
ready={frameReady}
|
|
338
|
+
locked={pendingCandidate}
|
|
339
|
+
marking={activeTool === 'mark'}
|
|
340
|
+
onSeek={seek}
|
|
341
|
+
onToggle={() => {
|
|
342
|
+
setAuthorToolMode(sessionId, 'select');
|
|
343
|
+
setSelection(undefined);
|
|
344
|
+
if (playback.playing) video.current?.pause();
|
|
345
|
+
else void video.current?.play().catch((reason: unknown) => setError(String(reason)));
|
|
346
|
+
}}
|
|
347
|
+
onAnnotate={() => {
|
|
348
|
+
video.current?.pause();
|
|
349
|
+
setAuthorToolMode(sessionId, 'mark');
|
|
350
|
+
}}
|
|
351
|
+
/>
|
|
352
|
+
) : null}
|
|
353
|
+
{error ? <output className="text-sm text-doom-red">{error}</output> : null}
|
|
354
|
+
</div>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Button } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import type { WebPluginSlotProps } from '@agimon-ai/doompi-web-contracts';
|
|
3
|
+
import { useStore } from '@tanstack/react-store';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import {
|
|
6
|
+
authorCaptureContext,
|
|
7
|
+
createAuthorCapturePacket,
|
|
8
|
+
multiRegionCaptureProvider,
|
|
9
|
+
} from '../stores/authorCapture.ts';
|
|
10
|
+
import { authorGrid } from '../lib/authorGrid.ts';
|
|
11
|
+
import { autonomousVoiceGridVisible } from './AuthorGridOverlay.tsx';
|
|
12
|
+
import { authorWorkspace } from '../stores/authorWorkspaceStore.ts';
|
|
13
|
+
import { AuthorToolPalette } from './AuthorToolPalette.tsx';
|
|
14
|
+
import { AuthorRegionDrafts } from './AuthorRegionDrafts.tsx';
|
|
15
|
+
import { AuthorRequestLog } from './AuthorRequestLog.tsx';
|
|
16
|
+
|
|
17
|
+
export function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses }: WebPluginSlotProps) {
|
|
18
|
+
const [captureStatus, setCaptureStatus] = useState<string>();
|
|
19
|
+
const [capturing, setCapturing] = useState(false);
|
|
20
|
+
const documents = useStore(authorWorkspace.store, (state) => {
|
|
21
|
+
if (sessionId === null) return [];
|
|
22
|
+
const prefix = `${sessionId}\n`;
|
|
23
|
+
return Object.entries(state.documents)
|
|
24
|
+
.filter(([key]) => key.startsWith(prefix))
|
|
25
|
+
.map(([, document]) => document);
|
|
26
|
+
});
|
|
27
|
+
const workspace = useStore(authorWorkspace.store, (state) =>
|
|
28
|
+
sessionId === null ? undefined : state.sessions[sessionId],
|
|
29
|
+
);
|
|
30
|
+
const grid = useStore(authorGrid.store, (state) => (sessionId === null ? undefined : state.sessions[sessionId]));
|
|
31
|
+
const focused = documents.find((document) => document.path === workspace?.focusedDocument?.path);
|
|
32
|
+
if (!activeMinorModes?.includes('author') || focused === undefined) return null;
|
|
33
|
+
return (
|
|
34
|
+
<section data-testid="author-panel" className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto p-3">
|
|
35
|
+
{sessionId !== null && workspace !== undefined && focused !== undefined ? (
|
|
36
|
+
<>
|
|
37
|
+
<h2 className="text-sm font-semibold text-doom-text">Annotations</h2>
|
|
38
|
+
<p className="text-sm leading-normal text-doom-dim sm:text-xs">
|
|
39
|
+
Select a region, add a comment, then submit here. Requests queue while the agent is working.
|
|
40
|
+
</p>
|
|
41
|
+
{focused.kind === 'video' ? (
|
|
42
|
+
<p className="text-sm text-doom-dim sm:text-xs">
|
|
43
|
+
Annotations reference video frames. They do not save changes to the source video.
|
|
44
|
+
</p>
|
|
45
|
+
) : null}
|
|
46
|
+
<AuthorToolPalette sessionId={sessionId} kind={focused.kind} activeTool={workspace.activeTool} />
|
|
47
|
+
{autonomousVoiceGridVisible(statuses) && grid !== undefined ? (
|
|
48
|
+
<div
|
|
49
|
+
data-testid="author-grid-snapshot"
|
|
50
|
+
className="rounded border border-doom-red/40 bg-doom-red/5 p-2 text-sm text-doom-dim"
|
|
51
|
+
>
|
|
52
|
+
<strong className="text-doom-red">VOICE GRID A1–H8</strong>
|
|
53
|
+
<p className="mt-1 truncate">token {grid.geometryToken}</p>
|
|
54
|
+
</div>
|
|
55
|
+
) : null}
|
|
56
|
+
<AuthorRegionDrafts key={focused.path} sessionId={sessionId} workspace={workspace} />
|
|
57
|
+
{workspace.regions.length > 0 ? (
|
|
58
|
+
<div className="space-y-1.5 border-b border-doom-border-soft pb-3">
|
|
59
|
+
<Button
|
|
60
|
+
className="min-h-11 min-w-11 w-full text-sm [@media(pointer:fine)]:min-h-8 sm:text-xs"
|
|
61
|
+
variant="outline"
|
|
62
|
+
data-testid="author-attach-capture"
|
|
63
|
+
disabled={capturing || workspace.candidate !== undefined || submitCapture === undefined}
|
|
64
|
+
onClick={async () => {
|
|
65
|
+
if (capturing || workspace.candidate || submitCapture === undefined) return;
|
|
66
|
+
setCapturing(true);
|
|
67
|
+
setCaptureStatus('Submitting annotations…');
|
|
68
|
+
try {
|
|
69
|
+
const packet = createAuthorCapturePacket(
|
|
70
|
+
crypto.randomUUID(),
|
|
71
|
+
Date.now(),
|
|
72
|
+
focused,
|
|
73
|
+
workspace.regions,
|
|
74
|
+
);
|
|
75
|
+
const image = await multiRegionCaptureProvider(workspace.regions).capture();
|
|
76
|
+
await submitCapture({ ...image, context: authorCaptureContext(packet) });
|
|
77
|
+
setCaptureStatus('Request submitted. You can keep annotating here.');
|
|
78
|
+
} catch (reason) {
|
|
79
|
+
setCaptureStatus(reason instanceof Error ? reason.message : String(reason));
|
|
80
|
+
} finally {
|
|
81
|
+
setCapturing(false);
|
|
82
|
+
}
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{capturing
|
|
86
|
+
? 'Submitting…'
|
|
87
|
+
: `Submit ${workspace.regions.length} annotation${workspace.regions.length === 1 ? '' : 's'}`}
|
|
88
|
+
</Button>
|
|
89
|
+
{workspace.candidate ? (
|
|
90
|
+
<p className="text-sm text-doom-dim sm:text-xs">
|
|
91
|
+
Add or discard the current selection before submitting.
|
|
92
|
+
</p>
|
|
93
|
+
) : null}
|
|
94
|
+
</div>
|
|
95
|
+
) : null}
|
|
96
|
+
{submitCapture === undefined ? (
|
|
97
|
+
<output className="text-xs text-doom-red">Submission unavailable. Reload to update the app.</output>
|
|
98
|
+
) : null}
|
|
99
|
+
{captureStatus ? <output className="block text-xs text-doom-dim">{captureStatus}</output> : null}
|
|
100
|
+
</>
|
|
101
|
+
) : null}
|
|
102
|
+
<AuthorRequestLog requests={workspace?.requests ?? []} />
|
|
103
|
+
</section>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Button } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import type { AuthorSessionWorkspace } from '../stores/authorWorkspaceStore.ts';
|
|
4
|
+
import {
|
|
5
|
+
commitAuthorRegion,
|
|
6
|
+
removeAuthorRegion,
|
|
7
|
+
seekAuthorVideo,
|
|
8
|
+
setAuthorRegionCandidate,
|
|
9
|
+
} from '../stores/authorWorkspaceStore.ts';
|
|
10
|
+
|
|
11
|
+
export function AuthorRegionDrafts({ sessionId, workspace }: { sessionId: string; workspace: AuthorSessionWorkspace }) {
|
|
12
|
+
const [comment, setComment] = useState('');
|
|
13
|
+
const [error, setError] = useState<string>();
|
|
14
|
+
const add = () => {
|
|
15
|
+
try {
|
|
16
|
+
commitAuthorRegion(sessionId, comment);
|
|
17
|
+
setComment('');
|
|
18
|
+
setError(undefined);
|
|
19
|
+
} catch (reason) {
|
|
20
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
if (!workspace.candidate && workspace.regions.length === 0) return null;
|
|
24
|
+
return (
|
|
25
|
+
<section className="space-y-2" data-testid="author-region-drafts">
|
|
26
|
+
{workspace.candidate ? (
|
|
27
|
+
<>
|
|
28
|
+
<h3 className="text-sm font-semibold text-doom-text">Current selection</h3>
|
|
29
|
+
<p className="text-sm text-doom-dim sm:text-xs">Add a comment to keep this selection as an unsent draft.</p>
|
|
30
|
+
{workspace.candidate.anchor.kind === 'video-time-rect' ? (
|
|
31
|
+
<p className="text-sm text-doom-dim sm:text-xs">
|
|
32
|
+
Frame at {workspace.candidate.anchor.timeSeconds.toFixed(3)}s
|
|
33
|
+
</p>
|
|
34
|
+
) : null}
|
|
35
|
+
<textarea
|
|
36
|
+
aria-label="Region comment"
|
|
37
|
+
value={comment}
|
|
38
|
+
onChange={(event) => setComment(event.target.value)}
|
|
39
|
+
placeholder="What should change here?"
|
|
40
|
+
rows={3}
|
|
41
|
+
className="min-h-11 w-full resize-y rounded border border-doom-border bg-doom-deep p-2 text-base leading-normal text-doom-text focus:border-doom-red focus:outline-none sm:text-xs"
|
|
42
|
+
/>
|
|
43
|
+
<Button
|
|
44
|
+
className="min-h-11 min-w-11 text-sm [@media(pointer:fine)]:min-h-8 sm:text-xs"
|
|
45
|
+
variant="outline"
|
|
46
|
+
onClick={add}
|
|
47
|
+
disabled={!workspace.candidate || !comment.trim() || workspace.regions.length >= 16}
|
|
48
|
+
>
|
|
49
|
+
add region
|
|
50
|
+
</Button>
|
|
51
|
+
<Button
|
|
52
|
+
variant="ghost"
|
|
53
|
+
className="min-h-11 min-w-11 text-sm [@media(pointer:fine)]:min-h-8 sm:text-xs"
|
|
54
|
+
onClick={() => {
|
|
55
|
+
setAuthorRegionCandidate(sessionId, undefined);
|
|
56
|
+
setComment('');
|
|
57
|
+
setError(undefined);
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
Discard selection
|
|
61
|
+
</Button>
|
|
62
|
+
</>
|
|
63
|
+
) : null}
|
|
64
|
+
{error ? <output className="block text-sm text-doom-red">{error}</output> : null}
|
|
65
|
+
{workspace.regions.length > 0 ? (
|
|
66
|
+
<h3 className="text-sm font-semibold text-doom-text">Unsent drafts ({workspace.regions.length})</h3>
|
|
67
|
+
) : null}
|
|
68
|
+
<ol className="space-y-2">
|
|
69
|
+
{workspace.regions.map((region, index) => (
|
|
70
|
+
<li
|
|
71
|
+
key={region.id}
|
|
72
|
+
className="rounded border border-doom-border bg-doom-panel p-2 text-sm leading-normal text-doom-text sm:text-xs"
|
|
73
|
+
>
|
|
74
|
+
<span>
|
|
75
|
+
({index + 1}) {region.comment}
|
|
76
|
+
</span>
|
|
77
|
+
{region.anchor.kind === 'video-time-rect' ? (
|
|
78
|
+
<Button
|
|
79
|
+
variant="outline"
|
|
80
|
+
className="min-h-11 min-w-11 text-sm [@media(pointer:fine)]:min-h-8 sm:text-xs"
|
|
81
|
+
disabled={workspace.candidate !== undefined}
|
|
82
|
+
onClick={() => {
|
|
83
|
+
if (region.anchor.kind === 'video-time-rect') seekAuthorVideo(sessionId, region.anchor.timeSeconds);
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
Go to {region.anchor.timeSeconds.toFixed(3)}s
|
|
87
|
+
</Button>
|
|
88
|
+
) : null}
|
|
89
|
+
<Button
|
|
90
|
+
className="min-h-11 min-w-11 text-sm [@media(pointer:fine)]:min-h-8 sm:text-xs"
|
|
91
|
+
variant="ghost"
|
|
92
|
+
aria-label={`Remove region ${index + 1}`}
|
|
93
|
+
onClick={() => removeAuthorRegion(sessionId, region.id)}
|
|
94
|
+
>
|
|
95
|
+
remove
|
|
96
|
+
</Button>
|
|
97
|
+
</li>
|
|
98
|
+
))}
|
|
99
|
+
</ol>
|
|
100
|
+
</section>
|
|
101
|
+
);
|
|
102
|
+
}
|