@agimon-ai/doompi-author 0.0.1-alpha.2 → 0.0.1-alpha.21
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/README.md +5 -1
- package/dist/apiContracts.cjs +316 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +311 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/authorCommand-B90ghX5J.mjs +226 -0
- package/dist/authorCommand-B90ghX5J.mjs.map +1 -0
- package/dist/authorCommand-Cd5dnLF7.cjs +315 -0
- package/dist/authorCommand-Cd5dnLF7.cjs.map +1 -0
- package/dist/authorFacade-B9-Re3Ga.mjs +75 -0
- package/dist/authorFacade-B9-Re3Ga.mjs.map +1 -0
- package/dist/authorFacade-BvCSzZ5K.cjs +104 -0
- package/dist/authorFacade-BvCSzZ5K.cjs.map +1 -0
- package/dist/authorFacade-RZ6EXlR5.d.mts +113 -0
- package/dist/authorFacade-RZ6EXlR5.d.mts.map +1 -0
- package/dist/authorFacade.cjs +26 -0
- package/dist/authorFacade.d.mts +3 -0
- package/dist/authorFacade.mjs +3 -0
- package/dist/extensions/pi.cjs +172 -1
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.mts +6 -2
- package/dist/extensions/pi.d.mts.map +1 -0
- package/dist/extensions/pi.mjs +167 -1
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/extensions/server.cjs +659 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +652 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +4231 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index-Btm_xNOb.d.mts +386 -0
- package/dist/index-Btm_xNOb.d.mts.map +1 -0
- package/dist/index.cjs +35 -1
- package/dist/index.d.mts +2 -21
- package/dist/index.mjs +4 -1
- package/dist/monitor-7T5YF6px.mjs +70 -0
- package/dist/monitor-7T5YF6px.mjs.map +1 -0
- package/dist/monitor-BxY8s_AX.cjs +81 -0
- package/dist/monitor-BxY8s_AX.cjs.map +1 -0
- package/dist/structuredDocuments-Bg6DIqu5.cjs +960 -0
- package/dist/structuredDocuments-Bg6DIqu5.cjs.map +1 -0
- package/dist/structuredDocuments-CzMYD0Bq.mjs +810 -0
- package/dist/structuredDocuments-CzMYD0Bq.mjs.map +1 -0
- package/package.json +48 -38
- package/src/types/authorPreview.ts +76 -0
- package/src/types/webAuthor.ts +1 -1
- package/dist/_virtual/_rolldown/runtime.cjs +0 -1
- package/dist/adapters/authorApi.cjs +0 -2
- package/dist/adapters/authorApi.cjs.map +0 -1
- package/dist/adapters/authorBridgeApi.cjs +0 -2
- package/dist/adapters/authorBridgeApi.cjs.map +0 -1
- package/dist/adapters/authorDocumentApi.cjs +0 -2
- package/dist/adapters/authorDocumentApi.cjs.map +0 -1
- package/dist/adapters/pi/authorBridgeClient.cjs +0 -2
- package/dist/adapters/pi/authorBridgeClient.cjs.map +0 -1
- package/dist/adapters/pi/authorTools.cjs +0 -2
- package/dist/adapters/pi/authorTools.cjs.map +0 -1
- package/dist/adapters/pi/extension.cjs +0 -2
- package/dist/adapters/pi/extension.cjs.map +0 -1
- package/dist/adapters/structuredDocuments/common.cjs +0 -2
- package/dist/adapters/structuredDocuments/common.cjs.map +0 -1
- package/dist/adapters/structuredDocuments/csv.cjs +0 -7
- package/dist/adapters/structuredDocuments/csv.cjs.map +0 -1
- package/dist/adapters/structuredDocuments/index.cjs +0 -2
- package/dist/adapters/structuredDocuments/index.cjs.map +0 -1
- package/dist/adapters/structuredDocuments/markdownSlides.cjs +0 -6
- package/dist/adapters/structuredDocuments/markdownSlides.cjs.map +0 -1
- package/dist/adapters/structuredDocuments/ooxmlArchive.cjs +0 -2
- package/dist/adapters/structuredDocuments/ooxmlArchive.cjs.map +0 -1
- package/dist/adapters/structuredDocuments/pptx.cjs +0 -2
- package/dist/adapters/structuredDocuments/pptx.cjs.map +0 -1
- package/dist/adapters/structuredDocuments/xlsx.cjs +0 -2
- package/dist/adapters/structuredDocuments/xlsx.cjs.map +0 -1
- package/dist/adapters/structuredDocuments/xmlText.cjs +0 -2
- package/dist/adapters/structuredDocuments/xmlText.cjs.map +0 -1
- package/dist/adapters/webAuthorChannel.cjs +0 -2
- package/dist/adapters/webAuthorChannel.cjs.map +0 -1
- package/dist/commands/doomAuthorCommand.cjs +0 -2
- package/dist/commands/doomAuthorCommand.cjs.map +0 -1
- package/dist/container/index.cjs +0 -2
- package/dist/container/index.cjs.map +0 -1
- package/dist/extensions/pi.d.cts +0 -2
- package/dist/index.d.cts +0 -21
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.cts +0 -6
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.cts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.mts +0 -6
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/engine/helpers/union.d.mts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.cts +0 -7
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.cts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.mts +0 -7
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/_optional.d.mts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.cts +0 -12
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.cts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.mts +0 -12
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/object.d.mts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.cts +0 -12
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.cts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.mts +0 -12
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/properties.d.mts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.cts +0 -9
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.cts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.mts +0 -9
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/record.d.mts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.cts +0 -4
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.cts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.mts +0 -4
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/schema.d.mts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.cts +0 -9
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.cts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.mts +0 -9
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/string.d.mts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.cts +0 -8
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.cts.map +0 -1
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.mts +0 -8
- package/dist/node_modules/.pnpm/typebox@1.3.25/node_modules/typebox/build/type/types/unknown.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.cts +0 -15
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.mts +0 -15
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/authorApi.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.cts +0 -6
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.mts +0 -6
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/authorBridgeApi.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.cts +0 -11
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.mts +0 -11
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/authorDocumentApi.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.cts +0 -18
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.mts +0 -18
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorBridgeClient.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.cts +0 -9
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.mts +0 -9
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/pi/authorTools.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.cts +0 -19
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.mts +0 -19
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/pi/extension.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/common.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/common.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.cts +0 -7
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.mts +0 -7
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.mjs +0 -7
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/csv.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.cts +0 -13
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.mts +0 -13
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/index.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.cts +0 -7
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.mts +0 -7
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.mjs +0 -6
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/markdownSlides.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.cts +0 -13
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.mts +0 -13
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/ooxmlArchive.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.cts +0 -7
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.mts +0 -7
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/pptx.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.cts +0 -7
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.mts +0 -7
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xlsx.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xmlText.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/structuredDocuments/xmlText.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.cts +0 -6
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.mts +0 -6
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/adapters/webAuthorChannel.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/commands/doomAuthorCommand.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/commands/doomAuthorCommand.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/container/index.d.cts +0 -5
- package/dist/packages/minor/doompi-author/src/container/index.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/container/index.d.mts +0 -5
- package/dist/packages/minor/doompi-author/src/container/index.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/container/index.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/container/index.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.cts +0 -16
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.mts +0 -16
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/schemas/authorTools.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.cts +0 -31
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.mts +0 -31
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/services/authorBridgeState.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.cts +0 -10
- package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.mts +0 -10
- package/dist/packages/minor/doompi-author/src/services/authorCatalog.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/authorMode.d.cts +0 -19
- package/dist/packages/minor/doompi-author/src/services/authorMode.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/authorMode.d.mts +0 -19
- package/dist/packages/minor/doompi-author/src/services/authorMode.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/authorMode.mjs +0 -3
- package/dist/packages/minor/doompi-author/src/services/authorMode.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/extensionService.d.cts +0 -7
- package/dist/packages/minor/doompi-author/src/services/extensionService.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/extensionService.d.mts +0 -7
- package/dist/packages/minor/doompi-author/src/services/extensionService.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/services/extensionService.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/services/extensionService.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/author.d.cts +0 -39
- package/dist/packages/minor/doompi-author/src/types/author.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/author.d.mts +0 -39
- package/dist/packages/minor/doompi-author/src/types/author.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/author.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/types/author.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/authorApi.d.cts +0 -23
- package/dist/packages/minor/doompi-author/src/types/authorApi.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/authorApi.d.mts +0 -23
- package/dist/packages/minor/doompi-author/src/types/authorApi.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/authorApi.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/types/authorApi.mjs.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/extension.d.cts +0 -16
- package/dist/packages/minor/doompi-author/src/types/extension.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/extension.d.mts +0 -16
- package/dist/packages/minor/doompi-author/src/types/extension.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.cts +0 -60
- package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.mts +0 -60
- package/dist/packages/minor/doompi-author/src/types/structuredDocuments.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/webAuthor.d.cts +0 -67
- package/dist/packages/minor/doompi-author/src/types/webAuthor.d.cts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/webAuthor.d.mts +0 -67
- package/dist/packages/minor/doompi-author/src/types/webAuthor.d.mts.map +0 -1
- package/dist/packages/minor/doompi-author/src/types/webAuthor.mjs +0 -2
- package/dist/packages/minor/doompi-author/src/types/webAuthor.mjs.map +0 -1
- package/dist/schemas/authorTools.cjs +0 -2
- package/dist/schemas/authorTools.cjs.map +0 -1
- package/dist/services/authorBridgeState.cjs +0 -2
- package/dist/services/authorBridgeState.cjs.map +0 -1
- package/dist/services/authorMode.cjs +0 -3
- package/dist/services/authorMode.cjs.map +0 -1
- package/dist/services/extensionService.cjs +0 -2
- package/dist/services/extensionService.cjs.map +0 -1
- package/dist/sessionApi.cjs +0 -1
- package/dist/sessionApi.d.cts +0 -7
- package/dist/sessionApi.d.mts +0 -7
- package/dist/sessionApi.mjs +0 -1
- package/dist/types/author.cjs +0 -2
- package/dist/types/author.cjs.map +0 -1
- package/dist/types/authorApi.cjs +0 -2
- package/dist/types/authorApi.cjs.map +0 -1
- package/dist/types/webAuthor.cjs +0 -2
- package/dist/types/webAuthor.cjs.map +0 -1
- package/dist/webHub.cjs +0 -1
- package/dist/webHub.d.cts +0 -2
- package/dist/webHub.d.mts +0 -2
- package/dist/webHub.mjs +0 -1
- package/src/exports/webClient.ts +0 -76
- package/src/web/api/authorBroker.ts +0 -14
- package/src/web/api/authorBrowserBridge.ts +0 -225
- package/src/web/api/authorFiles.ts +0 -219
- package/src/web/api/authorMedia.ts +0 -19
- package/src/web/api/authorRuntime.ts +0 -164
- package/src/web/components/AuthorDocumentPanel.tsx +0 -232
- package/src/web/components/AuthorGridOverlay.tsx +0 -81
- package/src/web/components/AuthorMediaPreview.tsx +0 -41
- package/src/web/components/AuthorMediaView.tsx +0 -356
- package/src/web/components/AuthorPanel.tsx +0 -105
- package/src/web/components/AuthorRegionDrafts.tsx +0 -102
- package/src/web/components/AuthorRequestLog.tsx +0 -179
- package/src/web/components/AuthorStructuredView.tsx +0 -121
- package/src/web/components/AuthorTextView.tsx +0 -97
- package/src/web/components/AuthorToolPalette.tsx +0 -103
- package/src/web/components/AuthorVideoControls.tsx +0 -111
- package/src/web/components/DescribeAuthorToolsToolCard.tsx +0 -50
- package/src/web/components/OpenAuthoringFileToolCard.tsx +0 -66
- package/src/web/components/UseAuthorToolsToolCard.tsx +0 -50
- package/src/web/index.ts +0 -68
- package/src/web/lib/authorGrid.ts +0 -124
- package/src/web/lib/authorRegions.ts +0 -30
- package/src/web/lib/authorViewportTypes.ts +0 -202
- package/src/web/lib/describeAuthorToolsToolRender.ts +0 -20
- package/src/web/lib/useAuthorToolsToolRender.ts +0 -20
- package/src/web/stores/authorCapture.ts +0 -322
- package/src/web/stores/authorGridTools.ts +0 -316
- package/src/web/stores/authorProfiles.ts +0 -130
- package/src/web/stores/authorRequestLifecycle.ts +0 -82
- package/src/web/stores/authorStore.ts +0 -60
- package/src/web/stores/authorWorkspaceStore.ts +0 -525
- package/src/web/tsconfig.json +0 -22
|
@@ -0,0 +1,4231 @@
|
|
|
1
|
+
import { apiResponse, createApiClient, defineApiRoutes, defineChannelFile, defineDockFace, defineFileLinks, defineGlobalStore, defineLeaderBinding, defineMinorModeFile, defineSessionStore, defineSlotFile, defineToolRenderer, defineWebLifecycle, defineWebPlugin, sessionApiAddress, toolResultTextLines } from "@agimon-ai/doompi-core/web";
|
|
2
|
+
import { Store } from "@tanstack/store";
|
|
3
|
+
import { Button, CodeEditor, Markdown, MediaPreview, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, MessageLines, collapseLines, toolTone } from "@agimon-ai/doompi-web-components";
|
|
4
|
+
import { useStore } from "@tanstack/react-store";
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { sealedTransport } from "@agimon-ai/doompi-web-security/browser";
|
|
8
|
+
//#region src/types/webAuthor.ts
|
|
9
|
+
const authorChannelType = "author_webmcp";
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorRuntime.ts
|
|
12
|
+
const AUTHOR_RUNTIME_BINDING_IDS = {
|
|
13
|
+
text: "author.viewport.text",
|
|
14
|
+
media: "author.viewport.media"
|
|
15
|
+
};
|
|
16
|
+
function assertProfile(profile) {
|
|
17
|
+
if (profile.id.trim() === "") throw new Error("Author profile id must not be empty");
|
|
18
|
+
if (profile.tools.length > 15) throw new Error("Author profiles may expose at most 15 tools");
|
|
19
|
+
const names = /* @__PURE__ */ new Set();
|
|
20
|
+
for (const tool of profile.tools) {
|
|
21
|
+
if (!/^[a-z][a-z0-9_]{0,29}$/.test(tool.name)) throw new Error(`Invalid Author tool name: ${tool.name}`);
|
|
22
|
+
if (names.has(tool.name)) throw new Error(`Duplicate Author tool name: ${tool.name}`);
|
|
23
|
+
names.add(tool.name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function descriptorMatches(actual, expected) {
|
|
27
|
+
return actual.name === expected.name && actual.description === expected.description && JSON.stringify(actual.inputSchema) === JSON.stringify(expected.inputSchema);
|
|
28
|
+
}
|
|
29
|
+
function staleError() {
|
|
30
|
+
return /* @__PURE__ */ new Error("Author viewport catalog is stale");
|
|
31
|
+
}
|
|
32
|
+
function invocationSignal(catalogSignal, requestSignal) {
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const abortCatalog = () => controller.abort(catalogSignal.reason);
|
|
35
|
+
const abortRequest = () => controller.abort(requestSignal.reason);
|
|
36
|
+
if (catalogSignal.aborted) abortCatalog();
|
|
37
|
+
else if (requestSignal.aborted) abortRequest();
|
|
38
|
+
else {
|
|
39
|
+
catalogSignal.addEventListener("abort", abortCatalog, { once: true });
|
|
40
|
+
requestSignal.addEventListener("abort", abortRequest, { once: true });
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
signal: controller.signal,
|
|
44
|
+
release: () => {
|
|
45
|
+
catalogSignal.removeEventListener("abort", abortCatalog);
|
|
46
|
+
requestSignal.removeEventListener("abort", abortRequest);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Owns page-lifetime WebMCP registrations. Profiles are replaced as one abortable catalog. */
|
|
51
|
+
var AuthorRuntime = class {
|
|
52
|
+
#runtime;
|
|
53
|
+
#context;
|
|
54
|
+
#active;
|
|
55
|
+
#pending;
|
|
56
|
+
#generation = 0;
|
|
57
|
+
#disposed = false;
|
|
58
|
+
constructor(runtime) {
|
|
59
|
+
this.#runtime = runtime;
|
|
60
|
+
}
|
|
61
|
+
async replaceProfiles(profiles) {
|
|
62
|
+
if (this.#disposed) throw new Error("Author runtime is disposed");
|
|
63
|
+
for (const profile of profiles) assertProfile(profile);
|
|
64
|
+
const profileMap = new Map(profiles.map((profile) => [profile.id, profile]));
|
|
65
|
+
const tools = profiles.flatMap((profile) => profile.tools);
|
|
66
|
+
if (new Set(tools.map((tool) => tool.name)).size !== tools.length) throw new Error("Author tool names must be unique across trusted profiles");
|
|
67
|
+
const context = this.#context ?? (await this.#runtime.acquireModelContext?.())?.modelContext;
|
|
68
|
+
if (context === void 0) throw new Error("Model Context is unavailable");
|
|
69
|
+
this.#context = context;
|
|
70
|
+
const generation = ++this.#generation;
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const candidate = {
|
|
73
|
+
generation,
|
|
74
|
+
controller,
|
|
75
|
+
profiles: profileMap
|
|
76
|
+
};
|
|
77
|
+
const inactive = staleError();
|
|
78
|
+
this.#pending?.controller.abort(inactive);
|
|
79
|
+
this.#active?.controller.abort(inactive);
|
|
80
|
+
this.#pending = candidate;
|
|
81
|
+
this.#active = void 0;
|
|
82
|
+
const assertCurrent = () => {
|
|
83
|
+
if (controller.signal.aborted || this.#disposed || this.#active?.generation !== generation) throw staleError();
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
await Promise.all(tools.map(async (tool) => context.registerTool({
|
|
87
|
+
name: tool.name,
|
|
88
|
+
description: tool.description,
|
|
89
|
+
inputSchema: tool.inputSchema,
|
|
90
|
+
execute: async (input, options) => {
|
|
91
|
+
assertCurrent();
|
|
92
|
+
const parsed = JSON.parse(input);
|
|
93
|
+
assertCurrent();
|
|
94
|
+
const invocation = invocationSignal(controller.signal, options.signal);
|
|
95
|
+
try {
|
|
96
|
+
const result = await tool.execute(parsed, invocation.signal);
|
|
97
|
+
assertCurrent();
|
|
98
|
+
return result;
|
|
99
|
+
} finally {
|
|
100
|
+
invocation.release();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}, { signal: controller.signal })));
|
|
104
|
+
const registered = await context.getTools();
|
|
105
|
+
if (!tools.every((tool) => registered.some((descriptor) => descriptorMatches(descriptor, tool)))) throw new Error("Model Context did not confirm the Author catalog");
|
|
106
|
+
if (this.#disposed || generation !== this.#generation || this.#pending !== candidate) throw staleError();
|
|
107
|
+
this.#pending = void 0;
|
|
108
|
+
this.#active = candidate;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
controller.abort(error);
|
|
111
|
+
if (this.#pending === candidate) this.#pending = void 0;
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
return () => {
|
|
115
|
+
if (this.#active?.generation !== generation) return;
|
|
116
|
+
controller.abort(staleError());
|
|
117
|
+
this.#active = void 0;
|
|
118
|
+
this.#generation += 1;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async execute(name, input, signal) {
|
|
122
|
+
const context = this.#context;
|
|
123
|
+
if (context === void 0 || this.#active === void 0) throw new Error("Author catalog is not registered");
|
|
124
|
+
return context.executeTool(name, JSON.stringify(input), { signal });
|
|
125
|
+
}
|
|
126
|
+
dispose() {
|
|
127
|
+
if (this.#disposed) return;
|
|
128
|
+
this.#disposed = true;
|
|
129
|
+
this.#generation += 1;
|
|
130
|
+
this.#pending?.controller.abort(/* @__PURE__ */ new Error("Author runtime disposed"));
|
|
131
|
+
this.#active?.controller.abort(/* @__PURE__ */ new Error("Author runtime disposed"));
|
|
132
|
+
this.#pending = void 0;
|
|
133
|
+
this.#active = void 0;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorBrowserBridge.ts
|
|
138
|
+
var AuthorBrowserBridge = class {
|
|
139
|
+
#host;
|
|
140
|
+
#runtime;
|
|
141
|
+
#pending = /* @__PURE__ */ new Map();
|
|
142
|
+
#active;
|
|
143
|
+
#generation = 0;
|
|
144
|
+
#disposed = false;
|
|
145
|
+
#releaseConnected;
|
|
146
|
+
#leaseTimer;
|
|
147
|
+
constructor(host) {
|
|
148
|
+
this.#host = host;
|
|
149
|
+
this.#runtime = new AuthorRuntime(host);
|
|
150
|
+
this.#releaseConnected = host.onHubConnected(() => {
|
|
151
|
+
if (this.#active !== void 0) {
|
|
152
|
+
this.#active.ownerToken = void 0;
|
|
153
|
+
this.#active.catalogToken = void 0;
|
|
154
|
+
}
|
|
155
|
+
this.#register();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async focus(sessionId, profiles) {
|
|
159
|
+
if (this.#disposed) throw new Error("Author browser bridge is disposed");
|
|
160
|
+
const generation = ++this.#generation;
|
|
161
|
+
const release = await this.#runtime.replaceProfiles(profiles);
|
|
162
|
+
if (this.#disposed || generation !== this.#generation) {
|
|
163
|
+
release();
|
|
164
|
+
return () => void 0;
|
|
165
|
+
}
|
|
166
|
+
this.#clearActive();
|
|
167
|
+
this.#active = {
|
|
168
|
+
sessionId,
|
|
169
|
+
generation,
|
|
170
|
+
profiles,
|
|
171
|
+
release
|
|
172
|
+
};
|
|
173
|
+
this.#register();
|
|
174
|
+
return () => {
|
|
175
|
+
if (this.#active?.generation !== generation) return;
|
|
176
|
+
this.#clearActive();
|
|
177
|
+
this.#generation += 1;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
drop(sessionId) {
|
|
181
|
+
if (this.#active?.sessionId !== sessionId) return;
|
|
182
|
+
this.#clearActive();
|
|
183
|
+
this.#generation += 1;
|
|
184
|
+
}
|
|
185
|
+
apply(sessionId, message) {
|
|
186
|
+
const active = this.#active;
|
|
187
|
+
if (active === void 0 || active.sessionId !== sessionId || message.kind === "rejected") return;
|
|
188
|
+
if (message.generation !== active.generation || message.ownerToken !== active.ownerToken && active.ownerToken !== void 0) return;
|
|
189
|
+
if (message.kind === "accepted") {
|
|
190
|
+
active.ownerToken = message.ownerToken;
|
|
191
|
+
clearTimeout(this.#leaseTimer);
|
|
192
|
+
this.#leaseTimer = setTimeout(() => this.#register(), Math.max(1e3, Math.floor(message.leaseMs / 2)));
|
|
193
|
+
if (message.catalogToken === void 0) this.#sendCatalog(active);
|
|
194
|
+
else active.catalogToken = message.catalogToken;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (message.catalogToken !== active.catalogToken) return;
|
|
198
|
+
if (message.kind === "cancel") {
|
|
199
|
+
this.#pending.get(message.requestId)?.abort(/* @__PURE__ */ new Error("Author tool request cancelled"));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (message.kind !== "request" || this.#pending.has(message.requestId)) return;
|
|
203
|
+
const controller = new AbortController();
|
|
204
|
+
this.#pending.set(message.requestId, controller);
|
|
205
|
+
this.#runtime.execute(message.name, message.arguments, controller.signal).then((result) => {
|
|
206
|
+
if (!this.#current(active, message.requestId, controller)) return;
|
|
207
|
+
if (controller.signal.aborted) this.#sendCancelled(active, message.requestId);
|
|
208
|
+
else this.#send(active.sessionId, {
|
|
209
|
+
kind: "result",
|
|
210
|
+
generation: active.generation,
|
|
211
|
+
ownerToken: active.ownerToken,
|
|
212
|
+
catalogToken: active.catalogToken,
|
|
213
|
+
requestId: message.requestId,
|
|
214
|
+
result
|
|
215
|
+
});
|
|
216
|
+
}).catch((error) => {
|
|
217
|
+
if (!this.#current(active, message.requestId, controller)) return;
|
|
218
|
+
if (controller.signal.aborted) this.#sendCancelled(active, message.requestId);
|
|
219
|
+
else {
|
|
220
|
+
const messageText = error instanceof Error ? error.message : String(error);
|
|
221
|
+
const code = /^([A-Z][A-Z_]+):/u.exec(messageText)?.[1] ?? "AUTHOR_TOOL_ERROR";
|
|
222
|
+
this.#send(active.sessionId, {
|
|
223
|
+
kind: "result",
|
|
224
|
+
generation: active.generation,
|
|
225
|
+
ownerToken: active.ownerToken,
|
|
226
|
+
catalogToken: active.catalogToken,
|
|
227
|
+
requestId: message.requestId,
|
|
228
|
+
result: { error: {
|
|
229
|
+
code,
|
|
230
|
+
message: messageText
|
|
231
|
+
} }
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}).finally(() => {
|
|
235
|
+
if (this.#pending.get(message.requestId) === controller) this.#pending.delete(message.requestId);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
activeView(sessionId) {
|
|
239
|
+
const active = this.#active;
|
|
240
|
+
return active?.sessionId === sessionId && active.catalogToken !== void 0 ? {
|
|
241
|
+
activation: "active",
|
|
242
|
+
capabilityCount: active.profiles.reduce((count, profile) => count + profile.tools.length, 0)
|
|
243
|
+
} : {
|
|
244
|
+
activation: "inactive",
|
|
245
|
+
capabilityCount: 0
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
dispose() {
|
|
249
|
+
if (this.#disposed) return;
|
|
250
|
+
this.#disposed = true;
|
|
251
|
+
clearTimeout(this.#leaseTimer);
|
|
252
|
+
this.#releaseConnected();
|
|
253
|
+
this.#clearActive();
|
|
254
|
+
this.#runtime.dispose();
|
|
255
|
+
}
|
|
256
|
+
#register() {
|
|
257
|
+
const active = this.#active;
|
|
258
|
+
if (active !== void 0) this.#send(active.sessionId, {
|
|
259
|
+
kind: "register",
|
|
260
|
+
generation: active.generation
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
#sendCatalog(active) {
|
|
264
|
+
this.#send(active.sessionId, {
|
|
265
|
+
kind: "catalog",
|
|
266
|
+
generation: active.generation,
|
|
267
|
+
ownerToken: active.ownerToken,
|
|
268
|
+
tools: active.profiles.flatMap((profile) => profile.tools.map((tool) => ({
|
|
269
|
+
name: tool.name,
|
|
270
|
+
label: tool.label ?? tool.name,
|
|
271
|
+
description: tool.description,
|
|
272
|
+
inputSchema: tool.inputSchema
|
|
273
|
+
})))
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
#sendCancelled(active, requestId) {
|
|
277
|
+
this.#send(active.sessionId, {
|
|
278
|
+
kind: "cancelled",
|
|
279
|
+
generation: active.generation,
|
|
280
|
+
ownerToken: active.ownerToken,
|
|
281
|
+
catalogToken: active.catalogToken,
|
|
282
|
+
requestId
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
#send(sessionId, payload) {
|
|
286
|
+
const workspaceId = this.#host.mount?.scope === "session" ? this.#host.mount.workspaceId : void 0;
|
|
287
|
+
const mount = workspaceId ? {
|
|
288
|
+
scope: "workspace",
|
|
289
|
+
workspaceId
|
|
290
|
+
} : { scope: "global" };
|
|
291
|
+
this.#host.invokeServerMethod({
|
|
292
|
+
mount,
|
|
293
|
+
service: "author.bridge",
|
|
294
|
+
method: "send",
|
|
295
|
+
input: {
|
|
296
|
+
sessionId,
|
|
297
|
+
message: payload
|
|
298
|
+
}
|
|
299
|
+
}).catch(() => void 0);
|
|
300
|
+
}
|
|
301
|
+
#current(active, requestId, controller) {
|
|
302
|
+
return this.#active === active && this.#pending.get(requestId) === controller;
|
|
303
|
+
}
|
|
304
|
+
#clearActive() {
|
|
305
|
+
clearTimeout(this.#leaseTimer);
|
|
306
|
+
this.#leaseTimer = void 0;
|
|
307
|
+
const active = this.#active;
|
|
308
|
+
if (active !== void 0) {
|
|
309
|
+
this.#send(active.sessionId, {
|
|
310
|
+
kind: "release",
|
|
311
|
+
generation: active.generation
|
|
312
|
+
});
|
|
313
|
+
active.release();
|
|
314
|
+
}
|
|
315
|
+
this.#active = void 0;
|
|
316
|
+
for (const controller of this.#pending.values()) controller.abort(/* @__PURE__ */ new Error("Author viewport changed"));
|
|
317
|
+
this.#pending.clear();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
const authorBridgeRuntime = new Store(void 0);
|
|
321
|
+
function startAuthorBrowserBridge(runtime) {
|
|
322
|
+
authorBridgeRuntime.state?.dispose();
|
|
323
|
+
const next = new AuthorBrowserBridge(runtime);
|
|
324
|
+
authorBridgeRuntime.setState(() => next);
|
|
325
|
+
return () => {
|
|
326
|
+
if (authorBridgeRuntime.state !== next) return;
|
|
327
|
+
next.dispose();
|
|
328
|
+
authorBridgeRuntime.setState(() => void 0);
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async function focusAuthorViewport(sessionId, profiles) {
|
|
332
|
+
return await authorBridgeRuntime.state?.focus(sessionId, profiles) ?? (() => void 0);
|
|
333
|
+
}
|
|
334
|
+
function applyAuthorHubMessage(sessionId, message) {
|
|
335
|
+
authorBridgeRuntime.state?.apply(sessionId, message);
|
|
336
|
+
}
|
|
337
|
+
function dropAuthorViewportSession(sessionId) {
|
|
338
|
+
authorBridgeRuntime.state?.drop(sessionId);
|
|
339
|
+
}
|
|
340
|
+
function authorBridgeView(sessionId) {
|
|
341
|
+
return authorBridgeRuntime.state?.activeView(sessionId) ?? {
|
|
342
|
+
activation: "inactive",
|
|
343
|
+
capabilityCount: 0
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorGrid.ts
|
|
348
|
+
const AUTHOR_GRID_COLUMNS = "ABCDEFGH";
|
|
349
|
+
const nativeResolvers = /* @__PURE__ */ new Map();
|
|
350
|
+
const authorGrid = defineGlobalStore({ sessions: {} });
|
|
351
|
+
function parseAuthorGridCell(cell) {
|
|
352
|
+
const normalized = cell.trim().toUpperCase();
|
|
353
|
+
if (!/^[A-H][1-8]$/u.test(normalized)) throw new Error(`Invalid Author grid cell '${cell}'. Expected A1 through H8.`);
|
|
354
|
+
return {
|
|
355
|
+
cell: normalized,
|
|
356
|
+
column: AUTHOR_GRID_COLUMNS.indexOf(normalized[0]),
|
|
357
|
+
row: Number(normalized[1]) - 1
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function updateAuthorGridGeometry(sessionId, input) {
|
|
361
|
+
const previous = authorGrid.store.state.sessions[sessionId];
|
|
362
|
+
const geometry = {
|
|
363
|
+
...input,
|
|
364
|
+
geometryToken: crypto.randomUUID(),
|
|
365
|
+
snapshotId: previous?.documentPath === input.documentPath && previous.revision === input.revision && previous.sourceSha256 === input.sourceSha256 ? previous.snapshotId : crypto.randomUUID(),
|
|
366
|
+
updatedAt: Date.now()
|
|
367
|
+
};
|
|
368
|
+
authorGrid.update((state) => ({
|
|
369
|
+
...state,
|
|
370
|
+
sessions: {
|
|
371
|
+
...state.sessions,
|
|
372
|
+
[sessionId]: geometry
|
|
373
|
+
}
|
|
374
|
+
}));
|
|
375
|
+
return geometry;
|
|
376
|
+
}
|
|
377
|
+
function authorGridGeometry(sessionId) {
|
|
378
|
+
return authorGrid.store.state.sessions[sessionId];
|
|
379
|
+
}
|
|
380
|
+
function clearAuthorGridGeometry(sessionId) {
|
|
381
|
+
authorGrid.update((state) => {
|
|
382
|
+
if (state.sessions[sessionId] === void 0) return state;
|
|
383
|
+
const sessions = { ...state.sessions };
|
|
384
|
+
delete sessions[sessionId];
|
|
385
|
+
return {
|
|
386
|
+
...state,
|
|
387
|
+
sessions
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
function registerAuthorGridResolver(sessionId, resolver) {
|
|
392
|
+
nativeResolvers.set(sessionId, resolver);
|
|
393
|
+
return () => {
|
|
394
|
+
if (nativeResolvers.get(sessionId) === resolver) nativeResolvers.delete(sessionId);
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function resolveAuthorGridNativeAnchor(sessionId, cell) {
|
|
398
|
+
const geometry = authorGridGeometry(sessionId);
|
|
399
|
+
const resolved = geometry === void 0 ? void 0 : nativeResolvers.get(sessionId)?.(cell, geometry);
|
|
400
|
+
if (resolved === void 0) throw new Error("AMBIGUOUS_GRID: The grid cell does not resolve to one visible native document target.");
|
|
401
|
+
return resolved;
|
|
402
|
+
}
|
|
403
|
+
function resolveAuthorGridCell(sessionId, cell, expectedGeometryToken) {
|
|
404
|
+
const geometry = authorGridGeometry(sessionId);
|
|
405
|
+
if (geometry === void 0 || geometry.geometryToken !== expectedGeometryToken) throw new Error("STALE_GRID: The Author grid geometry changed. Describe the grid again.");
|
|
406
|
+
const parsed = parseAuthorGridCell(cell);
|
|
407
|
+
const width = 1 / 8;
|
|
408
|
+
const height = 1 / 8;
|
|
409
|
+
const rect = {
|
|
410
|
+
x: parsed.column * width,
|
|
411
|
+
y: parsed.row * height,
|
|
412
|
+
width,
|
|
413
|
+
height
|
|
414
|
+
};
|
|
415
|
+
return {
|
|
416
|
+
...parsed,
|
|
417
|
+
rect,
|
|
418
|
+
center: {
|
|
419
|
+
x: rect.x + width / 2,
|
|
420
|
+
y: rect.y + height / 2
|
|
421
|
+
},
|
|
422
|
+
evidence: {
|
|
423
|
+
cell: parsed.cell,
|
|
424
|
+
geometryToken: geometry.geometryToken,
|
|
425
|
+
snapshotId: geometry.snapshotId
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
const TERMINAL_REQUEST_STATUSES = /* @__PURE__ */ new Set([
|
|
430
|
+
"COMPLETE",
|
|
431
|
+
"FAILED",
|
|
432
|
+
"CANCELLED"
|
|
433
|
+
]);
|
|
434
|
+
const EMPTY_SESSION = {
|
|
435
|
+
generation: 0,
|
|
436
|
+
activeTool: "select",
|
|
437
|
+
annotationsByDocument: {},
|
|
438
|
+
annotations: [],
|
|
439
|
+
candidateText: "",
|
|
440
|
+
regions: [],
|
|
441
|
+
requests: []
|
|
442
|
+
};
|
|
443
|
+
function normalizeAuthorPath(path) {
|
|
444
|
+
const parts = [];
|
|
445
|
+
for (const part of path.trim().replaceAll("\\", "/").split("/")) {
|
|
446
|
+
if (part === "" || part === ".") continue;
|
|
447
|
+
if (part === "..") parts.pop();
|
|
448
|
+
else parts.push(part);
|
|
449
|
+
}
|
|
450
|
+
return parts.join("/");
|
|
451
|
+
}
|
|
452
|
+
function authorDocumentKey(sessionId, path) {
|
|
453
|
+
return `${sessionId}\n${normalizeAuthorPath(path)}`;
|
|
454
|
+
}
|
|
455
|
+
const authorWorkspace = defineGlobalStore({
|
|
456
|
+
documents: {},
|
|
457
|
+
sessions: {}
|
|
458
|
+
});
|
|
459
|
+
function authorDocument(sessionId, path) {
|
|
460
|
+
if (sessionId === null) return void 0;
|
|
461
|
+
return authorWorkspace.store.state.documents[authorDocumentKey(sessionId, path)];
|
|
462
|
+
}
|
|
463
|
+
function authorSessionWorkspace(sessionId) {
|
|
464
|
+
if (sessionId === null) return EMPTY_SESSION;
|
|
465
|
+
return authorWorkspace.store.state.sessions[sessionId] ?? EMPTY_SESSION;
|
|
466
|
+
}
|
|
467
|
+
function hydrateAuthorAnnotationCollections(records) {
|
|
468
|
+
authorWorkspace.update((state) => {
|
|
469
|
+
let sessions = state.sessions;
|
|
470
|
+
for (const record of records) {
|
|
471
|
+
const path = normalizeAuthorPath(record.documentPath);
|
|
472
|
+
if (path === "") continue;
|
|
473
|
+
const session = sessions[record.sessionId] ?? EMPTY_SESSION;
|
|
474
|
+
const previous = session.annotationsByDocument[path];
|
|
475
|
+
if (previous !== void 0 && previous.updatedAt >= record.collection.updatedAt) continue;
|
|
476
|
+
const document = state.documents[authorDocumentKey(record.sessionId, path)];
|
|
477
|
+
const stale = record.collection.stale || document !== void 0 && (document.version !== record.collection.revision || document.sourceSha256 !== record.collection.sourceSha256);
|
|
478
|
+
const collection = restoreCollectionEvidence({
|
|
479
|
+
...record.collection,
|
|
480
|
+
stale
|
|
481
|
+
});
|
|
482
|
+
const visible = session.focusedDocument?.path === path && !stale;
|
|
483
|
+
const annotations = visible ? collection.annotations : session.annotations;
|
|
484
|
+
sessions = {
|
|
485
|
+
...sessions,
|
|
486
|
+
[record.sessionId]: {
|
|
487
|
+
...session,
|
|
488
|
+
annotationsByDocument: {
|
|
489
|
+
...session.annotationsByDocument,
|
|
490
|
+
[path]: collection
|
|
491
|
+
},
|
|
492
|
+
...visible ? {
|
|
493
|
+
annotations,
|
|
494
|
+
regions: annotations,
|
|
495
|
+
candidate: collection.candidate,
|
|
496
|
+
candidateText: collection.candidateText
|
|
497
|
+
} : {}
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
return sessions === state.sessions ? state : {
|
|
502
|
+
...state,
|
|
503
|
+
sessions
|
|
504
|
+
};
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
function putAuthorDocument(sessionId, input) {
|
|
508
|
+
const path = normalizeAuthorPath(input.path);
|
|
509
|
+
if (path === "") throw new Error("Author document path must not be empty");
|
|
510
|
+
const key = authorDocumentKey(sessionId, path);
|
|
511
|
+
const staleThumbnails = [];
|
|
512
|
+
let result;
|
|
513
|
+
authorWorkspace.update((state) => {
|
|
514
|
+
const previous = state.documents[key];
|
|
515
|
+
const sourceChanged = previous?.sourceSha256 !== void 0 && input.sourceSha256 !== void 0 && previous.sourceSha256 !== input.sourceSha256;
|
|
516
|
+
const baselineVersion = sourceChanged ? previous.version + 1 : previous?.version ?? 0;
|
|
517
|
+
result = {
|
|
518
|
+
...input,
|
|
519
|
+
path,
|
|
520
|
+
annotations: sourceChanged ? [] : previous?.annotations ?? [],
|
|
521
|
+
revisions: sourceChanged ? [] : previous?.revisions ?? [],
|
|
522
|
+
saveRequest: previous?.saveRequest ?? 0,
|
|
523
|
+
version: baselineVersion,
|
|
524
|
+
savedVersion: sourceChanged ? baselineVersion : previous?.savedVersion ?? 0,
|
|
525
|
+
...sourceChanged || previous?.crop === void 0 ? {} : { crop: previous.crop }
|
|
526
|
+
};
|
|
527
|
+
const session = state.sessions[sessionId];
|
|
528
|
+
let sessions = state.sessions;
|
|
529
|
+
if (sourceChanged && session?.focusedDocument?.path === path) {
|
|
530
|
+
staleThumbnails.push(...session.candidate?.thumbnailUrl === void 0 ? [] : [session.candidate.thumbnailUrl], ...session.annotations.flatMap((annotation) => annotation.thumbnailUrl === void 0 ? [] : [annotation.thumbnailUrl]));
|
|
531
|
+
const now = Date.now();
|
|
532
|
+
const generation = session.generation + 1;
|
|
533
|
+
const staleCollection = staleAuthorCollection(session.annotationsByDocument[path], now);
|
|
534
|
+
sessions = {
|
|
535
|
+
...sessions,
|
|
536
|
+
[sessionId]: {
|
|
537
|
+
...session,
|
|
538
|
+
generation,
|
|
539
|
+
focusedDocument: {
|
|
540
|
+
path,
|
|
541
|
+
generation,
|
|
542
|
+
revision: baselineVersion,
|
|
543
|
+
sourceSha256: input.sourceSha256,
|
|
544
|
+
focusedAt: now
|
|
545
|
+
},
|
|
546
|
+
annotationsByDocument: {
|
|
547
|
+
...session.annotationsByDocument,
|
|
548
|
+
...staleCollection && { [path]: staleCollection }
|
|
549
|
+
},
|
|
550
|
+
annotations: [],
|
|
551
|
+
regions: [],
|
|
552
|
+
candidate: void 0,
|
|
553
|
+
candidateText: "",
|
|
554
|
+
requests: session.requests.map((request) => input.kind !== "story-preview" && request.documentPath === path && (request.status === "REQUESTED" || request.status === "CHANGING" || request.status === "CHANGED") ? {
|
|
555
|
+
...request,
|
|
556
|
+
status: "FAILED",
|
|
557
|
+
currentOperation: void 0,
|
|
558
|
+
error: "The document source changed before the request completed.",
|
|
559
|
+
updatedAt: now
|
|
560
|
+
} : request)
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
} else if (sourceChanged && session?.annotationsByDocument[path] !== void 0) {
|
|
564
|
+
const collection = session.annotationsByDocument[path];
|
|
565
|
+
if (collection.candidate?.thumbnailUrl !== void 0) staleThumbnails.push(collection.candidate.thumbnailUrl);
|
|
566
|
+
staleThumbnails.push(...collection.annotations.flatMap((annotation) => annotation.thumbnailUrl === void 0 ? [] : [annotation.thumbnailUrl]));
|
|
567
|
+
sessions = {
|
|
568
|
+
...sessions,
|
|
569
|
+
[sessionId]: {
|
|
570
|
+
...session,
|
|
571
|
+
annotationsByDocument: {
|
|
572
|
+
...session.annotationsByDocument,
|
|
573
|
+
[path]: staleAuthorCollection(collection, Date.now())
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
documents: {
|
|
580
|
+
...state.documents,
|
|
581
|
+
[key]: result
|
|
582
|
+
},
|
|
583
|
+
sessions
|
|
584
|
+
};
|
|
585
|
+
});
|
|
586
|
+
staleThumbnails.forEach(revokeThumbnail);
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
589
|
+
/** Claims focus and returns a generation token that makes cleanup race-safe. */
|
|
590
|
+
function focusAuthorDocument(sessionId, path, revision, sourceSha256) {
|
|
591
|
+
const normalized = normalizeAuthorPath(path);
|
|
592
|
+
let generation = 0;
|
|
593
|
+
authorWorkspace.update((state) => {
|
|
594
|
+
const session = state.sessions[sessionId] ?? EMPTY_SESSION;
|
|
595
|
+
generation = session.generation + 1;
|
|
596
|
+
const stored = session.annotationsByDocument[normalized];
|
|
597
|
+
const current = stored !== void 0 && stored.revision === revision && stored.sourceSha256 === sourceSha256 && !stored.stale ? stored : void 0;
|
|
598
|
+
const annotations = current?.annotations ?? [];
|
|
599
|
+
return {
|
|
600
|
+
...state,
|
|
601
|
+
sessions: {
|
|
602
|
+
...state.sessions,
|
|
603
|
+
[sessionId]: {
|
|
604
|
+
...session,
|
|
605
|
+
generation,
|
|
606
|
+
focusedDocument: {
|
|
607
|
+
path: normalized,
|
|
608
|
+
generation,
|
|
609
|
+
revision,
|
|
610
|
+
sourceSha256,
|
|
611
|
+
focusedAt: Date.now()
|
|
612
|
+
},
|
|
613
|
+
annotations,
|
|
614
|
+
regions: annotations,
|
|
615
|
+
candidate: current?.candidate,
|
|
616
|
+
candidateText: current?.candidateText ?? ""
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
});
|
|
621
|
+
return generation;
|
|
622
|
+
}
|
|
623
|
+
function syncAuthorDocumentFocus(sessionId, generation, revision, sourceSha256) {
|
|
624
|
+
updateSession(sessionId, (session) => {
|
|
625
|
+
const focused = session.focusedDocument;
|
|
626
|
+
if (focused === void 0 || focused.generation !== generation) return session;
|
|
627
|
+
if (focused.revision === revision && focused.sourceSha256 === sourceSha256) return session;
|
|
628
|
+
return {
|
|
629
|
+
...session,
|
|
630
|
+
focusedDocument: {
|
|
631
|
+
...focused,
|
|
632
|
+
revision,
|
|
633
|
+
sourceSha256
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
function releaseAuthorDocumentFocus(sessionId, generation) {
|
|
639
|
+
updateSession(sessionId, (session) => {
|
|
640
|
+
if (session.focusedDocument?.generation !== generation) return session;
|
|
641
|
+
return {
|
|
642
|
+
...session,
|
|
643
|
+
focusedDocument: void 0,
|
|
644
|
+
candidate: void 0,
|
|
645
|
+
candidateText: "",
|
|
646
|
+
annotations: [],
|
|
647
|
+
regions: []
|
|
648
|
+
};
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
function seekAuthorVideo(sessionId, timeSeconds) {
|
|
652
|
+
const workspace = authorSessionWorkspace(sessionId);
|
|
653
|
+
const focused = workspace.focusedDocument;
|
|
654
|
+
if (!focused || workspace.candidate || !Number.isFinite(timeSeconds) || timeSeconds < 0 || authorDocument(sessionId, focused.path)?.kind !== "video") return false;
|
|
655
|
+
updateSession(sessionId, (session) => ({
|
|
656
|
+
...session,
|
|
657
|
+
activeTool: "select",
|
|
658
|
+
videoSeekRequest: {
|
|
659
|
+
path: focused.path,
|
|
660
|
+
generation: focused.generation,
|
|
661
|
+
timeSeconds,
|
|
662
|
+
sequence: (session.videoSeekRequest?.sequence ?? 0) + 1
|
|
663
|
+
}
|
|
664
|
+
}));
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
function setAuthorToolMode(sessionId, activeTool) {
|
|
668
|
+
updateSession(sessionId, (session) => session.activeTool === activeTool ? session : {
|
|
669
|
+
...session,
|
|
670
|
+
activeTool
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
function setAuthorRegionCandidate(sessionId, candidate) {
|
|
674
|
+
let staleThumbnail;
|
|
675
|
+
updateSession(sessionId, (session) => {
|
|
676
|
+
const focused = session.focusedDocument;
|
|
677
|
+
if (candidate !== void 0) {
|
|
678
|
+
if (focused === void 0 || focused.path !== normalizeAuthorPath(candidate.documentPath)) throw new Error("The Author selection does not belong to the focused document.");
|
|
679
|
+
if (focused.revision !== candidate.revision || focused.sourceSha256 !== candidate.sourceSha256) throw new Error("The Author selection is stale for the focused document.");
|
|
680
|
+
}
|
|
681
|
+
if (focused === void 0) return session;
|
|
682
|
+
staleThumbnail = session.candidate?.thumbnailUrl;
|
|
683
|
+
const copied = candidate === void 0 ? void 0 : copyCandidate(candidate);
|
|
684
|
+
return withFocusedCollection({
|
|
685
|
+
...session,
|
|
686
|
+
candidate: copied
|
|
687
|
+
}, focused.path, { candidate: copied });
|
|
688
|
+
});
|
|
689
|
+
if (staleThumbnail !== void 0 && staleThumbnail !== candidate?.thumbnailUrl) revokeThumbnail(staleThumbnail);
|
|
690
|
+
}
|
|
691
|
+
function setAuthorCandidateText(sessionId, candidateText) {
|
|
692
|
+
if (new TextEncoder().encode(candidateText).byteLength > 2048) throw new Error("Author candidate text must not exceed 2 KiB.");
|
|
693
|
+
updateSession(sessionId, (session) => {
|
|
694
|
+
const path = session.focusedDocument?.path;
|
|
695
|
+
return path === void 0 ? session : withFocusedCollection({
|
|
696
|
+
...session,
|
|
697
|
+
candidateText
|
|
698
|
+
}, path, { candidateText });
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
function commitAuthorRegion(sessionId, comment) {
|
|
702
|
+
const id = crypto.randomUUID();
|
|
703
|
+
const session = authorSessionWorkspace(sessionId);
|
|
704
|
+
if (session.candidate === void 0) throw new Error("Select a document annotation before adding a comment.");
|
|
705
|
+
addAuthorRegion(sessionId, {
|
|
706
|
+
...session.candidate,
|
|
707
|
+
id,
|
|
708
|
+
comment,
|
|
709
|
+
version: 1
|
|
710
|
+
});
|
|
711
|
+
updateSession(sessionId, (current) => {
|
|
712
|
+
const path = current.focusedDocument?.path;
|
|
713
|
+
return path === void 0 ? current : withFocusedCollection({
|
|
714
|
+
...current,
|
|
715
|
+
candidate: void 0,
|
|
716
|
+
candidateText: ""
|
|
717
|
+
}, path, {
|
|
718
|
+
candidate: void 0,
|
|
719
|
+
candidateText: ""
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
return id;
|
|
723
|
+
}
|
|
724
|
+
function addAuthorRegion(sessionId, region) {
|
|
725
|
+
if (region.comment.trim() === "") throw new Error("Every Author annotation requires a comment.");
|
|
726
|
+
updateSession(sessionId, (session) => {
|
|
727
|
+
const focused = session.focusedDocument;
|
|
728
|
+
if (focused === void 0 || focused.path !== normalizeAuthorPath(region.documentPath)) throw new Error("The Author annotation does not belong to the focused document.");
|
|
729
|
+
if (focused.revision !== region.revision || focused.sourceSha256 !== region.sourceSha256) throw new Error("The Author annotation is stale for the focused document.");
|
|
730
|
+
if (session.annotations.length >= 16) throw new Error(`Author requests support at most 16 annotations.`);
|
|
731
|
+
if (session.annotations.some((candidate) => candidate.id === region.id)) throw new Error(`Author annotation '${region.id}' already exists.`);
|
|
732
|
+
const annotations = [...session.annotations, copyRegion({
|
|
733
|
+
...region,
|
|
734
|
+
version: region.version ?? 1
|
|
735
|
+
})];
|
|
736
|
+
return withFocusedCollection({
|
|
737
|
+
...session,
|
|
738
|
+
annotations,
|
|
739
|
+
regions: annotations
|
|
740
|
+
}, focused.path, { annotations });
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
function removeAuthorRegion(sessionId, regionId, expectedVersion, documentPath) {
|
|
744
|
+
let thumbnail;
|
|
745
|
+
updateSession(sessionId, (session) => {
|
|
746
|
+
const path = documentPath === void 0 ? session.focusedDocument?.path : normalizeAuthorPath(documentPath);
|
|
747
|
+
if (path === void 0) return session;
|
|
748
|
+
const focused = session.focusedDocument?.path === path;
|
|
749
|
+
const collection = session.annotationsByDocument[path];
|
|
750
|
+
const current = focused ? session.annotations : collection?.annotations;
|
|
751
|
+
if (current === void 0) return session;
|
|
752
|
+
const annotation = current.find((candidate) => candidate.id === regionId);
|
|
753
|
+
if (annotation === void 0 || expectedVersion !== void 0 && (annotation.version ?? 1) !== expectedVersion) return session;
|
|
754
|
+
thumbnail = annotation.thumbnailUrl;
|
|
755
|
+
const annotations = current.filter((candidate) => candidate.id !== regionId);
|
|
756
|
+
if (focused) return withFocusedCollection({
|
|
757
|
+
...session,
|
|
758
|
+
annotations,
|
|
759
|
+
regions: annotations
|
|
760
|
+
}, path, { annotations });
|
|
761
|
+
return {
|
|
762
|
+
...session,
|
|
763
|
+
annotationsByDocument: {
|
|
764
|
+
...session.annotationsByDocument,
|
|
765
|
+
[path]: {
|
|
766
|
+
...collection,
|
|
767
|
+
annotations,
|
|
768
|
+
updatedAt: Date.now()
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
});
|
|
773
|
+
if (thumbnail !== void 0) revokeThumbnail(thumbnail);
|
|
774
|
+
}
|
|
775
|
+
function putAuthorRequest(sessionId, record) {
|
|
776
|
+
if (record.requestText.trim() === "") throw new Error("An Author request requires verbatim request text.");
|
|
777
|
+
if (record.regions.length === 0 || record.regions.some((region) => region.comment.trim() === "")) throw new Error("An Author request requires at least one commented region.");
|
|
778
|
+
updateSession(sessionId, (session) => {
|
|
779
|
+
if (session.requests.some((request) => request.id === record.id)) throw new Error(`Author request '${record.id}' already exists.`);
|
|
780
|
+
return {
|
|
781
|
+
...session,
|
|
782
|
+
requests: boundHistory([...session.requests, copyRequest(record)])
|
|
783
|
+
};
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
function updateAuthorRequest(sessionId, requestId, update) {
|
|
787
|
+
updateSession(sessionId, (session) => ({
|
|
788
|
+
...session,
|
|
789
|
+
requests: boundHistory(session.requests.map((request) => request.id === requestId ? copyRequest(update(request)) : request))
|
|
790
|
+
}));
|
|
791
|
+
}
|
|
792
|
+
function reviseAuthorDocument(sessionId, path, content) {
|
|
793
|
+
updateDocument(sessionId, path, (document) => {
|
|
794
|
+
if (document.content === content) return document;
|
|
795
|
+
const version = document.version + 1;
|
|
796
|
+
return {
|
|
797
|
+
...document,
|
|
798
|
+
content,
|
|
799
|
+
version,
|
|
800
|
+
revisions: [...document.revisions, {
|
|
801
|
+
revision: version,
|
|
802
|
+
content
|
|
803
|
+
}]
|
|
804
|
+
};
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
function reviseAuthorFragment(sessionId, path, fragmentId, text) {
|
|
808
|
+
updateDocument(sessionId, path, (document) => {
|
|
809
|
+
const fragment = document.fragments?.find((candidate) => candidate.id === fragmentId);
|
|
810
|
+
if (fragment === void 0 || fragment.text === text) return document;
|
|
811
|
+
const version = document.version + 1;
|
|
812
|
+
return {
|
|
813
|
+
...document,
|
|
814
|
+
fragments: document.fragments?.map((fragment) => fragment.id === fragmentId ? {
|
|
815
|
+
...fragment,
|
|
816
|
+
text
|
|
817
|
+
} : fragment),
|
|
818
|
+
version,
|
|
819
|
+
revisions: [...document.revisions, {
|
|
820
|
+
revision: version,
|
|
821
|
+
content: text
|
|
822
|
+
}]
|
|
823
|
+
};
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
function addAuthorAnnotation(sessionId, path, annotation) {
|
|
827
|
+
updateDocument(sessionId, path, (document) => ({
|
|
828
|
+
...document,
|
|
829
|
+
annotations: [...document.annotations, annotation]
|
|
830
|
+
}));
|
|
831
|
+
}
|
|
832
|
+
function setAuthorCrop(sessionId, path, crop) {
|
|
833
|
+
updateDocument(sessionId, path, (document) => {
|
|
834
|
+
const version = document.version + 1;
|
|
835
|
+
return {
|
|
836
|
+
...document,
|
|
837
|
+
...crop === void 0 ? { crop: void 0 } : { crop },
|
|
838
|
+
version,
|
|
839
|
+
revisions: [...document.revisions, {
|
|
840
|
+
revision: version,
|
|
841
|
+
content: JSON.stringify(crop ?? null)
|
|
842
|
+
}]
|
|
843
|
+
};
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
function requestAuthorSave(sessionId, path) {
|
|
847
|
+
updateDocument(sessionId, path, (document) => ({
|
|
848
|
+
...document,
|
|
849
|
+
saveRequest: document.saveRequest + 1,
|
|
850
|
+
savingVersion: document.version
|
|
851
|
+
}));
|
|
852
|
+
}
|
|
853
|
+
function failAuthorSave(sessionId, path, savingVersion) {
|
|
854
|
+
updateDocument(sessionId, path, (document) => document.savingVersion === savingVersion ? {
|
|
855
|
+
...document,
|
|
856
|
+
savingVersion: void 0
|
|
857
|
+
} : document);
|
|
858
|
+
}
|
|
859
|
+
function completeAuthorSave(sessionId, path, sourceSha256, savedVersion, savedFragments) {
|
|
860
|
+
updateDocument(sessionId, path, (document) => {
|
|
861
|
+
if (savedVersion < document.savedVersion) return document;
|
|
862
|
+
return {
|
|
863
|
+
...document,
|
|
864
|
+
sourceSha256,
|
|
865
|
+
savedVersion,
|
|
866
|
+
savingVersion: document.savingVersion === savedVersion ? void 0 : document.savingVersion,
|
|
867
|
+
originalFragments: savedFragments?.map((fragment) => ({ ...fragment })),
|
|
868
|
+
...document.version === savedVersion ? { crop: void 0 } : {},
|
|
869
|
+
revisions: document.revisions.filter((revision) => revision.revision > savedVersion)
|
|
870
|
+
};
|
|
871
|
+
});
|
|
872
|
+
const now = Date.now();
|
|
873
|
+
updateSession(sessionId, (session) => ({
|
|
874
|
+
...session,
|
|
875
|
+
requests: boundHistory(session.requests.map((request) => request.documentPath === normalizeAuthorPath(path) && request.status === "CHANGED" && request.revision <= savedVersion ? {
|
|
876
|
+
...request,
|
|
877
|
+
status: "COMPLETE",
|
|
878
|
+
currentOperation: void 0,
|
|
879
|
+
updatedAt: now
|
|
880
|
+
} : request))
|
|
881
|
+
}));
|
|
882
|
+
}
|
|
883
|
+
function dropAuthorSession(sessionId) {
|
|
884
|
+
const prefix = `${sessionId}\n`;
|
|
885
|
+
const session = authorWorkspace.store.state.sessions[sessionId];
|
|
886
|
+
for (const collection of Object.values(session?.annotationsByDocument ?? {})) {
|
|
887
|
+
if (collection.candidate?.thumbnailUrl !== void 0) revokeThumbnail(collection.candidate.thumbnailUrl);
|
|
888
|
+
collection.annotations.forEach((annotation) => {
|
|
889
|
+
if (annotation.thumbnailUrl !== void 0) revokeThumbnail(annotation.thumbnailUrl);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
authorWorkspace.update((state) => {
|
|
893
|
+
const sessions = { ...state.sessions };
|
|
894
|
+
delete sessions[sessionId];
|
|
895
|
+
return {
|
|
896
|
+
documents: Object.fromEntries(Object.entries(state.documents).filter(([key]) => !key.startsWith(prefix))),
|
|
897
|
+
sessions
|
|
898
|
+
};
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
function setAuthorStoryPreview(sessionId, path, storyPreview) {
|
|
902
|
+
updateDocument(sessionId, path, (document) => ({
|
|
903
|
+
...document,
|
|
904
|
+
storyPreview: structuredClone(storyPreview)
|
|
905
|
+
}));
|
|
906
|
+
}
|
|
907
|
+
function updateDocument(sessionId, path, update) {
|
|
908
|
+
const key = authorDocumentKey(sessionId, path);
|
|
909
|
+
const staleThumbnails = [];
|
|
910
|
+
authorWorkspace.update((state) => {
|
|
911
|
+
const document = state.documents[key];
|
|
912
|
+
if (document === void 0) return state;
|
|
913
|
+
const next = update(document);
|
|
914
|
+
const session = state.sessions[sessionId];
|
|
915
|
+
const focused = session?.focusedDocument;
|
|
916
|
+
const identityChanged = focused?.path === document.path && (focused.revision !== next.version || focused.sourceSha256 !== next.sourceSha256);
|
|
917
|
+
if (identityChanged) staleThumbnails.push(...session.candidate?.thumbnailUrl === void 0 ? [] : [session.candidate.thumbnailUrl], ...session.annotations.flatMap((annotation) => annotation.thumbnailUrl === void 0 ? [] : [annotation.thumbnailUrl]));
|
|
918
|
+
const staleCollection = identityChanged ? staleAuthorCollection(session.annotationsByDocument[document.path], Date.now()) : void 0;
|
|
919
|
+
const sessions = identityChanged ? {
|
|
920
|
+
...state.sessions,
|
|
921
|
+
[sessionId]: {
|
|
922
|
+
...session,
|
|
923
|
+
focusedDocument: {
|
|
924
|
+
...focused,
|
|
925
|
+
revision: next.version,
|
|
926
|
+
sourceSha256: next.sourceSha256
|
|
927
|
+
},
|
|
928
|
+
annotationsByDocument: {
|
|
929
|
+
...session.annotationsByDocument,
|
|
930
|
+
...staleCollection && { [document.path]: staleCollection }
|
|
931
|
+
},
|
|
932
|
+
candidate: void 0,
|
|
933
|
+
candidateText: "",
|
|
934
|
+
annotations: [],
|
|
935
|
+
regions: []
|
|
936
|
+
}
|
|
937
|
+
} : state.sessions;
|
|
938
|
+
return {
|
|
939
|
+
documents: {
|
|
940
|
+
...state.documents,
|
|
941
|
+
[key]: next
|
|
942
|
+
},
|
|
943
|
+
sessions
|
|
944
|
+
};
|
|
945
|
+
});
|
|
946
|
+
staleThumbnails.forEach(revokeThumbnail);
|
|
947
|
+
}
|
|
948
|
+
function updateSession(sessionId, update) {
|
|
949
|
+
authorWorkspace.update((state) => {
|
|
950
|
+
const session = state.sessions[sessionId] ?? EMPTY_SESSION;
|
|
951
|
+
const next = update(session);
|
|
952
|
+
return next === session ? state : {
|
|
953
|
+
...state,
|
|
954
|
+
sessions: {
|
|
955
|
+
...state.sessions,
|
|
956
|
+
[sessionId]: next
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
function copyCandidate(candidate) {
|
|
962
|
+
const anchor = "rect" in candidate.anchor ? {
|
|
963
|
+
...candidate.anchor,
|
|
964
|
+
rect: { ...candidate.anchor.rect }
|
|
965
|
+
} : "point" in candidate.anchor ? {
|
|
966
|
+
...candidate.anchor,
|
|
967
|
+
point: { ...candidate.anchor.point }
|
|
968
|
+
} : { ...candidate.anchor };
|
|
969
|
+
return {
|
|
970
|
+
...candidate,
|
|
971
|
+
anchor,
|
|
972
|
+
viewport: { ...candidate.viewport },
|
|
973
|
+
voiceGrid: candidate.voiceGrid && { ...candidate.voiceGrid }
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function copyRegion(region) {
|
|
977
|
+
return copyCandidate(region);
|
|
978
|
+
}
|
|
979
|
+
function staleAuthorCollection(collection, updatedAt) {
|
|
980
|
+
if (collection === void 0) return void 0;
|
|
981
|
+
return {
|
|
982
|
+
...collection,
|
|
983
|
+
stale: true,
|
|
984
|
+
candidate: collection.candidate && {
|
|
985
|
+
...copyCandidate(collection.candidate),
|
|
986
|
+
thumbnailUrl: void 0
|
|
987
|
+
},
|
|
988
|
+
annotations: collection.annotations.map((annotation) => ({
|
|
989
|
+
...copyRegion(annotation),
|
|
990
|
+
thumbnailUrl: void 0
|
|
991
|
+
})),
|
|
992
|
+
updatedAt
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function withFocusedCollection(session, path, update) {
|
|
996
|
+
const focused = session.focusedDocument;
|
|
997
|
+
if (focused === void 0 || focused.path !== path) return session;
|
|
998
|
+
const collection = {
|
|
999
|
+
...session.annotationsByDocument[path],
|
|
1000
|
+
revision: focused.revision,
|
|
1001
|
+
sourceSha256: focused.sourceSha256,
|
|
1002
|
+
stale: false,
|
|
1003
|
+
candidate: session.candidate,
|
|
1004
|
+
candidateText: session.candidateText,
|
|
1005
|
+
annotations: session.annotations,
|
|
1006
|
+
updatedAt: Date.now(),
|
|
1007
|
+
...update
|
|
1008
|
+
};
|
|
1009
|
+
return {
|
|
1010
|
+
...session,
|
|
1011
|
+
annotationsByDocument: {
|
|
1012
|
+
...session.annotationsByDocument,
|
|
1013
|
+
[path]: collection
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
function boundedHistoryText(value) {
|
|
1018
|
+
if (value === void 0) return void 0;
|
|
1019
|
+
const encoder = new TextEncoder();
|
|
1020
|
+
if (encoder.encode(value).byteLength <= 16384) return value;
|
|
1021
|
+
let end = value.length;
|
|
1022
|
+
while (end > 0 && encoder.encode(value.slice(0, end)).byteLength > 16384) end -= 256;
|
|
1023
|
+
return value.slice(0, end);
|
|
1024
|
+
}
|
|
1025
|
+
function copyRequest(record) {
|
|
1026
|
+
return {
|
|
1027
|
+
...record,
|
|
1028
|
+
before: boundedHistoryText(record.before),
|
|
1029
|
+
after: boundedHistoryText(record.after),
|
|
1030
|
+
regions: record.regions.map(copyRegion),
|
|
1031
|
+
pendingRegions: record.pendingRegions?.map(copyRegion)
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function boundHistory(records) {
|
|
1035
|
+
const next = [...records];
|
|
1036
|
+
const bytes = () => new TextEncoder().encode(JSON.stringify(next)).byteLength;
|
|
1037
|
+
while (next.length > 100 || bytes() > 524288) {
|
|
1038
|
+
const terminal = next.findIndex((record) => TERMINAL_REQUEST_STATUSES.has(record.status));
|
|
1039
|
+
if (terminal < 0) throw new Error("Author request history is full while active requests are still running.");
|
|
1040
|
+
next.splice(terminal, 1);
|
|
1041
|
+
}
|
|
1042
|
+
return next;
|
|
1043
|
+
}
|
|
1044
|
+
function restoreCollectionEvidence(collection) {
|
|
1045
|
+
const restore = (item) => {
|
|
1046
|
+
const copied = copyCandidate(item);
|
|
1047
|
+
const existingVersion = Reflect.get(copied, "version");
|
|
1048
|
+
const versioned = "id" in copied ? {
|
|
1049
|
+
...copied,
|
|
1050
|
+
version: typeof existingVersion === "number" ? existingVersion : 1
|
|
1051
|
+
} : copied;
|
|
1052
|
+
if (versioned.thumbnailUrl !== void 0 || versioned.evidence === void 0) return versioned;
|
|
1053
|
+
try {
|
|
1054
|
+
return {
|
|
1055
|
+
...versioned,
|
|
1056
|
+
thumbnailUrl: URL.createObjectURL(versioned.evidence)
|
|
1057
|
+
};
|
|
1058
|
+
} catch {
|
|
1059
|
+
return versioned;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
return {
|
|
1063
|
+
...collection,
|
|
1064
|
+
candidate: collection.candidate && restore(collection.candidate),
|
|
1065
|
+
annotations: collection.annotations.map(restore)
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
function revokeThumbnail(url) {
|
|
1069
|
+
if (!url.startsWith("blob:")) return;
|
|
1070
|
+
try {
|
|
1071
|
+
URL.revokeObjectURL(url);
|
|
1072
|
+
} catch {}
|
|
1073
|
+
}
|
|
1074
|
+
//#endregion
|
|
1075
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/_lib/authorStore.ts
|
|
1076
|
+
const author = defineSessionStore({
|
|
1077
|
+
activation: "inactive",
|
|
1078
|
+
capabilityCount: 0
|
|
1079
|
+
});
|
|
1080
|
+
function isRecord(value) {
|
|
1081
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1082
|
+
}
|
|
1083
|
+
function parseAuthorHubMessage(input) {
|
|
1084
|
+
if (!isRecord(input)) return null;
|
|
1085
|
+
if ((input.activation === "active" || input.activation === "inactive") && Number.isSafeInteger(input.capabilityCount) && input.capabilityCount >= 0) return input;
|
|
1086
|
+
if (typeof input.kind !== "string") return null;
|
|
1087
|
+
if (input.kind === "rejected") return typeof input.reason === "string" ? input : null;
|
|
1088
|
+
if (!Number.isSafeInteger(input.generation) || typeof input.ownerToken !== "string") return null;
|
|
1089
|
+
if (input.kind === "accepted") return typeof input.leaseMs === "number" && (input.catalogToken === void 0 || typeof input.catalogToken === "string") ? input : null;
|
|
1090
|
+
if ((input.kind === "request" || input.kind === "cancel") && typeof input.catalogToken === "string" && typeof input.requestId === "string") {
|
|
1091
|
+
if (input.kind === "cancel") return input;
|
|
1092
|
+
return typeof input.name === "string" && isRecord(input.arguments) ? input : null;
|
|
1093
|
+
}
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
//#endregion
|
|
1097
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/author-webmcp.web.ts
|
|
1098
|
+
var author_webmcp_web_default = defineChannelFile({
|
|
1099
|
+
channel: authorChannelType,
|
|
1100
|
+
parse: parseAuthorHubMessage,
|
|
1101
|
+
apply(sessionId, message) {
|
|
1102
|
+
if ("activation" in message) {
|
|
1103
|
+
author.update(sessionId, () => message);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
applyAuthorHubMessage(sessionId, message);
|
|
1107
|
+
author.update(sessionId, () => authorBridgeView(sessionId));
|
|
1108
|
+
},
|
|
1109
|
+
drop(sessionId) {
|
|
1110
|
+
clearAuthorGridGeometry(sessionId);
|
|
1111
|
+
dropAuthorViewportSession(sessionId);
|
|
1112
|
+
dropAuthorSession(sessionId);
|
|
1113
|
+
author.drop(sessionId);
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorGridOverlay.tsx
|
|
1118
|
+
function autonomousVoiceGridVisible(statuses) {
|
|
1119
|
+
return statuses["doom-voice"]?.trimStart().startsWith("voice auto:") === true;
|
|
1120
|
+
}
|
|
1121
|
+
function AuthorGridOverlay({ sessionId, document, visible }) {
|
|
1122
|
+
const host = useRef(null);
|
|
1123
|
+
useEffect(() => {
|
|
1124
|
+
const element = host.current?.parentElement;
|
|
1125
|
+
if (!visible || element === null || element === void 0) {
|
|
1126
|
+
clearAuthorGridGeometry(sessionId);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const publish = () => {
|
|
1130
|
+
const bounds = element.getBoundingClientRect();
|
|
1131
|
+
updateAuthorGridGeometry(sessionId, {
|
|
1132
|
+
documentPath: document.path,
|
|
1133
|
+
revision: document.version,
|
|
1134
|
+
sourceSha256: document.sourceSha256,
|
|
1135
|
+
viewport: {
|
|
1136
|
+
width: bounds.width,
|
|
1137
|
+
height: bounds.height,
|
|
1138
|
+
originX: bounds.left,
|
|
1139
|
+
originY: bounds.top,
|
|
1140
|
+
scrollX: element.scrollLeft,
|
|
1141
|
+
scrollY: element.scrollTop,
|
|
1142
|
+
zoom: window.devicePixelRatio
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
};
|
|
1146
|
+
publish();
|
|
1147
|
+
const observer = new ResizeObserver(publish);
|
|
1148
|
+
observer.observe(element);
|
|
1149
|
+
element.addEventListener("scroll", publish, {
|
|
1150
|
+
passive: true,
|
|
1151
|
+
capture: true
|
|
1152
|
+
});
|
|
1153
|
+
window.addEventListener("focus", publish);
|
|
1154
|
+
return () => {
|
|
1155
|
+
observer.disconnect();
|
|
1156
|
+
element.removeEventListener("scroll", publish, true);
|
|
1157
|
+
window.removeEventListener("focus", publish);
|
|
1158
|
+
clearAuthorGridGeometry(sessionId);
|
|
1159
|
+
};
|
|
1160
|
+
}, [
|
|
1161
|
+
document.path,
|
|
1162
|
+
document.sourceSha256,
|
|
1163
|
+
document.version,
|
|
1164
|
+
sessionId,
|
|
1165
|
+
visible
|
|
1166
|
+
]);
|
|
1167
|
+
if (!visible) return null;
|
|
1168
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1169
|
+
ref: host,
|
|
1170
|
+
"data-testid": "author-grid",
|
|
1171
|
+
"aria-hidden": "true",
|
|
1172
|
+
className: "pointer-events-none absolute inset-0 z-20 grid grid-cols-8 grid-rows-8",
|
|
1173
|
+
children: Array.from({ length: 64 }, (_, index) => {
|
|
1174
|
+
const column = index % 8;
|
|
1175
|
+
const row = Math.floor(index / 8);
|
|
1176
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1177
|
+
className: "relative border border-doom-red/25",
|
|
1178
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
1179
|
+
className: "absolute left-1 top-0.5 rounded bg-doom-deep/80 px-1 text-2xs font-bold text-doom-red",
|
|
1180
|
+
children: [AUTHOR_GRID_COLUMNS[column], row + 1]
|
|
1181
|
+
})
|
|
1182
|
+
}, index);
|
|
1183
|
+
})
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
//#endregion
|
|
1187
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorCapture.ts
|
|
1188
|
+
const AUTHOR_CAPTURE_MAX_DIMENSION = 1600;
|
|
1189
|
+
const AUTHOR_QUOTE_MAX_BYTES = 4096;
|
|
1190
|
+
const AUTHOR_REGION_COLORS = [
|
|
1191
|
+
"#ff5f57",
|
|
1192
|
+
"#00c2ff",
|
|
1193
|
+
"#ffd166",
|
|
1194
|
+
"#b388ff",
|
|
1195
|
+
"#4ade80",
|
|
1196
|
+
"#ff8c42",
|
|
1197
|
+
"#f472b6",
|
|
1198
|
+
"#2dd4bf",
|
|
1199
|
+
"#a3e635",
|
|
1200
|
+
"#60a5fa",
|
|
1201
|
+
"#e879f9",
|
|
1202
|
+
"#facc15",
|
|
1203
|
+
"#34d399",
|
|
1204
|
+
"#fb7185",
|
|
1205
|
+
"#818cf8",
|
|
1206
|
+
"#22d3ee"
|
|
1207
|
+
];
|
|
1208
|
+
function annotationForeground(color) {
|
|
1209
|
+
const red = Number.parseInt(color.slice(1, 3), 16);
|
|
1210
|
+
const green = Number.parseInt(color.slice(3, 5), 16);
|
|
1211
|
+
const blue = Number.parseInt(color.slice(5, 7), 16);
|
|
1212
|
+
return red * 299 + green * 587 + blue * 114 >= 15e4 ? "#101216" : "#ffffff";
|
|
1213
|
+
}
|
|
1214
|
+
const bytes = (value) => new TextEncoder().encode(value).byteLength;
|
|
1215
|
+
function utf8Prefix(value, limit) {
|
|
1216
|
+
if (bytes(value) <= limit) return value;
|
|
1217
|
+
let low = 0;
|
|
1218
|
+
let high = value.length;
|
|
1219
|
+
while (low < high) {
|
|
1220
|
+
const middle = Math.ceil((low + high) / 2);
|
|
1221
|
+
if (bytes(value.slice(0, middle)) <= limit) low = middle;
|
|
1222
|
+
else high = middle - 1;
|
|
1223
|
+
}
|
|
1224
|
+
return value.slice(0, low);
|
|
1225
|
+
}
|
|
1226
|
+
function createAuthorCapturePacket(captureId, capturedAt, document, regions) {
|
|
1227
|
+
if (regions.length === 0 || regions.length > 16) throw new Error("Capture requires between 1 and 16 regions.");
|
|
1228
|
+
if (document.kind === "story-preview" && document.storyPreview === void 0) throw new Error("Story preview capture requires source provenance.");
|
|
1229
|
+
const packet = {
|
|
1230
|
+
version: 1,
|
|
1231
|
+
captureId,
|
|
1232
|
+
capturedAt,
|
|
1233
|
+
document: {
|
|
1234
|
+
path: document.path,
|
|
1235
|
+
kind: document.kind,
|
|
1236
|
+
revision: document.version,
|
|
1237
|
+
...document.sourceSha256 === void 0 ? {} : { sourceSha256: document.sourceSha256 },
|
|
1238
|
+
...document.storyPreview === void 0 ? {} : { storyPreview: structuredClone(document.storyPreview) }
|
|
1239
|
+
},
|
|
1240
|
+
regions: regions.map((region, index) => {
|
|
1241
|
+
if (region.documentPath !== document.path || region.revision !== document.version || region.sourceSha256 !== document.sourceSha256) throw new Error(`Region ${String(index + 1)} is stale for the focused document.`);
|
|
1242
|
+
if (region.comment.trim() === "") throw new Error(`Region ${String(index + 1)} requires a comment.`);
|
|
1243
|
+
if (bytes(region.comment) > 2048) throw new Error(`Region ${String(index + 1)} comment exceeds 2 KiB.`);
|
|
1244
|
+
return {
|
|
1245
|
+
id: region.id,
|
|
1246
|
+
ordinal: index + 1,
|
|
1247
|
+
comment: region.comment,
|
|
1248
|
+
version: region.version ?? 1,
|
|
1249
|
+
mode: region.mode ?? ("point" in region.anchor ? "point" : "region"),
|
|
1250
|
+
...region.quote === void 0 ? {} : { quote: utf8Prefix(region.quote, AUTHOR_QUOTE_MAX_BYTES) },
|
|
1251
|
+
anchor: structuredClone(region.anchor),
|
|
1252
|
+
viewport: { ...region.viewport },
|
|
1253
|
+
...region.voiceGrid === void 0 ? {} : { voiceGrid: { ...region.voiceGrid } }
|
|
1254
|
+
};
|
|
1255
|
+
})
|
|
1256
|
+
};
|
|
1257
|
+
if (bytes(JSON.stringify(packet)) > 65536) throw new Error("Author capture packet exceeds 64 KiB.");
|
|
1258
|
+
return packet;
|
|
1259
|
+
}
|
|
1260
|
+
function percent(value) {
|
|
1261
|
+
return `${String(Math.round(value * 1e3) / 10)}%`;
|
|
1262
|
+
}
|
|
1263
|
+
function rectLocation(rect) {
|
|
1264
|
+
return `x ${percent(rect.x)}, y ${percent(rect.y)}, w ${percent(rect.width)}, h ${percent(rect.height)}`;
|
|
1265
|
+
}
|
|
1266
|
+
function anchorLocation(anchor) {
|
|
1267
|
+
switch (anchor.kind) {
|
|
1268
|
+
case "text-range": return anchor.startLine === anchor.endLine ? `line ${String(anchor.startLine)}` : `lines ${String(anchor.startLine)}-${String(anchor.endLine)}`;
|
|
1269
|
+
case "cell": return `${anchor.sheet === void 0 ? "" : `${anchor.sheet}!`}${anchor.location}`;
|
|
1270
|
+
case "slide-element": return `slide ${String(anchor.slide)}: ${anchor.location}`;
|
|
1271
|
+
case "image-rect": return `image ${rectLocation(anchor.rect)}`;
|
|
1272
|
+
case "pdf-page-rect": return `page ${String(anchor.page)}, ${rectLocation(anchor.rect)}`;
|
|
1273
|
+
case "video-time-rect": return `time ${anchor.timeSeconds.toFixed(3)}s${anchor.frame === void 0 ? "" : `, frame ${String(anchor.frame)}`}, ${rectLocation(anchor.rect)}`;
|
|
1274
|
+
case "story-preview-rect": return `story preview ${rectLocation(anchor.rect)}`;
|
|
1275
|
+
case "image-point": return `image point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
|
|
1276
|
+
case "pdf-page-point": return `page ${String(anchor.page)}, point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
|
|
1277
|
+
case "video-time-point": return `time ${anchor.timeSeconds.toFixed(3)}s, point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
|
|
1278
|
+
case "story-preview-point": return `story preview point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
function storyPreviewProvenance(packet) {
|
|
1282
|
+
const preview = packet.document.storyPreview;
|
|
1283
|
+
if (preview === void 0) return void 0;
|
|
1284
|
+
const sources = preview.sources.map((source) => `- ${source.path} (sha256 ${source.sha256})`).join("\n");
|
|
1285
|
+
return [
|
|
1286
|
+
"Story preview source (edit these sources, never the generated preview artifact):",
|
|
1287
|
+
`Project: ${preview.projectPath}`,
|
|
1288
|
+
`Story: ${preview.storyPath}#${preview.storyExport}`,
|
|
1289
|
+
`Build revision: ${preview.buildRevision}`,
|
|
1290
|
+
`Viewport: ${String(preview.viewport.width)}x${String(preview.viewport.height)}`,
|
|
1291
|
+
"Sources:",
|
|
1292
|
+
sources,
|
|
1293
|
+
"After editing the underlying story or component source, rebuild the preview before treating it as visually verified."
|
|
1294
|
+
].join("\n");
|
|
1295
|
+
}
|
|
1296
|
+
function authorCaptureContext(packet) {
|
|
1297
|
+
const metadata = JSON.stringify(packet);
|
|
1298
|
+
if (bytes(metadata) > 65536) throw new Error("Author capture packet exceeds 64 KiB.");
|
|
1299
|
+
const annotations = packet.regions.map((region) => `(${String(region.ordinal)}) ${region.comment} [${anchorLocation(region.anchor)}]${region.quote ? `\nQuoted text: ${region.quote}` : ""}`).join("\n\n");
|
|
1300
|
+
const provenance = storyPreviewProvenance(packet);
|
|
1301
|
+
const content = provenance === void 0 ? annotations : `${provenance}\n\nAnnotations:\n${annotations}`;
|
|
1302
|
+
return {
|
|
1303
|
+
kind: "author-capture",
|
|
1304
|
+
source: "author",
|
|
1305
|
+
id: packet.captureId,
|
|
1306
|
+
label: `${packet.regions.length} annotation${packet.regions.length === 1 ? "" : "s"} · ${packet.document.path}`,
|
|
1307
|
+
content,
|
|
1308
|
+
metadata
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function canvasBlob(canvas, type, quality) {
|
|
1312
|
+
return new Promise((resolve, reject) => {
|
|
1313
|
+
canvas.toBlob((blob) => blob === null ? reject(/* @__PURE__ */ new Error("Unable to capture viewport")) : resolve(blob), type, quality);
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
async function base64(blob) {
|
|
1317
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
1318
|
+
let binary = "";
|
|
1319
|
+
for (let offset = 0; offset < bytes.length; offset += 32768) binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
1320
|
+
return btoa(binary);
|
|
1321
|
+
}
|
|
1322
|
+
function throwIfAborted(signal) {
|
|
1323
|
+
if (signal?.aborted === true) throw signal.reason;
|
|
1324
|
+
}
|
|
1325
|
+
async function boundedCanvasCapture(canvas) {
|
|
1326
|
+
let mimeType = "image/png";
|
|
1327
|
+
let blob = await canvasBlob(canvas, mimeType);
|
|
1328
|
+
for (const quality of [
|
|
1329
|
+
.9,
|
|
1330
|
+
.75,
|
|
1331
|
+
.6,
|
|
1332
|
+
.45,
|
|
1333
|
+
.3
|
|
1334
|
+
]) {
|
|
1335
|
+
if (blob.size <= 2097152) break;
|
|
1336
|
+
mimeType = "image/jpeg";
|
|
1337
|
+
blob = await canvasBlob(canvas, mimeType, quality);
|
|
1338
|
+
}
|
|
1339
|
+
if (blob.size > 2097152) throw new Error("Viewport capture exceeds 2 MiB");
|
|
1340
|
+
return {
|
|
1341
|
+
data: await base64(blob),
|
|
1342
|
+
mimeType
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
function loadImage(url) {
|
|
1346
|
+
return new Promise((resolve, reject) => {
|
|
1347
|
+
const image = new Image();
|
|
1348
|
+
image.onload = () => resolve(image);
|
|
1349
|
+
image.onerror = () => reject(/* @__PURE__ */ new Error("Unable to load a region capture."));
|
|
1350
|
+
image.src = url;
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
function multiRegionCaptureProvider(regions) {
|
|
1354
|
+
return { async capture(signal) {
|
|
1355
|
+
throwIfAborted(signal);
|
|
1356
|
+
if (regions.length === 0 || regions.length > 16) throw new Error("Capture requires between 1 and 16 regions.");
|
|
1357
|
+
const tiles = [];
|
|
1358
|
+
for (const region of regions) {
|
|
1359
|
+
const image = region.thumbnailUrl === void 0 ? void 0 : await loadImage(region.thumbnailUrl);
|
|
1360
|
+
throwIfAborted(signal);
|
|
1361
|
+
const width = image?.naturalWidth ?? region.viewport.width;
|
|
1362
|
+
const height = image?.naturalHeight ?? region.viewport.height;
|
|
1363
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) throw new Error("Image is not ready to capture");
|
|
1364
|
+
tiles.push({
|
|
1365
|
+
region,
|
|
1366
|
+
image,
|
|
1367
|
+
width,
|
|
1368
|
+
height
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
const width = Math.min(AUTHOR_CAPTURE_MAX_DIMENSION, Math.max(...tiles.map((tile) => tile.width)));
|
|
1372
|
+
const naturalHeight = tiles.reduce((height, tile) => height + tile.height * width / tile.width, 0);
|
|
1373
|
+
const scale = Math.min(1, AUTHOR_CAPTURE_MAX_DIMENSION / Math.max(width, naturalHeight));
|
|
1374
|
+
const canvas = document.createElement("canvas");
|
|
1375
|
+
canvas.width = Math.max(1, Math.round(width * scale));
|
|
1376
|
+
canvas.height = Math.max(1, Math.round(naturalHeight * scale));
|
|
1377
|
+
const context = canvas.getContext("2d");
|
|
1378
|
+
if (context === null) throw new Error("Canvas is unavailable");
|
|
1379
|
+
context.scale(scale, scale);
|
|
1380
|
+
context.fillStyle = "#101216";
|
|
1381
|
+
context.fillRect(0, 0, width, naturalHeight);
|
|
1382
|
+
let y = 0;
|
|
1383
|
+
for (const [index, { region, image, width: sourceWidth, height: sourceHeight }] of tiles.entries()) {
|
|
1384
|
+
const annotationColor = AUTHOR_REGION_COLORS[index % AUTHOR_REGION_COLORS.length];
|
|
1385
|
+
throwIfAborted(signal);
|
|
1386
|
+
const tileHeight = sourceHeight * width / sourceWidth;
|
|
1387
|
+
const drawWidth = width;
|
|
1388
|
+
const drawHeight = tileHeight;
|
|
1389
|
+
const drawX = 0;
|
|
1390
|
+
const drawY = y;
|
|
1391
|
+
let labelX = drawX;
|
|
1392
|
+
let labelY = drawY;
|
|
1393
|
+
if (image !== void 0) {
|
|
1394
|
+
context.drawImage(image, drawX, drawY, drawWidth, drawHeight);
|
|
1395
|
+
if ("rect" in region.anchor) {
|
|
1396
|
+
const rect = region.anchor.rect;
|
|
1397
|
+
const rx = drawX + rect.x * drawWidth;
|
|
1398
|
+
const ry = drawY + rect.y * drawHeight;
|
|
1399
|
+
const rw = rect.width * drawWidth;
|
|
1400
|
+
const rh = rect.height * drawHeight;
|
|
1401
|
+
context.fillStyle = "rgba(8,10,13,0.62)";
|
|
1402
|
+
context.fillRect(drawX, drawY, drawWidth, Math.max(0, ry - drawY));
|
|
1403
|
+
context.fillRect(drawX, ry + rh, drawWidth, Math.max(0, drawY + drawHeight - ry - rh));
|
|
1404
|
+
context.fillRect(drawX, ry, Math.max(0, rx - drawX), rh);
|
|
1405
|
+
context.fillRect(rx + rw, ry, Math.max(0, drawX + drawWidth - rx - rw), rh);
|
|
1406
|
+
context.strokeStyle = annotationColor;
|
|
1407
|
+
context.lineWidth = 3;
|
|
1408
|
+
context.strokeRect(rx, ry, rw, rh);
|
|
1409
|
+
labelX = rx;
|
|
1410
|
+
labelY = ry;
|
|
1411
|
+
} else if ("point" in region.anchor) {
|
|
1412
|
+
const px = drawX + region.anchor.point.x * drawWidth;
|
|
1413
|
+
const py = drawY + region.anchor.point.y * drawHeight;
|
|
1414
|
+
const radius = Math.max(6, Math.min(14, Math.min(drawWidth, drawHeight) / 12));
|
|
1415
|
+
context.beginPath();
|
|
1416
|
+
context.arc(px, py, radius, 0, Math.PI * 2);
|
|
1417
|
+
context.fillStyle = annotationColor;
|
|
1418
|
+
context.fill();
|
|
1419
|
+
context.strokeStyle = annotationForeground(annotationColor);
|
|
1420
|
+
context.lineWidth = 2;
|
|
1421
|
+
context.stroke();
|
|
1422
|
+
labelX = px + radius;
|
|
1423
|
+
labelY = py - radius;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
const ordinal = String(index + 1);
|
|
1427
|
+
let badgeScale = Math.min(1 / scale, drawHeight / 36);
|
|
1428
|
+
context.font = `bold ${String(24 * badgeScale)}px monospace`;
|
|
1429
|
+
const measuredWidth = context.measureText(ordinal).width + 12 * badgeScale;
|
|
1430
|
+
if (measuredWidth > drawWidth) badgeScale *= drawWidth / measuredWidth;
|
|
1431
|
+
context.font = `bold ${String(24 * badgeScale)}px monospace`;
|
|
1432
|
+
context.textBaseline = "top";
|
|
1433
|
+
const padding = 6 * badgeScale;
|
|
1434
|
+
const labelWidth = Math.min(drawWidth, context.measureText(ordinal).width + padding * 2);
|
|
1435
|
+
const labelHeight = 36 * badgeScale;
|
|
1436
|
+
labelX = Math.max(drawX, Math.min(labelX, drawX + drawWidth - labelWidth));
|
|
1437
|
+
labelY = Math.max(drawY, Math.min(labelY, drawY + drawHeight - labelHeight));
|
|
1438
|
+
context.fillStyle = annotationColor;
|
|
1439
|
+
context.fillRect(labelX, labelY, labelWidth, labelHeight);
|
|
1440
|
+
context.fillStyle = annotationForeground(annotationColor);
|
|
1441
|
+
context.fillText(ordinal, labelX + padding, labelY + padding);
|
|
1442
|
+
y += tileHeight;
|
|
1443
|
+
}
|
|
1444
|
+
throwIfAborted(signal);
|
|
1445
|
+
return boundedCanvasCapture(canvas);
|
|
1446
|
+
} };
|
|
1447
|
+
}
|
|
1448
|
+
//#endregion
|
|
1449
|
+
//#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorRegionDrafts.tsx
|
|
1450
|
+
function AuthorRegionDrafts({ sessionId, workspace }) {
|
|
1451
|
+
const [error, setError] = useState();
|
|
1452
|
+
const add = () => {
|
|
1453
|
+
try {
|
|
1454
|
+
commitAuthorRegion(sessionId, workspace.candidateText);
|
|
1455
|
+
setError(void 0);
|
|
1456
|
+
} catch (reason) {
|
|
1457
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
if (!workspace.candidate && workspace.annotations.length === 0) return null;
|
|
1461
|
+
const candidateKind = workspace.candidate?.mode === "point" ? "point" : "region";
|
|
1462
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
1463
|
+
className: "space-y-2",
|
|
1464
|
+
"data-testid": "author-region-drafts",
|
|
1465
|
+
children: [
|
|
1466
|
+
workspace.candidate ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1467
|
+
/* @__PURE__ */ jsxs("h3", {
|
|
1468
|
+
className: "text-base font-semibold text-doom-text",
|
|
1469
|
+
children: ["Current ", candidateKind]
|
|
1470
|
+
}),
|
|
1471
|
+
/* @__PURE__ */ jsxs("p", {
|
|
1472
|
+
className: "text-base text-doom-dim sm:text-sm",
|
|
1473
|
+
children: [
|
|
1474
|
+
"Add feedback to keep this ",
|
|
1475
|
+
candidateKind,
|
|
1476
|
+
" as an unsent annotation."
|
|
1477
|
+
]
|
|
1478
|
+
}),
|
|
1479
|
+
workspace.candidate.anchor.kind === "video-time-rect" || workspace.candidate.anchor.kind === "video-time-point" ? /* @__PURE__ */ jsxs("p", {
|
|
1480
|
+
className: "text-base text-doom-dim sm:text-sm",
|
|
1481
|
+
children: [
|
|
1482
|
+
"Frame at ",
|
|
1483
|
+
workspace.candidate.anchor.timeSeconds.toFixed(3),
|
|
1484
|
+
"s"
|
|
1485
|
+
]
|
|
1486
|
+
}) : null,
|
|
1487
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
1488
|
+
"aria-label": "Annotation comment",
|
|
1489
|
+
value: workspace.candidateText,
|
|
1490
|
+
onChange: (event) => setAuthorCandidateText(sessionId, event.target.value),
|
|
1491
|
+
placeholder: "What should change here?",
|
|
1492
|
+
rows: 3,
|
|
1493
|
+
className: "min-h-11 w-full resize-y rounded border border-doom-border bg-doom-deep p-2 text-lg leading-normal text-doom-text focus:border-doom-red focus:outline-none sm:text-sm"
|
|
1494
|
+
}),
|
|
1495
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1496
|
+
className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
1497
|
+
variant: "outline",
|
|
1498
|
+
onClick: add,
|
|
1499
|
+
disabled: !workspace.candidate || !workspace.candidateText.trim() || workspace.annotations.length >= 16,
|
|
1500
|
+
children: "add annotation"
|
|
1501
|
+
}),
|
|
1502
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1503
|
+
variant: "ghost",
|
|
1504
|
+
className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
1505
|
+
onClick: () => {
|
|
1506
|
+
setAuthorRegionCandidate(sessionId, void 0);
|
|
1507
|
+
setAuthorCandidateText(sessionId, "");
|
|
1508
|
+
setError(void 0);
|
|
1509
|
+
},
|
|
1510
|
+
children: "Discard annotation"
|
|
1511
|
+
})
|
|
1512
|
+
] }) : null,
|
|
1513
|
+
error ? /* @__PURE__ */ jsx("output", {
|
|
1514
|
+
className: "block text-base text-doom-red",
|
|
1515
|
+
children: error
|
|
1516
|
+
}) : null,
|
|
1517
|
+
workspace.annotations.length > 0 ? /* @__PURE__ */ jsxs("h3", {
|
|
1518
|
+
className: "text-base font-semibold text-doom-text",
|
|
1519
|
+
children: [
|
|
1520
|
+
"Unsent annotations (",
|
|
1521
|
+
workspace.annotations.length,
|
|
1522
|
+
")"
|
|
1523
|
+
]
|
|
1524
|
+
}) : null,
|
|
1525
|
+
/* @__PURE__ */ jsx("ol", {
|
|
1526
|
+
className: "space-y-2",
|
|
1527
|
+
children: workspace.annotations.map((region, index) => /* @__PURE__ */ jsxs("li", {
|
|
1528
|
+
className: "rounded border border-doom-border bg-doom-panel p-2 text-base leading-normal text-doom-text sm:text-sm",
|
|
1529
|
+
children: [
|
|
1530
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1531
|
+
"(",
|
|
1532
|
+
index + 1,
|
|
1533
|
+
") ",
|
|
1534
|
+
region.mode === "point" ? "Point" : "Region",
|
|
1535
|
+
": ",
|
|
1536
|
+
region.comment
|
|
1537
|
+
] }),
|
|
1538
|
+
region.anchor.kind === "video-time-rect" || region.anchor.kind === "video-time-point" ? /* @__PURE__ */ jsxs(Button, {
|
|
1539
|
+
variant: "outline",
|
|
1540
|
+
className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
1541
|
+
disabled: workspace.candidate !== void 0,
|
|
1542
|
+
onClick: () => {
|
|
1543
|
+
if (region.anchor.kind === "video-time-rect" || region.anchor.kind === "video-time-point") seekAuthorVideo(sessionId, region.anchor.timeSeconds);
|
|
1544
|
+
},
|
|
1545
|
+
children: [
|
|
1546
|
+
"Go to ",
|
|
1547
|
+
region.anchor.timeSeconds.toFixed(3),
|
|
1548
|
+
"s"
|
|
1549
|
+
]
|
|
1550
|
+
}) : null,
|
|
1551
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1552
|
+
className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
1553
|
+
variant: "ghost",
|
|
1554
|
+
"aria-label": `Remove annotation ${index + 1}`,
|
|
1555
|
+
onClick: () => removeAuthorRegion(sessionId, region.id),
|
|
1556
|
+
children: "remove"
|
|
1557
|
+
})
|
|
1558
|
+
]
|
|
1559
|
+
}, region.id))
|
|
1560
|
+
})
|
|
1561
|
+
]
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
//#endregion
|
|
1565
|
+
//#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorRequestLog.tsx
|
|
1566
|
+
const STATUS_LABELS = {
|
|
1567
|
+
REQUESTED: "Request queued",
|
|
1568
|
+
CHANGING: "Agent working",
|
|
1569
|
+
CHANGED: "Change ready",
|
|
1570
|
+
COMPLETE: "Request completed",
|
|
1571
|
+
FAILED: "Request failed",
|
|
1572
|
+
CANCELLED: "Request cancelled"
|
|
1573
|
+
};
|
|
1574
|
+
const STATUS_STYLES = {
|
|
1575
|
+
REQUESTED: "border-doom-yellow/35 bg-doom-yellow/5 text-doom-yellow",
|
|
1576
|
+
CHANGING: "border-doom-magenta/45 bg-doom-magenta/10 text-doom-yellow",
|
|
1577
|
+
CHANGED: "border-doom-cyan/40 bg-doom-cyan/5 text-doom-cyan",
|
|
1578
|
+
COMPLETE: "border-doom-green/40 bg-doom-green/5 text-doom-green",
|
|
1579
|
+
FAILED: "border-doom-red/45 bg-doom-red/10 text-doom-red",
|
|
1580
|
+
CANCELLED: "border-doom-border bg-doom-panel text-doom-dim"
|
|
1581
|
+
};
|
|
1582
|
+
function requestInstruction(requestText) {
|
|
1583
|
+
const referencedContext = requestText.search(/(?:^|\s+)Referenced context "/u);
|
|
1584
|
+
return requestText.slice(0, referencedContext < 0 ? void 0 : referencedContext).replace(/^Please address the attached feedback\.\s*Request ID: \S+\s*/u, "").trim();
|
|
1585
|
+
}
|
|
1586
|
+
function documentName(path) {
|
|
1587
|
+
return path.replaceAll("\\", "/").split("/").at(-1) || path;
|
|
1588
|
+
}
|
|
1589
|
+
function anchorLabel(anchor) {
|
|
1590
|
+
switch (anchor.kind) {
|
|
1591
|
+
case "text-range": return anchor.startLine === anchor.endLine ? `Line ${anchor.startLine}` : `Lines ${anchor.startLine}–${anchor.endLine}`;
|
|
1592
|
+
case "cell": return [anchor.sheet, anchor.location].filter(Boolean).join(" · ");
|
|
1593
|
+
case "slide-element": return `Slide ${anchor.slide} · ${anchor.location}`;
|
|
1594
|
+
case "image-rect": return "Image region";
|
|
1595
|
+
case "pdf-page-rect": return `Page ${anchor.page}`;
|
|
1596
|
+
case "video-time-rect": return `Video ${Math.floor(anchor.timeSeconds / 60)}:${Math.floor(anchor.timeSeconds % 60).toString().padStart(2, "0")}`;
|
|
1597
|
+
case "story-preview-rect": return `${anchor.preview.storyPath}#${anchor.preview.storyExport}`;
|
|
1598
|
+
case "image-point": return "Image point";
|
|
1599
|
+
case "pdf-page-point": return `Page ${anchor.page} point`;
|
|
1600
|
+
case "video-time-point": return `Video ${Math.floor(anchor.timeSeconds / 60)}:${Math.floor(anchor.timeSeconds % 60).toString().padStart(2, "0")} point`;
|
|
1601
|
+
case "story-preview-point": return `${anchor.preview.storyPath}#${anchor.preview.storyExport} point`;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
function progressLabel(request) {
|
|
1605
|
+
const total = request.regions.length;
|
|
1606
|
+
const pending = request.pendingRegions?.length;
|
|
1607
|
+
if (request.status === "CHANGING" && request.currentOperation === "Agent is working on this request") return "Processing request";
|
|
1608
|
+
if (request.status === "CHANGING" && pending !== void 0) return `${total - pending} of ${total} annotations applied`;
|
|
1609
|
+
if (request.status === "CHANGED") return "Changes applied · awaiting save";
|
|
1610
|
+
if (request.status === "COMPLETE") return "Agent finished processing this request";
|
|
1611
|
+
if (request.status === "FAILED") return "Stopped with an error";
|
|
1612
|
+
if (request.status === "CANCELLED") return "Stopped before completion";
|
|
1613
|
+
return `${total} annotation${total === 1 ? "" : "s"} queued`;
|
|
1614
|
+
}
|
|
1615
|
+
function RequestRecord({ request, prominent = false }) {
|
|
1616
|
+
const instruction = requestInstruction(request.requestText);
|
|
1617
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
1618
|
+
"data-testid": prominent ? "author-active-request" : void 0,
|
|
1619
|
+
className: "space-y-3",
|
|
1620
|
+
children: [
|
|
1621
|
+
/* @__PURE__ */ jsxs("header", {
|
|
1622
|
+
className: "border-b border-doom-border-soft pb-3",
|
|
1623
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
1624
|
+
className: "text-2xs font-bold tracking-widest text-doom-faint",
|
|
1625
|
+
children: "DOCUMENT CONTEXT"
|
|
1626
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
1627
|
+
className: "mt-2 flex items-center gap-2 truncate text-base font-bold text-doom-hi",
|
|
1628
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1629
|
+
"aria-hidden": true,
|
|
1630
|
+
className: "text-doom-magenta",
|
|
1631
|
+
children: "↗"
|
|
1632
|
+
}), documentName(request.documentPath)]
|
|
1633
|
+
})]
|
|
1634
|
+
}),
|
|
1635
|
+
/* @__PURE__ */ jsx("section", {
|
|
1636
|
+
"aria-label": "Request locations",
|
|
1637
|
+
className: "rounded border border-doom-border bg-doom-panel p-3",
|
|
1638
|
+
children: /* @__PURE__ */ jsx("ol", {
|
|
1639
|
+
className: "space-y-3",
|
|
1640
|
+
children: request.regions.map((region, index) => /* @__PURE__ */ jsxs("li", {
|
|
1641
|
+
className: "space-y-2",
|
|
1642
|
+
children: [
|
|
1643
|
+
/* @__PURE__ */ jsxs("p", {
|
|
1644
|
+
className: "font-mono text-2xs font-bold uppercase tracking-wider text-doom-yellow",
|
|
1645
|
+
children: [
|
|
1646
|
+
"WHERE · ",
|
|
1647
|
+
request.regions.length > 1 ? `${index + 1} · ` : "",
|
|
1648
|
+
documentName(request.documentPath),
|
|
1649
|
+
" / ",
|
|
1650
|
+
anchorLabel(region.anchor),
|
|
1651
|
+
" · rev ",
|
|
1652
|
+
request.revision
|
|
1653
|
+
]
|
|
1654
|
+
}),
|
|
1655
|
+
region.quote ? /* @__PURE__ */ jsxs("blockquote", {
|
|
1656
|
+
className: "line-clamp-3 text-sm italic leading-relaxed text-doom-text",
|
|
1657
|
+
children: [
|
|
1658
|
+
"“",
|
|
1659
|
+
region.quote,
|
|
1660
|
+
"”"
|
|
1661
|
+
]
|
|
1662
|
+
}) : null,
|
|
1663
|
+
/* @__PURE__ */ jsx("p", {
|
|
1664
|
+
className: "text-xs leading-relaxed text-doom-dim",
|
|
1665
|
+
children: region.comment
|
|
1666
|
+
})
|
|
1667
|
+
]
|
|
1668
|
+
}, region.id))
|
|
1669
|
+
})
|
|
1670
|
+
}),
|
|
1671
|
+
/* @__PURE__ */ jsxs("section", {
|
|
1672
|
+
"data-testid": prominent ? "author-operation-card" : void 0,
|
|
1673
|
+
className: `rounded border p-3 ${STATUS_STYLES[request.status]}`,
|
|
1674
|
+
children: [
|
|
1675
|
+
/* @__PURE__ */ jsxs("p", {
|
|
1676
|
+
className: "font-mono text-xs font-bold tracking-wider",
|
|
1677
|
+
children: [
|
|
1678
|
+
"●",
|
|
1679
|
+
" ",
|
|
1680
|
+
request.status === "CHANGING" ? "WORKING" : request.status === "COMPLETE" ? "COMPLETED" : request.status === "FAILED" ? "ERROR" : request.status
|
|
1681
|
+
]
|
|
1682
|
+
}),
|
|
1683
|
+
/* @__PURE__ */ jsx("p", {
|
|
1684
|
+
className: "mt-2 text-sm leading-relaxed text-doom-hi",
|
|
1685
|
+
children: request.currentOperation || STATUS_LABELS[request.status]
|
|
1686
|
+
}),
|
|
1687
|
+
request.error ? /* @__PURE__ */ jsx("p", {
|
|
1688
|
+
className: "mt-2 text-xs leading-relaxed text-doom-red",
|
|
1689
|
+
children: request.error
|
|
1690
|
+
}) : null,
|
|
1691
|
+
request.before !== void 0 || request.after !== void 0 ? /* @__PURE__ */ jsxs("details", {
|
|
1692
|
+
className: "mt-2 border-t border-current/20 pt-2 text-xs text-doom-dim",
|
|
1693
|
+
children: [
|
|
1694
|
+
/* @__PURE__ */ jsx("summary", {
|
|
1695
|
+
className: "cursor-pointer select-none",
|
|
1696
|
+
children: "change preview"
|
|
1697
|
+
}),
|
|
1698
|
+
request.before !== void 0 ? /* @__PURE__ */ jsxs("pre", {
|
|
1699
|
+
className: "mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono",
|
|
1700
|
+
children: ["before: ", request.before]
|
|
1701
|
+
}) : null,
|
|
1702
|
+
request.after !== void 0 ? /* @__PURE__ */ jsxs("pre", {
|
|
1703
|
+
className: "mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono",
|
|
1704
|
+
children: ["after: ", request.after]
|
|
1705
|
+
}) : null
|
|
1706
|
+
]
|
|
1707
|
+
}) : null
|
|
1708
|
+
]
|
|
1709
|
+
}),
|
|
1710
|
+
/* @__PURE__ */ jsxs("section", {
|
|
1711
|
+
className: "space-y-2",
|
|
1712
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
1713
|
+
className: "text-2xs font-bold tracking-widest text-doom-faint",
|
|
1714
|
+
children: "REQUEST · HOW TO CHANGE"
|
|
1715
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1716
|
+
className: "rounded border border-doom-border bg-doom-deep p-3",
|
|
1717
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
1718
|
+
className: "whitespace-pre-wrap text-sm leading-relaxed text-doom-text",
|
|
1719
|
+
children: instruction || "No additional instruction."
|
|
1720
|
+
})
|
|
1721
|
+
})]
|
|
1722
|
+
}),
|
|
1723
|
+
/* @__PURE__ */ jsx("footer", {
|
|
1724
|
+
className: "font-mono text-2xs text-doom-dim",
|
|
1725
|
+
children: progressLabel(request)
|
|
1726
|
+
})
|
|
1727
|
+
]
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
function AuthorRequestLog({ requests }) {
|
|
1731
|
+
const latest = requests.at(-1);
|
|
1732
|
+
const earlier = requests.slice(0, -1).reverse();
|
|
1733
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
1734
|
+
"data-testid": "author-request-history",
|
|
1735
|
+
className: "space-y-3",
|
|
1736
|
+
children: [
|
|
1737
|
+
/* @__PURE__ */ jsx("h3", {
|
|
1738
|
+
className: "text-2xs font-bold tracking-widest text-doom-faint",
|
|
1739
|
+
children: "AUTHORING"
|
|
1740
|
+
}),
|
|
1741
|
+
latest === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
1742
|
+
className: "text-sm leading-relaxed text-doom-dim",
|
|
1743
|
+
children: "No requests or changes yet."
|
|
1744
|
+
}) : /* @__PURE__ */ jsx(RequestRecord, {
|
|
1745
|
+
request: latest,
|
|
1746
|
+
prominent: true
|
|
1747
|
+
}),
|
|
1748
|
+
earlier.length > 0 ? /* @__PURE__ */ jsxs("details", {
|
|
1749
|
+
className: "border-t border-doom-border-soft pt-3",
|
|
1750
|
+
"data-testid": "author-earlier-requests",
|
|
1751
|
+
children: [/* @__PURE__ */ jsxs("summary", {
|
|
1752
|
+
className: "cursor-pointer select-none text-2xs font-bold tracking-wider text-doom-faint",
|
|
1753
|
+
children: [
|
|
1754
|
+
"EARLIER REQUESTS (",
|
|
1755
|
+
earlier.length,
|
|
1756
|
+
")"
|
|
1757
|
+
]
|
|
1758
|
+
}), /* @__PURE__ */ jsx("ol", {
|
|
1759
|
+
className: "mt-3 space-y-5",
|
|
1760
|
+
children: earlier.map((request) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(RequestRecord, { request }) }, request.id))
|
|
1761
|
+
})]
|
|
1762
|
+
}) : null
|
|
1763
|
+
]
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
//#endregion
|
|
1767
|
+
//#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorToolPalette.tsx
|
|
1768
|
+
function AuthorToolPalette({ sessionId, kind, activeTool }) {
|
|
1769
|
+
const [error, setError] = useState();
|
|
1770
|
+
const format = (style) => {
|
|
1771
|
+
const selected = authorSessionWorkspace(sessionId).candidate;
|
|
1772
|
+
const document = selected && authorDocument(sessionId, selected.documentPath);
|
|
1773
|
+
if (!selected || !document || selected.anchor.kind !== "text-range" || selected.revision !== document.version || selected.sourceSha256 !== document.sourceSha256) {
|
|
1774
|
+
setError("Select text in the document first.");
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
const { startOffset, endOffset } = selected.anchor;
|
|
1778
|
+
const content = document.content ?? "";
|
|
1779
|
+
const text = content.slice(startOffset, endOffset);
|
|
1780
|
+
const replacement = style === "Bold" ? `**${text}**` : style === "Link" ? `[${text}](https://)` : text.split("\n").map((line) => `${style === "Heading" ? "## " : "- "}${line}`).join("\n");
|
|
1781
|
+
reviseAuthorDocument(sessionId, document.path, content.slice(0, startOffset) + replacement + content.slice(endOffset));
|
|
1782
|
+
setAuthorRegionCandidate(sessionId, void 0);
|
|
1783
|
+
setError(void 0);
|
|
1784
|
+
};
|
|
1785
|
+
const tools = [
|
|
1786
|
+
{
|
|
1787
|
+
label: "Region",
|
|
1788
|
+
glyph: "⌗",
|
|
1789
|
+
action: () => setAuthorToolMode(sessionId, activeTool === "mark" ? "select" : "mark"),
|
|
1790
|
+
active: activeTool === "mark"
|
|
1791
|
+
},
|
|
1792
|
+
...kind === "markdown" ? [
|
|
1793
|
+
{
|
|
1794
|
+
label: "Bold",
|
|
1795
|
+
glyph: "B",
|
|
1796
|
+
action: () => format("Bold")
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
label: "Heading",
|
|
1800
|
+
glyph: "H",
|
|
1801
|
+
action: () => format("Heading")
|
|
1802
|
+
},
|
|
1803
|
+
{
|
|
1804
|
+
label: "Link",
|
|
1805
|
+
glyph: "↗",
|
|
1806
|
+
action: () => format("Link")
|
|
1807
|
+
},
|
|
1808
|
+
{
|
|
1809
|
+
label: "List",
|
|
1810
|
+
glyph: "≡",
|
|
1811
|
+
action: () => format("List")
|
|
1812
|
+
}
|
|
1813
|
+
] : [],
|
|
1814
|
+
{
|
|
1815
|
+
label: "Comment",
|
|
1816
|
+
glyph: "●",
|
|
1817
|
+
action: () => setAuthorToolMode(sessionId, activeTool === "comment" ? "select" : "comment"),
|
|
1818
|
+
active: activeTool === "comment"
|
|
1819
|
+
}
|
|
1820
|
+
];
|
|
1821
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
1822
|
+
"data-testid": "author-tool-palette",
|
|
1823
|
+
className: "space-y-2 border-b border-doom-border-soft pb-3",
|
|
1824
|
+
children: [
|
|
1825
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1826
|
+
className: "flex justify-between text-sm text-doom-dim",
|
|
1827
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
1828
|
+
className: "font-bold tracking-widest",
|
|
1829
|
+
children: "TOOLS"
|
|
1830
|
+
}), /* @__PURE__ */ jsx("span", { children: "focused tab" })]
|
|
1831
|
+
}),
|
|
1832
|
+
/* @__PURE__ */ jsx("div", {
|
|
1833
|
+
className: "grid grid-cols-3 gap-1.5",
|
|
1834
|
+
children: tools.map((tool) => /* @__PURE__ */ jsxs(Button, {
|
|
1835
|
+
size: "md",
|
|
1836
|
+
variant: "outline",
|
|
1837
|
+
"aria-label": tool.label === "Region" ? "mark region" : tool.label,
|
|
1838
|
+
"aria-pressed": tool.active,
|
|
1839
|
+
onClick: tool.action,
|
|
1840
|
+
className: `min-h-11 min-w-11 gap-1.5 rounded border text-base [@media(pointer:fine)]:min-h-8 sm:text-sm ${tool.active ? "border-doom-red bg-doom-red/10 text-doom-red hover:bg-doom-red/15" : "border-doom-border bg-doom-panel text-doom-dim"}`,
|
|
1841
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1842
|
+
"aria-hidden": "true",
|
|
1843
|
+
children: tool.glyph
|
|
1844
|
+
}), tool.label]
|
|
1845
|
+
}, tool.label))
|
|
1846
|
+
}),
|
|
1847
|
+
/* @__PURE__ */ jsx("p", {
|
|
1848
|
+
className: "text-base leading-normal text-doom-dim sm:text-sm",
|
|
1849
|
+
children: activeTool === "mark" ? "Drag over the document to mark a region." : activeTool === "comment" ? "Click the document to place a comment pin." : "Choose Region to drag an area, or Comment to place a point."
|
|
1850
|
+
}),
|
|
1851
|
+
error ? /* @__PURE__ */ jsx("output", {
|
|
1852
|
+
className: "block text-base text-doom-red",
|
|
1853
|
+
children: error
|
|
1854
|
+
}) : null
|
|
1855
|
+
]
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
//#endregion
|
|
1859
|
+
//#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorPanel.tsx
|
|
1860
|
+
function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses, renderSlot, renderSessionActivity }) {
|
|
1861
|
+
const [captureStatus, setCaptureStatus] = useState();
|
|
1862
|
+
const [capturing, setCapturing] = useState(false);
|
|
1863
|
+
const documents = useStore(authorWorkspace.store, (state) => {
|
|
1864
|
+
if (sessionId === null) return [];
|
|
1865
|
+
const prefix = `${sessionId}\n`;
|
|
1866
|
+
return Object.entries(state.documents).filter(([key]) => key.startsWith(prefix)).map(([, document]) => document);
|
|
1867
|
+
});
|
|
1868
|
+
const workspace = useStore(authorWorkspace.store, (state) => sessionId === null ? void 0 : state.sessions[sessionId]);
|
|
1869
|
+
const grid = useStore(authorGrid.store, (state) => sessionId === null ? void 0 : state.sessions[sessionId]);
|
|
1870
|
+
const focused = documents.find((document) => document.path === workspace?.focusedDocument?.path);
|
|
1871
|
+
const activity = sessionId === null ? null : renderSessionActivity?.() ?? null;
|
|
1872
|
+
if (!activeMinorModes?.includes("author")) return null;
|
|
1873
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
1874
|
+
"data-testid": "author-panel",
|
|
1875
|
+
className: "flex min-h-0 flex-1 flex-col overflow-hidden",
|
|
1876
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1877
|
+
className: "min-h-0 flex-1 overflow-y-auto p-3",
|
|
1878
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1879
|
+
className: "flex flex-col gap-2",
|
|
1880
|
+
children: [
|
|
1881
|
+
/* @__PURE__ */ jsx("div", {
|
|
1882
|
+
"data-testid": "author-preview-providers",
|
|
1883
|
+
children: renderSlot("author.preview-provider")
|
|
1884
|
+
}),
|
|
1885
|
+
sessionId !== null && workspace !== void 0 && focused !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1886
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1887
|
+
className: "text-base font-semibold text-doom-text",
|
|
1888
|
+
children: "Annotations"
|
|
1889
|
+
}),
|
|
1890
|
+
/* @__PURE__ */ jsx("p", {
|
|
1891
|
+
className: "text-base leading-normal text-doom-dim sm:text-sm",
|
|
1892
|
+
children: "Drag a region or place a point, add feedback, then submit here. Requests queue while the agent is working."
|
|
1893
|
+
}),
|
|
1894
|
+
focused.kind === "video" ? /* @__PURE__ */ jsx("p", {
|
|
1895
|
+
className: "text-base text-doom-dim sm:text-sm",
|
|
1896
|
+
children: "Annotations reference video frames. They do not save changes to the source video."
|
|
1897
|
+
}) : null,
|
|
1898
|
+
/* @__PURE__ */ jsx(AuthorToolPalette, {
|
|
1899
|
+
sessionId,
|
|
1900
|
+
kind: focused.kind,
|
|
1901
|
+
activeTool: workspace.activeTool
|
|
1902
|
+
}),
|
|
1903
|
+
autonomousVoiceGridVisible(statuses) && grid !== void 0 ? /* @__PURE__ */ jsxs("div", {
|
|
1904
|
+
"data-testid": "author-grid-snapshot",
|
|
1905
|
+
className: "rounded border border-doom-red/40 bg-doom-red/5 p-2 text-base text-doom-dim",
|
|
1906
|
+
children: [/* @__PURE__ */ jsx("strong", {
|
|
1907
|
+
className: "text-doom-red",
|
|
1908
|
+
children: "VOICE GRID A1–H8"
|
|
1909
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
1910
|
+
className: "mt-1 truncate",
|
|
1911
|
+
children: ["token ", grid.geometryToken]
|
|
1912
|
+
})]
|
|
1913
|
+
}) : null,
|
|
1914
|
+
/* @__PURE__ */ jsx(AuthorRegionDrafts, {
|
|
1915
|
+
sessionId,
|
|
1916
|
+
workspace
|
|
1917
|
+
}, focused.path),
|
|
1918
|
+
workspace.annotations.length > 0 ? /* @__PURE__ */ jsxs("div", {
|
|
1919
|
+
className: "space-y-1.5 border-b border-doom-border-soft pb-3",
|
|
1920
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
1921
|
+
className: "min-h-11 min-w-11 w-full text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
1922
|
+
variant: "outline",
|
|
1923
|
+
"data-testid": "author-attach-capture",
|
|
1924
|
+
disabled: capturing || workspace.candidate !== void 0 || submitCapture === void 0,
|
|
1925
|
+
onClick: async () => {
|
|
1926
|
+
if (capturing || workspace.candidate || submitCapture === void 0) return;
|
|
1927
|
+
setCapturing(true);
|
|
1928
|
+
setCaptureStatus("Submitting annotations…");
|
|
1929
|
+
try {
|
|
1930
|
+
const packet = createAuthorCapturePacket(crypto.randomUUID(), Date.now(), focused, workspace.annotations);
|
|
1931
|
+
await submitCapture({
|
|
1932
|
+
...await multiRegionCaptureProvider(workspace.annotations).capture(),
|
|
1933
|
+
context: authorCaptureContext(packet)
|
|
1934
|
+
});
|
|
1935
|
+
setCaptureStatus("Request submitted. You can keep annotating here.");
|
|
1936
|
+
} catch (reason) {
|
|
1937
|
+
setCaptureStatus(reason instanceof Error ? reason.message : String(reason));
|
|
1938
|
+
} finally {
|
|
1939
|
+
setCapturing(false);
|
|
1940
|
+
}
|
|
1941
|
+
},
|
|
1942
|
+
children: capturing ? "Submitting…" : `Submit ${workspace.annotations.length} annotation${workspace.annotations.length === 1 ? "" : "s"}`
|
|
1943
|
+
}), workspace.candidate ? /* @__PURE__ */ jsx("p", {
|
|
1944
|
+
className: "text-base text-doom-dim sm:text-sm",
|
|
1945
|
+
children: "Add or discard the current annotation before submitting."
|
|
1946
|
+
}) : null]
|
|
1947
|
+
}) : null,
|
|
1948
|
+
submitCapture === void 0 ? /* @__PURE__ */ jsx("output", {
|
|
1949
|
+
className: "text-sm text-doom-red",
|
|
1950
|
+
children: "Submission unavailable. Reload to update the app."
|
|
1951
|
+
}) : null,
|
|
1952
|
+
captureStatus ? /* @__PURE__ */ jsx("output", {
|
|
1953
|
+
className: "block text-sm text-doom-dim",
|
|
1954
|
+
children: captureStatus
|
|
1955
|
+
}) : null
|
|
1956
|
+
] }) : null,
|
|
1957
|
+
/* @__PURE__ */ jsx(AuthorRequestLog, { requests: workspace?.requests ?? [] })
|
|
1958
|
+
]
|
|
1959
|
+
})
|
|
1960
|
+
}), activity === null ? null : /* @__PURE__ */ jsx("footer", {
|
|
1961
|
+
"data-testid": "author-session-activity",
|
|
1962
|
+
className: "shrink-0 border-t border-doom-border bg-doom-rail p-3",
|
|
1963
|
+
children: activity
|
|
1964
|
+
})]
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
//#endregion
|
|
1968
|
+
//#region src/extensions/workspaces/sessions/(frontend)/dock/authoring.web.tsx
|
|
1969
|
+
var authoring_web_default = defineDockFace({
|
|
1970
|
+
id: "authoring",
|
|
1971
|
+
label: "authoring",
|
|
1972
|
+
order: 30,
|
|
1973
|
+
autoSelect: true,
|
|
1974
|
+
panel: AuthorPanel,
|
|
1975
|
+
requiredMinorMode: "author",
|
|
1976
|
+
visibility: {
|
|
1977
|
+
subscribe() {
|
|
1978
|
+
return () => void 0;
|
|
1979
|
+
},
|
|
1980
|
+
isVisible(sessionId) {
|
|
1981
|
+
return sessionId !== null;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
//#endregion
|
|
1986
|
+
//#region src/types/authorPreview.ts
|
|
1987
|
+
function parseAuthorPreviewAction(input) {
|
|
1988
|
+
if (typeof input !== "object" || input === null) return null;
|
|
1989
|
+
const candidate = input;
|
|
1990
|
+
if (candidate.version !== 1 || typeof candidate.label !== "string" || candidate.label.trim() === "") return null;
|
|
1991
|
+
if (candidate.detail !== void 0 && typeof candidate.detail !== "string") return null;
|
|
1992
|
+
if (typeof candidate.createTab !== "function") return null;
|
|
1993
|
+
if (candidate.supportsSource !== void 0 && typeof candidate.supportsSource !== "function") return null;
|
|
1994
|
+
if (candidate.embeddedPanel !== void 0 && typeof candidate.embeddedPanel !== "function") return null;
|
|
1995
|
+
return candidate;
|
|
1996
|
+
}
|
|
1997
|
+
const authorPreviewActionSlot = {
|
|
1998
|
+
slot: "author.preview-action",
|
|
1999
|
+
parse: parseAuthorPreviewAction
|
|
2000
|
+
};
|
|
2001
|
+
//#endregion
|
|
2002
|
+
//#region src/types/apiRoutes.ts
|
|
2003
|
+
/**
|
|
2004
|
+
* This package's routes, as data.
|
|
2005
|
+
*
|
|
2006
|
+
* No scope and no base path. The build reads both off
|
|
2007
|
+
* `src/extensions/workspaces/sessions/(backend)/api/author/`, so the mount is
|
|
2008
|
+
* stated once, by the folder that creates it. The browser used to restate it:
|
|
2009
|
+
* `authorStateUrl` built one URL from `API_BASE_PATH` and the document helper
|
|
2010
|
+
* two files away spelled `author` out again as a literal, so the two could
|
|
2011
|
+
* disagree and nothing would say so.
|
|
2012
|
+
*
|
|
2013
|
+
* The order below is the order the three Hono apps register in: `/state` in
|
|
2014
|
+
* `authorApi`, the eight bridge routes in `authorBridgeApi`, the three document
|
|
2015
|
+
* routes in `authorDocumentApi`. A reviewer can read the two side by side.
|
|
2016
|
+
*
|
|
2017
|
+
* Response types are named only where a caller reads the body. The bridge
|
|
2018
|
+
* routes are called by the hub's author channel over `requestSessionApi`, which
|
|
2019
|
+
* has its own message vocabulary and never sees this client.
|
|
2020
|
+
*/
|
|
2021
|
+
var apiRoutes_default = defineApiRoutes({
|
|
2022
|
+
state: {
|
|
2023
|
+
method: "GET",
|
|
2024
|
+
path: "/state",
|
|
2025
|
+
response: apiResponse()
|
|
2026
|
+
},
|
|
2027
|
+
bridgeRegister: {
|
|
2028
|
+
method: "POST",
|
|
2029
|
+
path: "/bridge/register"
|
|
2030
|
+
},
|
|
2031
|
+
bridgeCatalog: {
|
|
2032
|
+
method: "POST",
|
|
2033
|
+
path: "/bridge/catalog"
|
|
2034
|
+
},
|
|
2035
|
+
/** Long-polls for the next capability request, so it settles when one arrives. */
|
|
2036
|
+
bridgeNext: {
|
|
2037
|
+
method: "POST",
|
|
2038
|
+
path: "/bridge/next"
|
|
2039
|
+
},
|
|
2040
|
+
bridgeResult: {
|
|
2041
|
+
method: "POST",
|
|
2042
|
+
path: "/bridge/result"
|
|
2043
|
+
},
|
|
2044
|
+
bridgeCancelled: {
|
|
2045
|
+
method: "POST",
|
|
2046
|
+
path: "/bridge/cancelled"
|
|
2047
|
+
},
|
|
2048
|
+
bridgeDisconnect: {
|
|
2049
|
+
method: "POST",
|
|
2050
|
+
path: "/bridge/disconnect"
|
|
2051
|
+
},
|
|
2052
|
+
bridgeDescribe: {
|
|
2053
|
+
method: "GET",
|
|
2054
|
+
path: "/bridge/describe"
|
|
2055
|
+
},
|
|
2056
|
+
bridgeInvoke: {
|
|
2057
|
+
method: "POST",
|
|
2058
|
+
path: "/bridge/invoke"
|
|
2059
|
+
},
|
|
2060
|
+
/**
|
|
2061
|
+
* Parses one structured document.
|
|
2062
|
+
*
|
|
2063
|
+
* Answers a byte-length summary instead when the request names no format,
|
|
2064
|
+
* which is the agent-side tool path; the page always names one.
|
|
2065
|
+
*/
|
|
2066
|
+
documentsOpen: {
|
|
2067
|
+
method: "POST",
|
|
2068
|
+
path: "/documents/open",
|
|
2069
|
+
query: ["session"],
|
|
2070
|
+
response: apiResponse()
|
|
2071
|
+
},
|
|
2072
|
+
documentsPreflight: {
|
|
2073
|
+
method: "POST",
|
|
2074
|
+
path: "/documents/preflight",
|
|
2075
|
+
query: ["session"],
|
|
2076
|
+
response: apiResponse()
|
|
2077
|
+
},
|
|
2078
|
+
documentsSerialize: {
|
|
2079
|
+
method: "POST",
|
|
2080
|
+
path: "/documents/serialize",
|
|
2081
|
+
query: ["session"],
|
|
2082
|
+
response: apiResponse()
|
|
2083
|
+
},
|
|
2084
|
+
/**
|
|
2085
|
+
* The host's own bytes route, not this package's.
|
|
2086
|
+
*
|
|
2087
|
+
* `host: true` hangs it off the session root rather than under `/plugins`,
|
|
2088
|
+
* which is how the cockpit already serves it. Author reads a document's
|
|
2089
|
+
* bytes and its digest through it, and writes the edited bytes back to the
|
|
2090
|
+
* same path, so both stay raw transfers rather than calls: the body is the
|
|
2091
|
+
* file and the digest travels in a header.
|
|
2092
|
+
*/
|
|
2093
|
+
file: {
|
|
2094
|
+
method: "GET",
|
|
2095
|
+
path: "/file",
|
|
2096
|
+
query: ["path"],
|
|
2097
|
+
host: true
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
const api = createApiClient(apiRoutes_default, {
|
|
2101
|
+
scopes: ["session"],
|
|
2102
|
+
basePath: "author",
|
|
2103
|
+
transport: (input, init) => sealedTransport.fetch(input, init),
|
|
2104
|
+
sessionAddress: sessionApiAddress
|
|
2105
|
+
});
|
|
2106
|
+
//#endregion
|
|
2107
|
+
//#region src/types/authorApi.ts
|
|
2108
|
+
/** Query parameter naming the session a document request belongs to. */
|
|
2109
|
+
const SESSION_QUERY_PARAM = "session";
|
|
2110
|
+
//#endregion
|
|
2111
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorFiles.ts
|
|
2112
|
+
const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2113
|
+
"avif",
|
|
2114
|
+
"bmp",
|
|
2115
|
+
"gif",
|
|
2116
|
+
"jpeg",
|
|
2117
|
+
"jpg",
|
|
2118
|
+
"png",
|
|
2119
|
+
"svg",
|
|
2120
|
+
"webp"
|
|
2121
|
+
]);
|
|
2122
|
+
const VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2123
|
+
"m4v",
|
|
2124
|
+
"mov",
|
|
2125
|
+
"mp4",
|
|
2126
|
+
"webm"
|
|
2127
|
+
]);
|
|
2128
|
+
function structuredFormat(path) {
|
|
2129
|
+
const lower = path.toLowerCase();
|
|
2130
|
+
if (lower.endsWith(".slides.md")) return "markdown-slides";
|
|
2131
|
+
if (lower.endsWith(".csv")) return "csv";
|
|
2132
|
+
if (lower.endsWith(".pptx")) return "pptx";
|
|
2133
|
+
if (lower.endsWith(".xlsx")) return "xlsx";
|
|
2134
|
+
}
|
|
2135
|
+
function authorKindForPath(path) {
|
|
2136
|
+
const lower = path.toLowerCase();
|
|
2137
|
+
if (/\.stories\.(?:js|jsx|ts|tsx)$/u.test(lower)) return "story-preview";
|
|
2138
|
+
const format = structuredFormat(path);
|
|
2139
|
+
if (format === "markdown-slides") return "slides";
|
|
2140
|
+
if (format !== void 0) return format;
|
|
2141
|
+
const extension = lower.split(".").at(-1) ?? "";
|
|
2142
|
+
if (extension === "md") return "markdown";
|
|
2143
|
+
if (IMAGE_EXTENSIONS.has(extension)) return "image";
|
|
2144
|
+
if (VIDEO_EXTENSIONS.has(extension)) return "video";
|
|
2145
|
+
if (extension === "pdf") return "pdf";
|
|
2146
|
+
if ([
|
|
2147
|
+
"txt",
|
|
2148
|
+
"json",
|
|
2149
|
+
"yaml",
|
|
2150
|
+
"yml",
|
|
2151
|
+
"xml",
|
|
2152
|
+
"html",
|
|
2153
|
+
"css",
|
|
2154
|
+
"js",
|
|
2155
|
+
"ts",
|
|
2156
|
+
"tsx",
|
|
2157
|
+
"jsx"
|
|
2158
|
+
].includes(extension)) return "text";
|
|
2159
|
+
return "opaque";
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Where a document's bytes are read from and written back to.
|
|
2163
|
+
*
|
|
2164
|
+
* This is the host's route, not this package's, which is why the route table
|
|
2165
|
+
* marks it `host: true` and it lands beside the session rather than under
|
|
2166
|
+
* `/plugins`. It stays a raw transfer rather than a call: the body is the file
|
|
2167
|
+
* itself, and the digest travels in a header on both legs.
|
|
2168
|
+
*/
|
|
2169
|
+
function authorSessionFileUrl(sessionId, path) {
|
|
2170
|
+
return api.session(sessionId).file.url({ query: { path } });
|
|
2171
|
+
}
|
|
2172
|
+
/** One document route, with the refusal the reader is shown: the route's own words, or its status. */
|
|
2173
|
+
async function documentRequest(method, sessionId, body, signal) {
|
|
2174
|
+
const result = await method({
|
|
2175
|
+
body,
|
|
2176
|
+
query: { [SESSION_QUERY_PARAM]: sessionId },
|
|
2177
|
+
...signal === void 0 ? {} : { signal }
|
|
2178
|
+
});
|
|
2179
|
+
if (!result.ok) throw new Error(result.error === "" ? `Author document API failed (${result.status})` : result.error);
|
|
2180
|
+
return result.data;
|
|
2181
|
+
}
|
|
2182
|
+
async function loadAuthorDocument(sessionId, path, signal) {
|
|
2183
|
+
const kind = authorKindForPath(path);
|
|
2184
|
+
const url = authorSessionFileUrl(sessionId, path);
|
|
2185
|
+
const response = await sealedTransport.fetch(url, { signal });
|
|
2186
|
+
if (!response.ok) throw new Error(`Author could not open ${path} (${response.status})`);
|
|
2187
|
+
const sourceSha256 = response.headers.get("X-File-SHA256") ?? void 0;
|
|
2188
|
+
const format = structuredFormat(path);
|
|
2189
|
+
if (format !== void 0) {
|
|
2190
|
+
const parsed = await documentRequest(api.session(sessionId).documentsOpen, sessionId, {
|
|
2191
|
+
path,
|
|
2192
|
+
format
|
|
2193
|
+
}, signal);
|
|
2194
|
+
const parsedDigest = parsed.manifest?.sourceDigest;
|
|
2195
|
+
if (sourceSha256 !== void 0 && parsedDigest !== void 0 && sourceSha256 !== parsedDigest) throw new Error("Document source changed while opening. Reopen the document.");
|
|
2196
|
+
const csvDialect = format === "csv" ? csvDialectFromMetadata(parsed.manifest?.metadata) : void 0;
|
|
2197
|
+
return {
|
|
2198
|
+
path,
|
|
2199
|
+
kind,
|
|
2200
|
+
title: path.split("/").at(-1) ?? path,
|
|
2201
|
+
structuredFormat: format,
|
|
2202
|
+
fragments: parsed.fragments,
|
|
2203
|
+
...csvDialect === void 0 ? {} : { csvDialect },
|
|
2204
|
+
originalFragments: parsed.fragments.map((fragment) => ({ ...fragment })),
|
|
2205
|
+
...sourceSha256 === void 0 ? {} : { sourceSha256 }
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
const mediaUrl = kind === "image" || kind === "video" || kind === "pdf" || kind === "opaque" ? url : void 0;
|
|
2209
|
+
const content = kind === "text" || kind === "markdown" || kind === "story-preview" ? await response.text() : void 0;
|
|
2210
|
+
return {
|
|
2211
|
+
path,
|
|
2212
|
+
kind,
|
|
2213
|
+
title: path.split("/").at(-1) ?? path,
|
|
2214
|
+
...content === void 0 ? {} : { content },
|
|
2215
|
+
...mediaUrl === void 0 ? {} : { mediaUrl },
|
|
2216
|
+
...sourceSha256 === void 0 ? {} : { sourceSha256 }
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
function operationsFor(document) {
|
|
2220
|
+
const originals = new Map((document.originalFragments ?? []).map((fragment) => [fragment.id, fragment.text]));
|
|
2221
|
+
return (document.fragments ?? []).filter((fragment) => !fragment.readOnly && originals.get(fragment.id) !== fragment.text).map((fragment) => ({
|
|
2222
|
+
fragmentId: fragment.id,
|
|
2223
|
+
replacement: fragment.text
|
|
2224
|
+
}));
|
|
2225
|
+
}
|
|
2226
|
+
function decodeBase64(value) {
|
|
2227
|
+
if (!/^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$/.test(value)) throw new Error("Invalid serialized document bytes.");
|
|
2228
|
+
const binary = atob(value);
|
|
2229
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
2230
|
+
}
|
|
2231
|
+
function imageMimeType(path) {
|
|
2232
|
+
const extension = path.split(".").at(-1)?.toLowerCase();
|
|
2233
|
+
if (extension === "png") return "image/png";
|
|
2234
|
+
if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
|
|
2235
|
+
if (extension === "webp") return "image/webp";
|
|
2236
|
+
}
|
|
2237
|
+
async function croppedImageBytes(documentInput, signal) {
|
|
2238
|
+
const { crop, mediaUrl } = documentInput;
|
|
2239
|
+
if (crop === void 0 || mediaUrl === void 0) throw new Error("This Author image has no crop source.");
|
|
2240
|
+
const mimeType = imageMimeType(documentInput.path);
|
|
2241
|
+
if (mimeType === void 0) throw new Error("This image format cannot preserve its encoding after crop.");
|
|
2242
|
+
const response = await sealedTransport.fetch(mediaUrl, {
|
|
2243
|
+
signal,
|
|
2244
|
+
cache: "no-store"
|
|
2245
|
+
});
|
|
2246
|
+
if (!response.ok) throw new Error(`Author could not read the image before saving (${response.status})`);
|
|
2247
|
+
const bitmap = await createImageBitmap(await response.blob());
|
|
2248
|
+
try {
|
|
2249
|
+
const sourceX = Math.round(crop.x * bitmap.width);
|
|
2250
|
+
const sourceY = Math.round(crop.y * bitmap.height);
|
|
2251
|
+
const sourceWidth = Math.max(1, Math.round(crop.width * bitmap.width));
|
|
2252
|
+
const sourceHeight = Math.max(1, Math.round(crop.height * bitmap.height));
|
|
2253
|
+
const canvas = window.document.createElement("canvas");
|
|
2254
|
+
canvas.width = sourceWidth;
|
|
2255
|
+
canvas.height = sourceHeight;
|
|
2256
|
+
const context = canvas.getContext("2d");
|
|
2257
|
+
if (context === null) throw new Error("Canvas is unavailable for image crop.");
|
|
2258
|
+
context.drawImage(bitmap, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, sourceWidth, sourceHeight);
|
|
2259
|
+
const blob = await new Promise((resolve) => canvas.toBlob(resolve, mimeType));
|
|
2260
|
+
if (blob === null || blob.type !== mimeType) throw new Error("The browser cannot encode this cropped image format.");
|
|
2261
|
+
return blob;
|
|
2262
|
+
} finally {
|
|
2263
|
+
bitmap.close();
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
async function bytesToSave(sessionId, document, signal) {
|
|
2267
|
+
if (document.kind === "text" || document.kind === "markdown" || document.kind === "story-preview") return document.content ?? "";
|
|
2268
|
+
if (document.kind === "image" && document.crop !== void 0) return await croppedImageBytes(document, signal);
|
|
2269
|
+
if (document.structuredFormat === void 0) throw new Error("This Author view cannot be saved.");
|
|
2270
|
+
const operations = operationsFor(document);
|
|
2271
|
+
const request = {
|
|
2272
|
+
path: document.path,
|
|
2273
|
+
format: document.structuredFormat,
|
|
2274
|
+
operations,
|
|
2275
|
+
...document.csvDialect === void 0 ? {} : { csvDialect: document.csvDialect }
|
|
2276
|
+
};
|
|
2277
|
+
const preflight = await documentRequest(api.session(sessionId).documentsPreflight, sessionId, request, signal);
|
|
2278
|
+
if (preflight.sourceDigest !== void 0 && preflight.sourceDigest !== document.sourceSha256) throw new Error("Document source changed before saving. Reopen the document.");
|
|
2279
|
+
if (!preflight.accepted) throw new Error(preflight.issues.map((issue) => issue.message).join("\n") || "Author preflight rejected the edit.");
|
|
2280
|
+
const serialized = await documentRequest(api.session(sessionId).documentsSerialize, sessionId, {
|
|
2281
|
+
...request,
|
|
2282
|
+
preflightDigest: preflight.digest
|
|
2283
|
+
}, signal);
|
|
2284
|
+
if (serialized.encoding !== "base64") throw new Error("Author serialize returned an unsupported encoding.");
|
|
2285
|
+
const bytes = decodeBase64(serialized.bytes);
|
|
2286
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2287
|
+
}
|
|
2288
|
+
async function saveAuthorDocument(sessionId, document, signal) {
|
|
2289
|
+
if (document.sourceSha256 === void 0) throw new Error("This Author view has no source digest.");
|
|
2290
|
+
const response = await sealedTransport.fetch(authorSessionFileUrl(sessionId, document.path), {
|
|
2291
|
+
method: "PUT",
|
|
2292
|
+
headers: {
|
|
2293
|
+
"Content-Type": "application/octet-stream",
|
|
2294
|
+
"X-Expected-SHA256": document.sourceSha256
|
|
2295
|
+
},
|
|
2296
|
+
body: await bytesToSave(sessionId, document, signal),
|
|
2297
|
+
signal
|
|
2298
|
+
});
|
|
2299
|
+
if (!response.ok) throw new Error(`Author save failed (${response.status})`);
|
|
2300
|
+
const sha256 = response.headers.get("X-File-SHA256");
|
|
2301
|
+
if (sha256 === null || !/^[a-f0-9]{64}$/.test(sha256)) throw new Error("Author save returned an invalid digest.");
|
|
2302
|
+
return sha256;
|
|
2303
|
+
}
|
|
2304
|
+
function csvDialectFromMetadata(metadata) {
|
|
2305
|
+
if (metadata === void 0) return void 0;
|
|
2306
|
+
const { delimiter, quote, recordDelimiter } = metadata;
|
|
2307
|
+
if (delimiter !== "," && delimiter !== ";" && delimiter !== " " && delimiter !== "|" || quote !== "\"" && quote !== "'" || recordDelimiter !== "\n" && recordDelimiter !== "\r\n") throw new Error("Author document API returned an invalid CSV dialect.");
|
|
2308
|
+
return {
|
|
2309
|
+
delimiter,
|
|
2310
|
+
quote,
|
|
2311
|
+
recordDelimiter
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
//#endregion
|
|
2315
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorGridTools.ts
|
|
2316
|
+
const stringProperty = {
|
|
2317
|
+
type: "string",
|
|
2318
|
+
minLength: 1
|
|
2319
|
+
};
|
|
2320
|
+
function inputRecord(input) {
|
|
2321
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) throw new Error("Expected an object.");
|
|
2322
|
+
return input;
|
|
2323
|
+
}
|
|
2324
|
+
function requiredString(value, name) {
|
|
2325
|
+
if (typeof value[name] !== "string" || value[name] === "") throw new Error(`${name} must be a non-empty string.`);
|
|
2326
|
+
return value[name];
|
|
2327
|
+
}
|
|
2328
|
+
function resolvedAnchor(sessionId, path, kind, cell) {
|
|
2329
|
+
if (authorDocument(sessionId, path) === void 0) throw new Error("STALE_DOCUMENT: The focused Author document is no longer open.");
|
|
2330
|
+
if (kind === "pdf" || kind === "video" || kind === "opaque") throw new Error("UNSUPPORTED_GRID: PDF and video coordinates are capture-only and require a visible native selection.");
|
|
2331
|
+
return resolveAuthorGridNativeAnchor(sessionId, cell);
|
|
2332
|
+
}
|
|
2333
|
+
function checkedRegion(sessionId, path, value) {
|
|
2334
|
+
const document = authorDocument(sessionId, path);
|
|
2335
|
+
if (document === void 0) throw new Error("STALE_DOCUMENT: The Author document is no longer open.");
|
|
2336
|
+
if (document.savingVersion !== void 0) throw new Error("SAVE_IN_PROGRESS: Wait for the current Author save to finish.");
|
|
2337
|
+
const expectedRevision = value.expectedRevision;
|
|
2338
|
+
const expectedSourceSha256 = requiredString(value, "expectedSourceSha256");
|
|
2339
|
+
if (expectedRevision !== document.version || expectedSourceSha256 !== document.sourceSha256) throw new Error("STALE_DOCUMENT: The Author revision or source digest changed.");
|
|
2340
|
+
const regionId = requiredString(value, "regionId");
|
|
2341
|
+
const workspace = authorSessionWorkspace(sessionId);
|
|
2342
|
+
const region = workspace.regions.find((candidate) => candidate.id === regionId) ?? workspace.requests.filter((request) => request.status === "REQUESTED" || request.status === "CHANGING").flatMap((request) => request.pendingRegions ?? request.regions).find((candidate) => candidate.id === regionId);
|
|
2343
|
+
if (region === void 0 || region.documentPath !== path) throw new Error("STALE_REGION: The resolved Author region is unavailable.");
|
|
2344
|
+
if (region.revision !== document.version || region.sourceSha256 !== document.sourceSha256) throw new Error("STALE_REGION: The resolved Author region no longer matches the document.");
|
|
2345
|
+
return {
|
|
2346
|
+
document,
|
|
2347
|
+
region
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
function gridTools(sessionId, path, kind) {
|
|
2351
|
+
return [{
|
|
2352
|
+
name: "author_describe_grid",
|
|
2353
|
+
label: "Describe grid",
|
|
2354
|
+
description: "Describe the current A1 through H8 grid and return its geometry token.",
|
|
2355
|
+
inputSchema: {
|
|
2356
|
+
type: "object",
|
|
2357
|
+
additionalProperties: false,
|
|
2358
|
+
properties: {}
|
|
2359
|
+
},
|
|
2360
|
+
execute: async () => {
|
|
2361
|
+
const geometry = authorGridGeometry(sessionId);
|
|
2362
|
+
const document = authorDocument(sessionId, path);
|
|
2363
|
+
if (geometry === void 0 || document === void 0 || geometry.documentPath !== path || geometry.revision !== document.version) throw new Error("STALE_GRID: The visible Author grid is unavailable.");
|
|
2364
|
+
return {
|
|
2365
|
+
columns: "A-H",
|
|
2366
|
+
rows: "1-8",
|
|
2367
|
+
...geometry
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
}, {
|
|
2371
|
+
name: "author_resolve_grid_cell",
|
|
2372
|
+
label: "Resolve grid cell",
|
|
2373
|
+
description: "Resolve one current grid cell to a native document anchor before mutation.",
|
|
2374
|
+
inputSchema: {
|
|
2375
|
+
type: "object",
|
|
2376
|
+
additionalProperties: false,
|
|
2377
|
+
required: [
|
|
2378
|
+
"cell",
|
|
2379
|
+
"geometryToken",
|
|
2380
|
+
"instruction"
|
|
2381
|
+
],
|
|
2382
|
+
properties: {
|
|
2383
|
+
cell: stringProperty,
|
|
2384
|
+
geometryToken: stringProperty,
|
|
2385
|
+
instruction: stringProperty
|
|
2386
|
+
}
|
|
2387
|
+
},
|
|
2388
|
+
execute: async (input) => {
|
|
2389
|
+
const value = inputRecord(input);
|
|
2390
|
+
const instruction = requiredString(value, "instruction");
|
|
2391
|
+
const cell = resolveAuthorGridCell(sessionId, requiredString(value, "cell"), requiredString(value, "geometryToken"));
|
|
2392
|
+
const geometry = authorGridGeometry(sessionId);
|
|
2393
|
+
const resolved = resolvedAnchor(sessionId, path, kind, cell);
|
|
2394
|
+
const regionId = crypto.randomUUID();
|
|
2395
|
+
const createdAt = Date.now();
|
|
2396
|
+
const region = {
|
|
2397
|
+
id: regionId,
|
|
2398
|
+
documentPath: path,
|
|
2399
|
+
revision: geometry.revision,
|
|
2400
|
+
sourceSha256: geometry.sourceSha256,
|
|
2401
|
+
comment: instruction,
|
|
2402
|
+
...resolved,
|
|
2403
|
+
viewport: { ...geometry.viewport },
|
|
2404
|
+
voiceGrid: cell.evidence,
|
|
2405
|
+
createdAt
|
|
2406
|
+
};
|
|
2407
|
+
addAuthorRegion(sessionId, region);
|
|
2408
|
+
putAuthorRequest(sessionId, {
|
|
2409
|
+
id: `voice:${regionId}`,
|
|
2410
|
+
documentPath: path,
|
|
2411
|
+
requestText: instruction,
|
|
2412
|
+
regions: [region],
|
|
2413
|
+
pendingRegions: [region],
|
|
2414
|
+
status: "REQUESTED",
|
|
2415
|
+
createdAt,
|
|
2416
|
+
updatedAt: createdAt,
|
|
2417
|
+
revision: geometry.revision,
|
|
2418
|
+
sourceSha256: geometry.sourceSha256
|
|
2419
|
+
});
|
|
2420
|
+
return {
|
|
2421
|
+
regionId,
|
|
2422
|
+
anchor: resolved.anchor,
|
|
2423
|
+
quote: resolved.quote,
|
|
2424
|
+
evidence: cell.evidence
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
}];
|
|
2428
|
+
}
|
|
2429
|
+
function assertRebasable(pending, changed) {
|
|
2430
|
+
const remaining = pending.filter((region) => region.id !== changed.id);
|
|
2431
|
+
if (changed.anchor.kind === "image-rect" && remaining.length > 0) throw new Error("UNSUPPORTED_REGION: A multi-region image request cannot apply more than one crop.");
|
|
2432
|
+
if (changed.anchor.kind !== "text-range") return;
|
|
2433
|
+
for (const region of remaining) {
|
|
2434
|
+
if (region.anchor.kind !== "text-range") continue;
|
|
2435
|
+
if (!(region.anchor.endOffset <= changed.anchor.startOffset || region.anchor.startOffset >= changed.anchor.endOffset)) throw new Error("AMBIGUOUS_REGION: Submitted text regions overlap and cannot be safely rebased.");
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
function lineAtOffset(content, offset) {
|
|
2439
|
+
return content.slice(0, Math.max(0, offset)).split("\n").length;
|
|
2440
|
+
}
|
|
2441
|
+
function rebasePendingRegions(pending, changed, result, nextContent) {
|
|
2442
|
+
return pending.filter((region) => region.id !== changed.id).map((region) => {
|
|
2443
|
+
if (changed.anchor.kind !== "text-range" || region.anchor.kind !== "text-range" || nextContent === void 0) return {
|
|
2444
|
+
...region,
|
|
2445
|
+
revision: result.revision
|
|
2446
|
+
};
|
|
2447
|
+
const delta = (result.after?.length ?? 0) - (result.before?.length ?? 0);
|
|
2448
|
+
const shift = region.anchor.startOffset >= changed.anchor.endOffset ? delta : 0;
|
|
2449
|
+
const startOffset = region.anchor.startOffset + shift;
|
|
2450
|
+
const endOffset = region.anchor.endOffset + shift;
|
|
2451
|
+
return {
|
|
2452
|
+
...region,
|
|
2453
|
+
revision: result.revision,
|
|
2454
|
+
anchor: {
|
|
2455
|
+
...region.anchor,
|
|
2456
|
+
startOffset,
|
|
2457
|
+
endOffset,
|
|
2458
|
+
startLine: lineAtOffset(nextContent, startOffset),
|
|
2459
|
+
endLine: lineAtOffset(nextContent, endOffset)
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
function mutationTool(sessionId, path, kind) {
|
|
2465
|
+
if (kind === "pdf" || kind === "video" || kind === "opaque") return void 0;
|
|
2466
|
+
const needsReplacement = kind !== "image";
|
|
2467
|
+
return {
|
|
2468
|
+
name: "author_apply_region",
|
|
2469
|
+
label: "Apply region change",
|
|
2470
|
+
description: "Apply a fenced mutation through a previously resolved native Author region.",
|
|
2471
|
+
inputSchema: {
|
|
2472
|
+
type: "object",
|
|
2473
|
+
additionalProperties: false,
|
|
2474
|
+
required: [
|
|
2475
|
+
"regionId",
|
|
2476
|
+
"expectedRevision",
|
|
2477
|
+
"expectedSourceSha256",
|
|
2478
|
+
...needsReplacement ? ["replacement"] : []
|
|
2479
|
+
],
|
|
2480
|
+
properties: {
|
|
2481
|
+
regionId: stringProperty,
|
|
2482
|
+
expectedRevision: {
|
|
2483
|
+
type: "integer",
|
|
2484
|
+
minimum: 0
|
|
2485
|
+
},
|
|
2486
|
+
expectedSourceSha256: stringProperty,
|
|
2487
|
+
...needsReplacement ? { replacement: { type: "string" } } : {}
|
|
2488
|
+
}
|
|
2489
|
+
},
|
|
2490
|
+
execute: async (input, signal) => {
|
|
2491
|
+
const value = inputRecord(input);
|
|
2492
|
+
const regionId = requiredString(value, "regionId");
|
|
2493
|
+
const request = authorSessionWorkspace(sessionId).requests.find((candidate) => (candidate.pendingRegions ?? candidate.regions).some((item) => item.id === regionId) && candidate.status === "REQUESTED");
|
|
2494
|
+
if (request !== void 0) updateAuthorRequest(sessionId, request.id, (current) => ({
|
|
2495
|
+
...current,
|
|
2496
|
+
status: "CHANGING",
|
|
2497
|
+
currentOperation: "validating native anchor",
|
|
2498
|
+
updatedAt: Date.now()
|
|
2499
|
+
}));
|
|
2500
|
+
try {
|
|
2501
|
+
if (signal.aborted) throw signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("Author mutation cancelled.");
|
|
2502
|
+
const { document, region } = checkedRegion(sessionId, path, value);
|
|
2503
|
+
const pending = request?.pendingRegions ?? request?.regions ?? [region];
|
|
2504
|
+
assertRebasable(pending, region);
|
|
2505
|
+
if (request !== void 0) updateAuthorRequest(sessionId, request.id, (current) => ({
|
|
2506
|
+
...current,
|
|
2507
|
+
currentOperation: `mutating ${region.anchor.kind}`,
|
|
2508
|
+
updatedAt: Date.now()
|
|
2509
|
+
}));
|
|
2510
|
+
let result;
|
|
2511
|
+
let nextContent;
|
|
2512
|
+
if (region.anchor.kind === "text-range") {
|
|
2513
|
+
const replacement = typeof value.replacement === "string" ? value.replacement : "";
|
|
2514
|
+
const { startOffset, endOffset } = region.anchor;
|
|
2515
|
+
const content = document.content ?? "";
|
|
2516
|
+
const before = content.slice(startOffset, endOffset);
|
|
2517
|
+
nextContent = content.slice(0, startOffset) + replacement + content.slice(endOffset);
|
|
2518
|
+
reviseAuthorDocument(sessionId, path, nextContent);
|
|
2519
|
+
result = {
|
|
2520
|
+
changed: true,
|
|
2521
|
+
before,
|
|
2522
|
+
after: replacement,
|
|
2523
|
+
revision: document.version + 1
|
|
2524
|
+
};
|
|
2525
|
+
} else if (region.anchor.kind === "cell" || region.anchor.kind === "slide-element") {
|
|
2526
|
+
const fragmentId = region.anchor.fragmentId;
|
|
2527
|
+
const fragment = document.fragments?.find((candidate) => candidate.id === fragmentId);
|
|
2528
|
+
if (fragment === void 0 || fragment.readOnly === true) throw new Error("UNSUPPORTED_REGION: The native fragment is not editable.");
|
|
2529
|
+
const replacement = typeof value.replacement === "string" ? value.replacement : "";
|
|
2530
|
+
reviseAuthorFragment(sessionId, path, fragment.id, replacement);
|
|
2531
|
+
result = {
|
|
2532
|
+
changed: true,
|
|
2533
|
+
before: fragment.text,
|
|
2534
|
+
after: replacement,
|
|
2535
|
+
revision: document.version + 1
|
|
2536
|
+
};
|
|
2537
|
+
} else if (region.anchor.kind === "image-rect") {
|
|
2538
|
+
setAuthorCrop(sessionId, path, region.anchor.rect);
|
|
2539
|
+
result = {
|
|
2540
|
+
changed: true,
|
|
2541
|
+
crop: region.anchor.rect,
|
|
2542
|
+
revision: document.version + 1
|
|
2543
|
+
};
|
|
2544
|
+
} else throw new Error("UNSUPPORTED_REGION: PDF and video regions are capture-only.");
|
|
2545
|
+
if (request !== void 0) {
|
|
2546
|
+
const pendingRegions = rebasePendingRegions(pending, region, result, nextContent);
|
|
2547
|
+
updateAuthorRequest(sessionId, request.id, (current) => ({
|
|
2548
|
+
...current,
|
|
2549
|
+
status: pendingRegions.length === 0 ? "CHANGED" : "REQUESTED",
|
|
2550
|
+
currentOperation: void 0,
|
|
2551
|
+
before: [current.before, result.before].filter((item) => item !== void 0).join("\n") || void 0,
|
|
2552
|
+
after: [current.after, result.after ?? (result.crop === void 0 ? void 0 : JSON.stringify(result.crop))].filter((item) => item !== void 0).join("\n") || void 0,
|
|
2553
|
+
pendingRegions,
|
|
2554
|
+
revision: result.revision,
|
|
2555
|
+
updatedAt: Date.now()
|
|
2556
|
+
}));
|
|
2557
|
+
}
|
|
2558
|
+
return result;
|
|
2559
|
+
} catch (error) {
|
|
2560
|
+
if (request !== void 0) updateAuthorRequest(sessionId, request.id, (current) => ({
|
|
2561
|
+
...current,
|
|
2562
|
+
status: signal.aborted ? "CANCELLED" : "FAILED",
|
|
2563
|
+
currentOperation: void 0,
|
|
2564
|
+
error: signal.aborted ? "Author mutation cancelled." : error instanceof Error ? error.message : String(error),
|
|
2565
|
+
updatedAt: Date.now()
|
|
2566
|
+
}));
|
|
2567
|
+
throw error;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function authorGridTools(sessionId, path, kind) {
|
|
2573
|
+
const mutation = mutationTool(sessionId, path, kind);
|
|
2574
|
+
return [...gridTools(sessionId, path, kind), ...mutation === void 0 ? [] : [mutation]];
|
|
2575
|
+
}
|
|
2576
|
+
//#endregion
|
|
2577
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorProfiles.ts
|
|
2578
|
+
function record(input) {
|
|
2579
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) throw new Error("Expected an object");
|
|
2580
|
+
return input;
|
|
2581
|
+
}
|
|
2582
|
+
function text(value, field) {
|
|
2583
|
+
if (typeof value !== "string") throw new Error(`${field} must be a string`);
|
|
2584
|
+
return value;
|
|
2585
|
+
}
|
|
2586
|
+
function target(input) {
|
|
2587
|
+
const value = record(input);
|
|
2588
|
+
return {
|
|
2589
|
+
sessionId: text(value.sessionId, "sessionId"),
|
|
2590
|
+
path: text(value.path, "path"),
|
|
2591
|
+
value
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
const TARGET_PROPERTIES = {
|
|
2595
|
+
sessionId: {
|
|
2596
|
+
type: "string",
|
|
2597
|
+
minLength: 1
|
|
2598
|
+
},
|
|
2599
|
+
path: {
|
|
2600
|
+
type: "string",
|
|
2601
|
+
minLength: 1
|
|
2602
|
+
}
|
|
2603
|
+
};
|
|
2604
|
+
const AUTHOR_TEXT_PROFILE = {
|
|
2605
|
+
id: AUTHOR_RUNTIME_BINDING_IDS.text,
|
|
2606
|
+
tools: [{
|
|
2607
|
+
name: "author_add_comment",
|
|
2608
|
+
label: "Add comment",
|
|
2609
|
+
description: "Add a local review comment to one open Author document.",
|
|
2610
|
+
inputSchema: {
|
|
2611
|
+
type: "object",
|
|
2612
|
+
additionalProperties: false,
|
|
2613
|
+
required: [
|
|
2614
|
+
"sessionId",
|
|
2615
|
+
"path",
|
|
2616
|
+
"body"
|
|
2617
|
+
],
|
|
2618
|
+
properties: {
|
|
2619
|
+
...TARGET_PROPERTIES,
|
|
2620
|
+
body: { type: "string" },
|
|
2621
|
+
quote: { type: "string" }
|
|
2622
|
+
}
|
|
2623
|
+
},
|
|
2624
|
+
execute: async (input) => {
|
|
2625
|
+
const { sessionId, path, value } = target(input);
|
|
2626
|
+
const body = text(value.body, "body");
|
|
2627
|
+
addAuthorAnnotation(sessionId, path, {
|
|
2628
|
+
id: `${Date.now()}:${body.length}`,
|
|
2629
|
+
kind: "comment",
|
|
2630
|
+
body,
|
|
2631
|
+
...typeof value.quote === "string" ? { quote: value.quote } : {}
|
|
2632
|
+
});
|
|
2633
|
+
return { added: true };
|
|
2634
|
+
}
|
|
2635
|
+
}, {
|
|
2636
|
+
name: "author_add_highlight",
|
|
2637
|
+
label: "Add highlight",
|
|
2638
|
+
description: "Highlight a line range in one open Author text document.",
|
|
2639
|
+
inputSchema: {
|
|
2640
|
+
type: "object",
|
|
2641
|
+
additionalProperties: false,
|
|
2642
|
+
required: [
|
|
2643
|
+
"sessionId",
|
|
2644
|
+
"path",
|
|
2645
|
+
"startLine",
|
|
2646
|
+
"endLine"
|
|
2647
|
+
],
|
|
2648
|
+
properties: {
|
|
2649
|
+
...TARGET_PROPERTIES,
|
|
2650
|
+
startLine: {
|
|
2651
|
+
type: "integer",
|
|
2652
|
+
minimum: 1
|
|
2653
|
+
},
|
|
2654
|
+
endLine: {
|
|
2655
|
+
type: "integer",
|
|
2656
|
+
minimum: 1
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
},
|
|
2660
|
+
execute: async (input) => {
|
|
2661
|
+
const { sessionId, path, value } = target(input);
|
|
2662
|
+
if (!Number.isSafeInteger(value.startLine) || !Number.isSafeInteger(value.endLine)) throw new Error("Highlight lines must be integers");
|
|
2663
|
+
const startLine = value.startLine;
|
|
2664
|
+
const endLine = value.endLine;
|
|
2665
|
+
if (startLine < 1 || endLine < startLine) throw new Error("Invalid highlight range");
|
|
2666
|
+
addAuthorAnnotation(sessionId, path, {
|
|
2667
|
+
id: `${Date.now()}:${startLine}:${endLine}`,
|
|
2668
|
+
kind: "highlight",
|
|
2669
|
+
body: "",
|
|
2670
|
+
startLine,
|
|
2671
|
+
endLine
|
|
2672
|
+
});
|
|
2673
|
+
return { added: true };
|
|
2674
|
+
}
|
|
2675
|
+
}]
|
|
2676
|
+
};
|
|
2677
|
+
const AUTHOR_MEDIA_PROFILE = {
|
|
2678
|
+
id: AUTHOR_RUNTIME_BINDING_IDS.media,
|
|
2679
|
+
tools: []
|
|
2680
|
+
};
|
|
2681
|
+
function bindProfile(profile, sessionId, path) {
|
|
2682
|
+
return {
|
|
2683
|
+
id: profile.id,
|
|
2684
|
+
tools: profile.tools.map((tool) => {
|
|
2685
|
+
const schema = tool.inputSchema;
|
|
2686
|
+
const { sessionId: _sessionId, path: _path, ...boundProperties } = record(schema.properties);
|
|
2687
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((entry) => entry !== "sessionId" && entry !== "path") : [];
|
|
2688
|
+
return {
|
|
2689
|
+
...tool,
|
|
2690
|
+
inputSchema: {
|
|
2691
|
+
...schema,
|
|
2692
|
+
properties: boundProperties,
|
|
2693
|
+
required
|
|
2694
|
+
},
|
|
2695
|
+
execute: async (input, signal) => await tool.execute({
|
|
2696
|
+
...record(input),
|
|
2697
|
+
sessionId,
|
|
2698
|
+
path
|
|
2699
|
+
}, signal)
|
|
2700
|
+
};
|
|
2701
|
+
})
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
function authorProfilesForDocument(sessionId, path, kind) {
|
|
2705
|
+
const profiles = kind === "image" ? [bindProfile(AUTHOR_MEDIA_PROFILE, sessionId, path)] : kind === "text" || kind === "markdown" ? [bindProfile(AUTHOR_TEXT_PROFILE, sessionId, path)] : [];
|
|
2706
|
+
const tools = authorGridTools(sessionId, path, kind);
|
|
2707
|
+
if (tools.length === 0) return profiles;
|
|
2708
|
+
return profiles.length === 0 ? [{
|
|
2709
|
+
id: AUTHOR_RUNTIME_BINDING_IDS.media,
|
|
2710
|
+
tools
|
|
2711
|
+
}] : [{
|
|
2712
|
+
...profiles[0],
|
|
2713
|
+
tools: [...profiles[0].tools, ...tools]
|
|
2714
|
+
}];
|
|
2715
|
+
}
|
|
2716
|
+
//#endregion
|
|
2717
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorMedia.ts
|
|
2718
|
+
/**
|
|
2719
|
+
* Native media elements cannot perform the remote session's encrypted HTTP exchange.
|
|
2720
|
+
*
|
|
2721
|
+
* `source` addresses the host's own session file route, which the route table
|
|
2722
|
+
* declares as `file` with `host: true`; this package serves no bytes of its
|
|
2723
|
+
* own. The transport stays raw because what comes back is a blob, and a call
|
|
2724
|
+
* through the generated client reads the body as JSON to report a refusal.
|
|
2725
|
+
*/
|
|
2726
|
+
async function loadAuthorMedia(source, signal) {
|
|
2727
|
+
const response = await sealedTransport.fetch(source, {
|
|
2728
|
+
signal,
|
|
2729
|
+
cache: "no-store"
|
|
2730
|
+
});
|
|
2731
|
+
if (!response.ok) throw new Error(`Author media could not be loaded (${response.status}).`);
|
|
2732
|
+
const blob = await response.blob();
|
|
2733
|
+
signal.throwIfAborted();
|
|
2734
|
+
const url = URL.createObjectURL(blob);
|
|
2735
|
+
let disposed = false;
|
|
2736
|
+
return {
|
|
2737
|
+
url,
|
|
2738
|
+
dispose() {
|
|
2739
|
+
if (disposed) return;
|
|
2740
|
+
disposed = true;
|
|
2741
|
+
URL.revokeObjectURL(url);
|
|
2742
|
+
}
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
//#endregion
|
|
2746
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorRegions.ts
|
|
2747
|
+
/** Clamp a drag to the source element, including drags made in reverse. */
|
|
2748
|
+
function normalizedAuthorRectangle(bounds, drag) {
|
|
2749
|
+
const width = bounds.right - bounds.left;
|
|
2750
|
+
const height = bounds.bottom - bounds.top;
|
|
2751
|
+
if (![...Object.values(bounds), ...Object.values(drag)].every(Number.isFinite) || width <= 0 || height <= 0) return null;
|
|
2752
|
+
const left = Math.max(bounds.left, Math.min(drag.left, drag.right));
|
|
2753
|
+
const right = Math.min(bounds.right, Math.max(drag.left, drag.right));
|
|
2754
|
+
const top = Math.max(bounds.top, Math.min(drag.top, drag.bottom));
|
|
2755
|
+
const bottom = Math.min(bounds.bottom, Math.max(drag.top, drag.bottom));
|
|
2756
|
+
if (right <= left || bottom <= top) return null;
|
|
2757
|
+
return {
|
|
2758
|
+
x: (left - bounds.left) / width,
|
|
2759
|
+
y: (top - bounds.top) / height,
|
|
2760
|
+
width: (right - left) / width,
|
|
2761
|
+
height: (bottom - top) / height
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
//#endregion
|
|
2765
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorVideoControls.tsx
|
|
2766
|
+
function videoTimeLabel(seconds) {
|
|
2767
|
+
const milliseconds = Math.max(0, Math.round((Number.isFinite(seconds) ? seconds : 0) * 1e3));
|
|
2768
|
+
return `${String(Math.floor(milliseconds / 6e4)).padStart(2, "0")}:${String(Math.floor(milliseconds / 1e3) % 60).padStart(2, "0")}.${String(milliseconds % 1e3).padStart(3, "0")}`;
|
|
2769
|
+
}
|
|
2770
|
+
function AuthorVideoControls({ playback, ready, locked, marking, onSeek, onToggle, onAnnotate }) {
|
|
2771
|
+
const [seconds, setSeconds] = useState("");
|
|
2772
|
+
const duration = playback.duration;
|
|
2773
|
+
const invalid = seconds.trim() === "" || !Number.isFinite(Number(seconds)) || Number(seconds) < 0 || Number(seconds) > duration;
|
|
2774
|
+
const buttonClass = "min-h-11 px-3 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm";
|
|
2775
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
2776
|
+
"aria-label": "Video annotation controls",
|
|
2777
|
+
className: "mt-2 space-y-2 rounded border border-doom-border bg-doom-panel p-2.5 text-base text-doom-text sm:text-sm",
|
|
2778
|
+
children: [
|
|
2779
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2780
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
2781
|
+
children: [/* @__PURE__ */ jsxs("output", {
|
|
2782
|
+
"aria-label": "Playback position",
|
|
2783
|
+
className: "font-mono text-base tabular-nums",
|
|
2784
|
+
children: [
|
|
2785
|
+
videoTimeLabel(playback.currentTime),
|
|
2786
|
+
" / ",
|
|
2787
|
+
videoTimeLabel(duration)
|
|
2788
|
+
]
|
|
2789
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2790
|
+
className: "text-doom-dim",
|
|
2791
|
+
children: locked ? "Annotation draft: add or discard it before seeking." : marking ? "Frame paused. Drag a region on the video." : "Seek to a moment, then choose Annotate this frame."
|
|
2792
|
+
})]
|
|
2793
|
+
}),
|
|
2794
|
+
/* @__PURE__ */ jsx("input", {
|
|
2795
|
+
"aria-label": "Video timeline",
|
|
2796
|
+
type: "range",
|
|
2797
|
+
min: 0,
|
|
2798
|
+
max: duration || 0,
|
|
2799
|
+
step: .001,
|
|
2800
|
+
value: Math.min(playback.currentTime, duration),
|
|
2801
|
+
disabled: locked || duration <= 0,
|
|
2802
|
+
onChange: (event) => onSeek(Number(event.target.value)),
|
|
2803
|
+
className: "h-11 w-full cursor-pointer accent-doom-blue [@media(pointer:fine)]:h-6"
|
|
2804
|
+
}),
|
|
2805
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2806
|
+
className: "flex flex-wrap items-center gap-2",
|
|
2807
|
+
children: [
|
|
2808
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2809
|
+
className: buttonClass,
|
|
2810
|
+
disabled: locked || duration <= 0,
|
|
2811
|
+
onClick: onToggle,
|
|
2812
|
+
children: playback.playing ? "Pause" : "Play"
|
|
2813
|
+
}),
|
|
2814
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2815
|
+
className: buttonClass,
|
|
2816
|
+
disabled: locked || duration <= 0,
|
|
2817
|
+
onClick: () => onSeek(Math.max(0, playback.currentTime - .1)),
|
|
2818
|
+
"aria-label": "Seek back 0.1 seconds",
|
|
2819
|
+
children: "−0.1s"
|
|
2820
|
+
}),
|
|
2821
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2822
|
+
className: buttonClass,
|
|
2823
|
+
disabled: locked || duration <= 0,
|
|
2824
|
+
onClick: () => onSeek(Math.min(duration, playback.currentTime + .1)),
|
|
2825
|
+
"aria-label": "Seek forward 0.1 seconds",
|
|
2826
|
+
children: "+0.1s"
|
|
2827
|
+
}),
|
|
2828
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2829
|
+
variant: "primary",
|
|
2830
|
+
className: buttonClass,
|
|
2831
|
+
disabled: locked || !ready,
|
|
2832
|
+
onClick: onAnnotate,
|
|
2833
|
+
children: "Annotate this frame"
|
|
2834
|
+
}),
|
|
2835
|
+
/* @__PURE__ */ jsxs("form", {
|
|
2836
|
+
className: "flex flex-wrap items-center gap-2 sm:ml-auto",
|
|
2837
|
+
onSubmit: (event) => {
|
|
2838
|
+
event.preventDefault();
|
|
2839
|
+
if (!invalid && !locked) onSeek(Number(seconds));
|
|
2840
|
+
},
|
|
2841
|
+
children: [
|
|
2842
|
+
/* @__PURE__ */ jsx("label", {
|
|
2843
|
+
htmlFor: "author-video-seconds",
|
|
2844
|
+
className: "text-doom-dim",
|
|
2845
|
+
children: "Go to seconds"
|
|
2846
|
+
}),
|
|
2847
|
+
/* @__PURE__ */ jsx("input", {
|
|
2848
|
+
id: "author-video-seconds",
|
|
2849
|
+
type: "number",
|
|
2850
|
+
min: 0,
|
|
2851
|
+
max: duration,
|
|
2852
|
+
step: .001,
|
|
2853
|
+
value: seconds,
|
|
2854
|
+
onChange: (event) => setSeconds(event.target.value),
|
|
2855
|
+
disabled: locked || duration <= 0,
|
|
2856
|
+
placeholder: "1.250",
|
|
2857
|
+
className: "h-11 w-24 rounded border border-doom-border bg-doom-deep px-2 text-lg [@media(pointer:fine)]:h-8 sm:text-sm"
|
|
2858
|
+
}),
|
|
2859
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2860
|
+
type: "submit",
|
|
2861
|
+
className: buttonClass,
|
|
2862
|
+
disabled: locked || duration <= 0 || invalid,
|
|
2863
|
+
children: "Go"
|
|
2864
|
+
})
|
|
2865
|
+
]
|
|
2866
|
+
})
|
|
2867
|
+
]
|
|
2868
|
+
})
|
|
2869
|
+
]
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
//#endregion
|
|
2873
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorMediaView.tsx
|
|
2874
|
+
/** Marks source media. Native playback and page controls remain usable in select mode. */
|
|
2875
|
+
function AuthorMediaView({ sessionId, document: source, activeTool, displayedRegions, pendingCandidate = false, seekRequest }) {
|
|
2876
|
+
const host = useRef(null);
|
|
2877
|
+
const image = useRef(null);
|
|
2878
|
+
const video = useRef(null);
|
|
2879
|
+
const pdf = useRef(null);
|
|
2880
|
+
const start = useRef(void 0);
|
|
2881
|
+
const [playback, setPlayback] = useState({
|
|
2882
|
+
playing: false,
|
|
2883
|
+
currentTime: 0,
|
|
2884
|
+
duration: 0
|
|
2885
|
+
});
|
|
2886
|
+
const [frameReady, setFrameReady] = useState(false);
|
|
2887
|
+
const [selection, setSelection] = useState();
|
|
2888
|
+
const [error, setError] = useState();
|
|
2889
|
+
const [media, setMedia] = useState();
|
|
2890
|
+
const mediaUrl = media?.source === source.mediaUrl ? media?.url : void 0;
|
|
2891
|
+
useEffect(() => {
|
|
2892
|
+
if (source.mediaUrl === void 0) return;
|
|
2893
|
+
const input = source.mediaUrl;
|
|
2894
|
+
const controller = new AbortController();
|
|
2895
|
+
let dispose;
|
|
2896
|
+
setError(void 0);
|
|
2897
|
+
loadAuthorMedia(input, controller.signal).then((asset) => {
|
|
2898
|
+
if (controller.signal.aborted) {
|
|
2899
|
+
asset.dispose();
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
dispose = () => asset.dispose();
|
|
2903
|
+
setMedia({
|
|
2904
|
+
source: input,
|
|
2905
|
+
url: asset.url
|
|
2906
|
+
});
|
|
2907
|
+
}).catch((cause) => {
|
|
2908
|
+
if (!controller.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause));
|
|
2909
|
+
});
|
|
2910
|
+
return () => {
|
|
2911
|
+
controller.abort();
|
|
2912
|
+
dispose?.();
|
|
2913
|
+
};
|
|
2914
|
+
}, [source.mediaUrl]);
|
|
2915
|
+
const seek = (seconds) => {
|
|
2916
|
+
if (authorSessionWorkspace(sessionId).candidate || !Number.isFinite(seconds)) return;
|
|
2917
|
+
const player = video.current;
|
|
2918
|
+
if (!player) return;
|
|
2919
|
+
player.pause();
|
|
2920
|
+
setAuthorToolMode(sessionId, "select");
|
|
2921
|
+
setSelection(void 0);
|
|
2922
|
+
if (player.getState().currentTime === seconds) return;
|
|
2923
|
+
setFrameReady(false);
|
|
2924
|
+
player.seek(seconds);
|
|
2925
|
+
};
|
|
2926
|
+
useEffect(() => {
|
|
2927
|
+
if (seekRequest?.path === source.path && seekRequest.generation === authorSessionWorkspace(sessionId).focusedDocument?.generation) seek(seekRequest.timeSeconds);
|
|
2928
|
+
}, [seekRequest]);
|
|
2929
|
+
useEffect(() => {
|
|
2930
|
+
if (!pendingCandidate && start.current === void 0) setSelection(void 0);
|
|
2931
|
+
}, [pendingCandidate]);
|
|
2932
|
+
useEffect(() => registerAuthorGridResolver(sessionId, (cell, geometry) => {
|
|
2933
|
+
const target = image.current;
|
|
2934
|
+
if (source.kind !== "image" || target === null || target.naturalWidth === 0 || target.naturalHeight === 0) return void 0;
|
|
2935
|
+
const { originX = 0, originY = 0, width, height } = geometry.viewport;
|
|
2936
|
+
const cellBounds = {
|
|
2937
|
+
left: originX + cell.rect.x * width,
|
|
2938
|
+
top: originY + cell.rect.y * height,
|
|
2939
|
+
right: originX + (cell.rect.x + cell.rect.width) * width,
|
|
2940
|
+
bottom: originY + (cell.rect.y + cell.rect.height) * height
|
|
2941
|
+
};
|
|
2942
|
+
const bounds = target.getBoundingClientRect();
|
|
2943
|
+
const left = Math.max(bounds.left, cellBounds.left);
|
|
2944
|
+
const top = Math.max(bounds.top, cellBounds.top);
|
|
2945
|
+
const right = Math.min(bounds.right, cellBounds.right);
|
|
2946
|
+
const bottom = Math.min(bounds.bottom, cellBounds.bottom);
|
|
2947
|
+
if (right <= left || bottom <= top || bounds.width <= 0 || bounds.height <= 0) return void 0;
|
|
2948
|
+
return { anchor: {
|
|
2949
|
+
kind: "image-rect",
|
|
2950
|
+
rect: {
|
|
2951
|
+
x: (left - bounds.left) / bounds.width,
|
|
2952
|
+
y: (top - bounds.top) / bounds.height,
|
|
2953
|
+
width: (right - left) / bounds.width,
|
|
2954
|
+
height: (bottom - top) / bounds.height
|
|
2955
|
+
},
|
|
2956
|
+
naturalWidth: target.naturalWidth,
|
|
2957
|
+
naturalHeight: target.naturalHeight
|
|
2958
|
+
} };
|
|
2959
|
+
}), [
|
|
2960
|
+
sessionId,
|
|
2961
|
+
source.kind,
|
|
2962
|
+
source.path,
|
|
2963
|
+
source.version
|
|
2964
|
+
]);
|
|
2965
|
+
const mark = async (event) => {
|
|
2966
|
+
const drag = start.current;
|
|
2967
|
+
start.current = void 0;
|
|
2968
|
+
if (!drag) return;
|
|
2969
|
+
const bounds = drag.element.getBoundingClientRect();
|
|
2970
|
+
if (bounds.width <= 0 || bounds.height <= 0) return;
|
|
2971
|
+
const point = {
|
|
2972
|
+
x: Math.min(1, Math.max(0, (event.clientX - bounds.left) / bounds.width)),
|
|
2973
|
+
y: Math.min(1, Math.max(0, (event.clientY - bounds.top) / bounds.height))
|
|
2974
|
+
};
|
|
2975
|
+
const rect = drag.mode === "mark" ? normalizedAuthorRectangle(bounds, {
|
|
2976
|
+
left: drag.x,
|
|
2977
|
+
top: drag.y,
|
|
2978
|
+
right: event.clientX,
|
|
2979
|
+
bottom: event.clientY
|
|
2980
|
+
}) : void 0;
|
|
2981
|
+
if (drag.mode === "mark" && !rect) return;
|
|
2982
|
+
setSelection(drag.mode === "mark" ? {
|
|
2983
|
+
path: source.path,
|
|
2984
|
+
rect
|
|
2985
|
+
} : {
|
|
2986
|
+
path: source.path,
|
|
2987
|
+
point
|
|
2988
|
+
});
|
|
2989
|
+
const focused = authorSessionWorkspace(sessionId).focusedDocument;
|
|
2990
|
+
if (!focused || focused.path !== source.path) return;
|
|
2991
|
+
let anchor;
|
|
2992
|
+
let blob = null;
|
|
2993
|
+
let thumbnailUrl;
|
|
2994
|
+
try {
|
|
2995
|
+
if (drag.element instanceof HTMLImageElement) {
|
|
2996
|
+
const image = drag.element;
|
|
2997
|
+
if (!image.naturalWidth || !image.naturalHeight) throw new Error("Image is not ready.");
|
|
2998
|
+
anchor = drag.mode === "mark" ? {
|
|
2999
|
+
kind: "image-rect",
|
|
3000
|
+
rect,
|
|
3001
|
+
naturalWidth: image.naturalWidth,
|
|
3002
|
+
naturalHeight: image.naturalHeight
|
|
3003
|
+
} : {
|
|
3004
|
+
kind: "image-point",
|
|
3005
|
+
point,
|
|
3006
|
+
naturalWidth: image.naturalWidth,
|
|
3007
|
+
naturalHeight: image.naturalHeight
|
|
3008
|
+
};
|
|
3009
|
+
const canvas = document.createElement("canvas");
|
|
3010
|
+
const scale = Math.min(1, 1600 / Math.max(image.naturalWidth, image.naturalHeight));
|
|
3011
|
+
canvas.width = Math.max(1, Math.round(image.naturalWidth * scale));
|
|
3012
|
+
canvas.height = Math.max(1, Math.round(image.naturalHeight * scale));
|
|
3013
|
+
const context = canvas.getContext("2d");
|
|
3014
|
+
if (!context) throw new Error("Canvas is unavailable.");
|
|
3015
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
3016
|
+
blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
3017
|
+
} else if (source.kind === "pdf") {
|
|
3018
|
+
const page = pdf.current?.getState();
|
|
3019
|
+
if (!page?.sourceWidth) throw new Error("PDF page is not ready.");
|
|
3020
|
+
anchor = drag.mode === "mark" ? {
|
|
3021
|
+
kind: "pdf-page-rect",
|
|
3022
|
+
page: page.page,
|
|
3023
|
+
rect
|
|
3024
|
+
} : {
|
|
3025
|
+
kind: "pdf-page-point",
|
|
3026
|
+
page: page.page,
|
|
3027
|
+
point
|
|
3028
|
+
};
|
|
3029
|
+
blob = await pdf.current.capturePage();
|
|
3030
|
+
} else {
|
|
3031
|
+
const frame = await video.current?.captureFrame();
|
|
3032
|
+
if (!frame) throw new Error("Video frame is not ready.");
|
|
3033
|
+
anchor = drag.mode === "mark" ? {
|
|
3034
|
+
kind: "video-time-rect",
|
|
3035
|
+
rect,
|
|
3036
|
+
timeSeconds: frame.timeSeconds,
|
|
3037
|
+
intrinsicWidth: frame.width,
|
|
3038
|
+
intrinsicHeight: frame.height
|
|
3039
|
+
} : {
|
|
3040
|
+
kind: "video-time-point",
|
|
3041
|
+
point,
|
|
3042
|
+
timeSeconds: frame.timeSeconds,
|
|
3043
|
+
intrinsicWidth: frame.width,
|
|
3044
|
+
intrinsicHeight: frame.height
|
|
3045
|
+
};
|
|
3046
|
+
blob = frame.blob;
|
|
3047
|
+
}
|
|
3048
|
+
if (!blob) throw new Error("Unable to capture selected media.");
|
|
3049
|
+
if (authorSessionWorkspace(sessionId).focusedDocument?.generation !== focused.generation) return;
|
|
3050
|
+
thumbnailUrl = URL.createObjectURL(blob);
|
|
3051
|
+
setAuthorRegionCandidate(sessionId, {
|
|
3052
|
+
documentPath: source.path,
|
|
3053
|
+
revision: source.version,
|
|
3054
|
+
sourceSha256: source.sourceSha256,
|
|
3055
|
+
mode: drag.mode === "mark" ? "region" : "point",
|
|
3056
|
+
anchor,
|
|
3057
|
+
viewport: {
|
|
3058
|
+
width: bounds.width,
|
|
3059
|
+
height: bounds.height
|
|
3060
|
+
},
|
|
3061
|
+
thumbnailUrl,
|
|
3062
|
+
evidence: blob,
|
|
3063
|
+
createdAt: Date.now()
|
|
3064
|
+
});
|
|
3065
|
+
setError(void 0);
|
|
3066
|
+
} catch (reason) {
|
|
3067
|
+
if (thumbnailUrl) URL.revokeObjectURL(thumbnailUrl);
|
|
3068
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
3069
|
+
}
|
|
3070
|
+
};
|
|
3071
|
+
const selected = selection?.path === source.path ? selection.rect : void 0;
|
|
3072
|
+
const selectedPoint = selection?.path === source.path ? selection.point : void 0;
|
|
3073
|
+
const selectionOverlay = selected === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
3074
|
+
"data-testid": "author-media-selection",
|
|
3075
|
+
className: "pointer-events-none absolute border-2 border-doom-red bg-doom-red/10",
|
|
3076
|
+
style: {
|
|
3077
|
+
left: `${selected.x * 100}%`,
|
|
3078
|
+
top: `${selected.y * 100}%`,
|
|
3079
|
+
width: `${selected.width * 100}%`,
|
|
3080
|
+
height: `${selected.height * 100}%`
|
|
3081
|
+
}
|
|
3082
|
+
});
|
|
3083
|
+
const pointPin = (ordinal, point, key) => /* @__PURE__ */ jsx("span", {
|
|
3084
|
+
"data-author-point": ordinal,
|
|
3085
|
+
"data-testid": ordinal === void 0 ? "author-media-point-selection" : void 0,
|
|
3086
|
+
className: `pointer-events-none absolute z-10 flex h-5 min-w-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border-2 px-1 text-2xs font-bold ${ordinal === void 0 ? "border-white bg-doom-red text-white" : "border-doom-deep bg-doom-yellow text-doom-deep"}`,
|
|
3087
|
+
style: {
|
|
3088
|
+
left: `${String(point.x * 100)}%`,
|
|
3089
|
+
top: `${String(point.y * 100)}%`
|
|
3090
|
+
},
|
|
3091
|
+
children: ordinal ?? "•"
|
|
3092
|
+
}, key);
|
|
3093
|
+
const pointSelectionOverlay = selectedPoint === void 0 ? null : pointPin(void 0, selectedPoint, "selection");
|
|
3094
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3095
|
+
ref: host,
|
|
3096
|
+
className: "min-h-0 flex-1 overflow-auto p-4",
|
|
3097
|
+
style: {
|
|
3098
|
+
touchAction: activeTool === "select" ? "auto" : "none",
|
|
3099
|
+
cursor: activeTool === "select" ? void 0 : "crosshair"
|
|
3100
|
+
},
|
|
3101
|
+
onPointerMove: (event) => {
|
|
3102
|
+
const drag = start.current;
|
|
3103
|
+
if (drag === void 0 || drag.mode !== "mark") return;
|
|
3104
|
+
const rect = normalizedAuthorRectangle(drag.element.getBoundingClientRect(), {
|
|
3105
|
+
left: drag.x,
|
|
3106
|
+
top: drag.y,
|
|
3107
|
+
right: event.clientX,
|
|
3108
|
+
bottom: event.clientY
|
|
3109
|
+
});
|
|
3110
|
+
setSelection(rect === null ? void 0 : {
|
|
3111
|
+
path: source.path,
|
|
3112
|
+
rect
|
|
3113
|
+
});
|
|
3114
|
+
},
|
|
3115
|
+
onPointerDownCapture: (event) => {
|
|
3116
|
+
if (activeTool !== "mark" && activeTool !== "comment" || event.button !== 0 || pendingCandidate) return;
|
|
3117
|
+
const element = event.target;
|
|
3118
|
+
if (!(element instanceof HTMLImageElement || element instanceof HTMLVideoElement || element instanceof HTMLCanvasElement)) return;
|
|
3119
|
+
event.preventDefault();
|
|
3120
|
+
setSelection(void 0);
|
|
3121
|
+
if (element instanceof HTMLVideoElement) {
|
|
3122
|
+
if (video.current?.isFrameReady?.() === false) {
|
|
3123
|
+
setError("Wait for the selected frame to finish loading.");
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
video.current?.pause();
|
|
3127
|
+
}
|
|
3128
|
+
start.current = {
|
|
3129
|
+
x: event.clientX,
|
|
3130
|
+
y: event.clientY,
|
|
3131
|
+
mode: activeTool,
|
|
3132
|
+
element
|
|
3133
|
+
};
|
|
3134
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
3135
|
+
},
|
|
3136
|
+
onPointerUp: (event) => void mark(event),
|
|
3137
|
+
onPointerCancel: () => {
|
|
3138
|
+
start.current = void 0;
|
|
3139
|
+
setSelection(void 0);
|
|
3140
|
+
},
|
|
3141
|
+
children: [
|
|
3142
|
+
mediaUrl === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
3143
|
+
className: "text-doom-dim",
|
|
3144
|
+
children: error === void 0 ? "Loading media..." : "Media unavailable."
|
|
3145
|
+
}) : source.kind === "image" ? /* @__PURE__ */ jsxs("div", {
|
|
3146
|
+
className: "relative inline-block max-w-full",
|
|
3147
|
+
children: [
|
|
3148
|
+
/* @__PURE__ */ jsx("img", {
|
|
3149
|
+
ref: image,
|
|
3150
|
+
src: mediaUrl,
|
|
3151
|
+
alt: source.path,
|
|
3152
|
+
draggable: false,
|
|
3153
|
+
className: "block max-w-full"
|
|
3154
|
+
}),
|
|
3155
|
+
selectionOverlay,
|
|
3156
|
+
pointSelectionOverlay,
|
|
3157
|
+
displayedRegions.flatMap(({ ordinal, region }) => {
|
|
3158
|
+
if (region.anchor.kind === "image-point") return [pointPin(ordinal, region.anchor.point, region.id)];
|
|
3159
|
+
if (region.anchor.kind !== "image-rect") return [];
|
|
3160
|
+
const { rect } = region.anchor;
|
|
3161
|
+
return [/* @__PURE__ */ jsx("div", {
|
|
3162
|
+
"data-author-region": ordinal,
|
|
3163
|
+
className: "pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10",
|
|
3164
|
+
style: {
|
|
3165
|
+
left: `${String(rect.x * 100)}%`,
|
|
3166
|
+
top: `${String(rect.y * 100)}%`,
|
|
3167
|
+
width: `${String(rect.width * 100)}%`,
|
|
3168
|
+
height: `${String(rect.height * 100)}%`
|
|
3169
|
+
},
|
|
3170
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3171
|
+
className: "absolute -left-2 -top-2 flex h-4 min-w-4 items-center justify-center rounded-full bg-doom-yellow px-1 text-2xs font-bold text-doom-deep",
|
|
3172
|
+
children: ordinal
|
|
3173
|
+
})
|
|
3174
|
+
}, region.id)];
|
|
3175
|
+
})
|
|
3176
|
+
]
|
|
3177
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
3178
|
+
className: "relative w-fit max-w-full",
|
|
3179
|
+
children: [
|
|
3180
|
+
/* @__PURE__ */ jsx(MediaPreview, {
|
|
3181
|
+
src: mediaUrl,
|
|
3182
|
+
path: source.path,
|
|
3183
|
+
kind: source.kind === "pdf" ? "pdf" : source.kind === "video" ? "video" : "download",
|
|
3184
|
+
controllerRef: video,
|
|
3185
|
+
pdfControllerRef: pdf,
|
|
3186
|
+
"data-testid": "author-media",
|
|
3187
|
+
videoControls: false,
|
|
3188
|
+
onPlaybackStateChange: (state) => {
|
|
3189
|
+
setPlayback(state);
|
|
3190
|
+
setFrameReady(video.current?.isFrameReady() ?? false);
|
|
3191
|
+
if (state.playing) setSelection(void 0);
|
|
3192
|
+
}
|
|
3193
|
+
}),
|
|
3194
|
+
selectionOverlay,
|
|
3195
|
+
pointSelectionOverlay,
|
|
3196
|
+
displayedRegions.map(({ ordinal, region }) => {
|
|
3197
|
+
if (source.kind === "pdf") {
|
|
3198
|
+
if (region.anchor.kind !== "pdf-page-point" && region.anchor.kind !== "pdf-page-rect") return null;
|
|
3199
|
+
if (region.anchor.page !== pdf.current?.getState().page) return null;
|
|
3200
|
+
if (region.anchor.kind === "pdf-page-point") return pointPin(ordinal, region.anchor.point, region.id);
|
|
3201
|
+
const { rect } = region.anchor;
|
|
3202
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3203
|
+
"data-author-region": ordinal,
|
|
3204
|
+
className: "pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10",
|
|
3205
|
+
style: {
|
|
3206
|
+
left: `${String(rect.x * 100)}%`,
|
|
3207
|
+
top: `${String(rect.y * 100)}%`,
|
|
3208
|
+
width: `${String(rect.width * 100)}%`,
|
|
3209
|
+
height: `${String(rect.height * 100)}%`
|
|
3210
|
+
},
|
|
3211
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3212
|
+
className: "bg-doom-yellow text-doom-deep",
|
|
3213
|
+
children: ordinal
|
|
3214
|
+
})
|
|
3215
|
+
}, region.id);
|
|
3216
|
+
}
|
|
3217
|
+
if (source.kind !== "video" || playback.playing) return null;
|
|
3218
|
+
if (region.anchor.kind === "video-time-point" && Math.abs(region.anchor.timeSeconds - playback.currentTime) <= .1) return pointPin(ordinal, region.anchor.point, region.id);
|
|
3219
|
+
if (region.anchor.kind !== "video-time-rect" || Math.abs(region.anchor.timeSeconds - playback.currentTime) > .1) return null;
|
|
3220
|
+
const { rect } = region.anchor;
|
|
3221
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3222
|
+
"data-author-region": ordinal,
|
|
3223
|
+
className: "pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10",
|
|
3224
|
+
style: {
|
|
3225
|
+
left: `${rect.x * 100}%`,
|
|
3226
|
+
top: `${rect.y * 100}%`,
|
|
3227
|
+
width: `${rect.width * 100}%`,
|
|
3228
|
+
height: `${rect.height * 100}%`
|
|
3229
|
+
},
|
|
3230
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3231
|
+
className: "bg-doom-yellow text-doom-deep",
|
|
3232
|
+
children: ordinal
|
|
3233
|
+
})
|
|
3234
|
+
}, region.id);
|
|
3235
|
+
})
|
|
3236
|
+
]
|
|
3237
|
+
}),
|
|
3238
|
+
source.kind === "video" ? /* @__PURE__ */ jsx(AuthorVideoControls, {
|
|
3239
|
+
playback,
|
|
3240
|
+
ready: frameReady,
|
|
3241
|
+
locked: pendingCandidate,
|
|
3242
|
+
marking: activeTool === "mark",
|
|
3243
|
+
onSeek: seek,
|
|
3244
|
+
onToggle: () => {
|
|
3245
|
+
setAuthorToolMode(sessionId, "select");
|
|
3246
|
+
setSelection(void 0);
|
|
3247
|
+
if (playback.playing) video.current?.pause();
|
|
3248
|
+
else video.current?.play().catch((reason) => setError(String(reason)));
|
|
3249
|
+
},
|
|
3250
|
+
onAnnotate: () => {
|
|
3251
|
+
video.current?.pause();
|
|
3252
|
+
setAuthorToolMode(sessionId, "mark");
|
|
3253
|
+
}
|
|
3254
|
+
}) : null,
|
|
3255
|
+
error ? /* @__PURE__ */ jsx("output", {
|
|
3256
|
+
className: "text-base text-doom-red",
|
|
3257
|
+
children: error
|
|
3258
|
+
}) : null
|
|
3259
|
+
]
|
|
3260
|
+
});
|
|
3261
|
+
}
|
|
3262
|
+
//#endregion
|
|
3263
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorStructuredView.tsx
|
|
3264
|
+
function AuthorStructuredView({ sessionId, document, displayedRegions }) {
|
|
3265
|
+
useEffect(() => registerAuthorGridResolver(sessionId, (cell, geometry) => {
|
|
3266
|
+
const { originX = 0, originY = 0, width, height } = geometry.viewport;
|
|
3267
|
+
const fragmentId = window.document.elementFromPoint(originX + cell.center.x * width, originY + cell.center.y * height)?.closest("[data-author-fragment]")?.dataset.authorFragment;
|
|
3268
|
+
const fragment = document.fragments?.find((candidate) => candidate.id === fragmentId);
|
|
3269
|
+
if (fragment === void 0 || fragment.readOnly === true) return void 0;
|
|
3270
|
+
if (document.kind === "csv" || document.kind === "xlsx") return {
|
|
3271
|
+
anchor: {
|
|
3272
|
+
kind: "cell",
|
|
3273
|
+
fragmentId: fragment.id,
|
|
3274
|
+
location: fragment.location
|
|
3275
|
+
},
|
|
3276
|
+
quote: fragment.text
|
|
3277
|
+
};
|
|
3278
|
+
const slide = Number(fragment.location.match(/(?:slide\s*|slides\/slide)(\d+)/u)?.[1]);
|
|
3279
|
+
if (!Number.isSafeInteger(slide)) return void 0;
|
|
3280
|
+
return {
|
|
3281
|
+
anchor: {
|
|
3282
|
+
kind: "slide-element",
|
|
3283
|
+
fragmentId: fragment.id,
|
|
3284
|
+
location: fragment.location,
|
|
3285
|
+
slide
|
|
3286
|
+
},
|
|
3287
|
+
quote: fragment.text
|
|
3288
|
+
};
|
|
3289
|
+
}), [
|
|
3290
|
+
document.fragments,
|
|
3291
|
+
document.kind,
|
|
3292
|
+
document.version,
|
|
3293
|
+
sessionId
|
|
3294
|
+
]);
|
|
3295
|
+
const displayedByFragment = new Map(displayedRegions.flatMap(({ ordinal, region }) => region.anchor.kind === "cell" || region.anchor.kind === "slide-element" ? [[region.anchor.fragmentId, ordinal]] : []));
|
|
3296
|
+
const cells = document.kind === "csv" || document.kind === "xlsx";
|
|
3297
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3298
|
+
for (const fragment of document.fragments ?? []) {
|
|
3299
|
+
const key = cells ? document.kind === "csv" ? fragment.location.split(",")[0] : fragment.location.replace(/![A-Z]+(\d+)$/, "!row $1") : fragment.location;
|
|
3300
|
+
const group = groups.get(key) ?? [];
|
|
3301
|
+
group.push(fragment);
|
|
3302
|
+
groups.set(key, group);
|
|
3303
|
+
}
|
|
3304
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3305
|
+
"data-testid": "author-structured",
|
|
3306
|
+
className: "min-h-0 flex-1 space-y-3 overflow-auto p-4",
|
|
3307
|
+
children: [!cells ? /* @__PURE__ */ jsx("p", {
|
|
3308
|
+
className: "text-xs text-doom-faint",
|
|
3309
|
+
children: "Slide text view, not a layout-faithful rendering."
|
|
3310
|
+
}) : null, [...groups].map(([location, fragments]) => /* @__PURE__ */ jsxs("section", {
|
|
3311
|
+
className: "space-y-1",
|
|
3312
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
3313
|
+
className: "text-xs text-doom-faint",
|
|
3314
|
+
children: location
|
|
3315
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3316
|
+
className: cells ? "flex gap-1" : "space-y-2",
|
|
3317
|
+
children: fragments.map((fragment) => /* @__PURE__ */ jsxs("div", {
|
|
3318
|
+
"data-author-fragment": fragment.id,
|
|
3319
|
+
"data-author-region": displayedByFragment.get(fragment.id),
|
|
3320
|
+
className: `${cells ? "w-40 shrink-0" : ""} relative rounded ${displayedByFragment.has(fragment.id) ? "ring-1 ring-doom-yellow" : ""}`,
|
|
3321
|
+
children: [
|
|
3322
|
+
displayedByFragment.has(fragment.id) ? /* @__PURE__ */ jsx("span", {
|
|
3323
|
+
className: "absolute -left-1 -top-1 z-10 flex h-4 min-w-4 items-center justify-center rounded-full bg-doom-yellow px-1 text-2xs font-bold text-doom-deep",
|
|
3324
|
+
children: displayedByFragment.get(fragment.id)
|
|
3325
|
+
}) : null,
|
|
3326
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
3327
|
+
size: "xs",
|
|
3328
|
+
variant: "ghost",
|
|
3329
|
+
onClick: (event) => {
|
|
3330
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
3331
|
+
const slide = Number(fragment.location.match(/(?:slide\s*|slides\/slide)(\d+)/)?.[1]);
|
|
3332
|
+
if (!cells && !Number.isSafeInteger(slide)) return;
|
|
3333
|
+
setAuthorRegionCandidate(sessionId, {
|
|
3334
|
+
documentPath: document.path,
|
|
3335
|
+
revision: document.version,
|
|
3336
|
+
sourceSha256: document.sourceSha256,
|
|
3337
|
+
quote: fragment.text,
|
|
3338
|
+
anchor: cells ? {
|
|
3339
|
+
kind: "cell",
|
|
3340
|
+
fragmentId: fragment.id,
|
|
3341
|
+
location: fragment.location
|
|
3342
|
+
} : {
|
|
3343
|
+
kind: "slide-element",
|
|
3344
|
+
fragmentId: fragment.id,
|
|
3345
|
+
location: fragment.location,
|
|
3346
|
+
slide
|
|
3347
|
+
},
|
|
3348
|
+
viewport: {
|
|
3349
|
+
width: bounds.width,
|
|
3350
|
+
height: bounds.height
|
|
3351
|
+
},
|
|
3352
|
+
createdAt: Date.now()
|
|
3353
|
+
});
|
|
3354
|
+
},
|
|
3355
|
+
children: ["mark ", fragment.location]
|
|
3356
|
+
}),
|
|
3357
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
3358
|
+
"aria-label": fragment.location,
|
|
3359
|
+
value: fragment.text,
|
|
3360
|
+
readOnly: fragment.readOnly === true,
|
|
3361
|
+
className: "w-full rounded border border-doom-border bg-doom-deep p-2 text-sm text-doom-text",
|
|
3362
|
+
onChange: (event) => reviseAuthorFragment(sessionId, document.path, fragment.id, event.target.value)
|
|
3363
|
+
})
|
|
3364
|
+
]
|
|
3365
|
+
}, fragment.id))
|
|
3366
|
+
})]
|
|
3367
|
+
}, location))]
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
//#endregion
|
|
3371
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorTextView.tsx
|
|
3372
|
+
function AuthorTextView({ sessionId, document, preview, displayedRegions }) {
|
|
3373
|
+
const host = useRef(null);
|
|
3374
|
+
const editor = useRef(null);
|
|
3375
|
+
useEffect(() => registerAuthorGridResolver(sessionId, (cell, geometry) => {
|
|
3376
|
+
if (preview || editor.current === null) return void 0;
|
|
3377
|
+
const { originX = 0, originY = 0, width, height } = geometry.viewport;
|
|
3378
|
+
const range = editor.current.resolveViewportRegion({
|
|
3379
|
+
left: originX + cell.rect.x * width,
|
|
3380
|
+
top: originY + cell.rect.y * height,
|
|
3381
|
+
right: originX + (cell.rect.x + cell.rect.width) * width,
|
|
3382
|
+
bottom: originY + (cell.rect.y + cell.rect.height) * height
|
|
3383
|
+
});
|
|
3384
|
+
if (range === null || range.from === range.to) return void 0;
|
|
3385
|
+
return {
|
|
3386
|
+
anchor: {
|
|
3387
|
+
kind: "text-range",
|
|
3388
|
+
startOffset: range.from,
|
|
3389
|
+
endOffset: range.to,
|
|
3390
|
+
startLine: range.startLine,
|
|
3391
|
+
endLine: range.endLine
|
|
3392
|
+
},
|
|
3393
|
+
quote: range.text
|
|
3394
|
+
};
|
|
3395
|
+
}), [preview, sessionId]);
|
|
3396
|
+
useEffect(() => {
|
|
3397
|
+
editor.current?.setMarkedRanges(displayedRegions.flatMap(({ ordinal, region }) => region.anchor.kind === "text-range" ? [{
|
|
3398
|
+
from: region.anchor.startOffset,
|
|
3399
|
+
to: region.anchor.endOffset,
|
|
3400
|
+
label: String(ordinal)
|
|
3401
|
+
}] : []));
|
|
3402
|
+
}, [displayedRegions]);
|
|
3403
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3404
|
+
ref: host,
|
|
3405
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
3406
|
+
children: preview ? /* @__PURE__ */ jsx("div", {
|
|
3407
|
+
"data-testid": "author-markdown",
|
|
3408
|
+
className: "min-h-0 flex-1 overflow-auto p-4 text-sm text-doom-text",
|
|
3409
|
+
children: /* @__PURE__ */ jsx(Markdown, { text: document.content ?? "" })
|
|
3410
|
+
}) : /* @__PURE__ */ jsx(CodeEditor, {
|
|
3411
|
+
value: document.content ?? "",
|
|
3412
|
+
path: document.path,
|
|
3413
|
+
"data-testid": "author-editor",
|
|
3414
|
+
controllerRef: editor,
|
|
3415
|
+
className: "min-h-0 flex-1",
|
|
3416
|
+
onChange: (content) => reviseAuthorDocument(sessionId, document.path, content),
|
|
3417
|
+
onSelect: (range) => {
|
|
3418
|
+
const current = authorDocument(sessionId, document.path);
|
|
3419
|
+
const bounds = host.current?.getBoundingClientRect();
|
|
3420
|
+
if (!current || !bounds || range.from === range.to) return;
|
|
3421
|
+
setAuthorRegionCandidate(sessionId, {
|
|
3422
|
+
documentPath: current.path,
|
|
3423
|
+
revision: current.version,
|
|
3424
|
+
sourceSha256: current.sourceSha256,
|
|
3425
|
+
quote: range.text,
|
|
3426
|
+
anchor: {
|
|
3427
|
+
kind: "text-range",
|
|
3428
|
+
startOffset: range.from,
|
|
3429
|
+
endOffset: range.to,
|
|
3430
|
+
startLine: range.startLine,
|
|
3431
|
+
endLine: range.endLine
|
|
3432
|
+
},
|
|
3433
|
+
viewport: {
|
|
3434
|
+
width: bounds.width,
|
|
3435
|
+
height: bounds.height
|
|
3436
|
+
},
|
|
3437
|
+
createdAt: Date.now()
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
})
|
|
3441
|
+
});
|
|
3442
|
+
}
|
|
3443
|
+
//#endregion
|
|
3444
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/AuthorDocumentPanel.tsx
|
|
3445
|
+
function tabId(path) {
|
|
3446
|
+
let hash = 2166136261;
|
|
3447
|
+
for (const char of normalizeAuthorPath(path)) hash = Math.imul(hash ^ char.charCodeAt(0), 16777619);
|
|
3448
|
+
return `author-file-${(hash >>> 0).toString(36)}`;
|
|
3449
|
+
}
|
|
3450
|
+
function displayedAuthorRegions(workspace) {
|
|
3451
|
+
const draftRegions = workspace?.regions ?? [];
|
|
3452
|
+
if (draftRegions.length > 0) return draftRegions.map((region, index) => ({
|
|
3453
|
+
ordinal: index + 1,
|
|
3454
|
+
region
|
|
3455
|
+
}));
|
|
3456
|
+
const activeRequest = workspace?.requests.slice().reverse().find((request) => request.status === "REQUESTED" || request.status === "CHANGING");
|
|
3457
|
+
return (activeRequest?.pendingRegions ?? []).map((region) => ({
|
|
3458
|
+
ordinal: Math.max(1, (activeRequest?.regions.findIndex((original) => original.id === region.id) ?? 0) + 1),
|
|
3459
|
+
region
|
|
3460
|
+
}));
|
|
3461
|
+
}
|
|
3462
|
+
function AuthorDocumentPanel(props) {
|
|
3463
|
+
if (!props.activeMinorModes?.includes("author")) {
|
|
3464
|
+
const fallback = props.fileTabFor(props.path);
|
|
3465
|
+
if (fallback !== void 0 && fallback.id !== tabId(props.path)) return /* @__PURE__ */ jsx(fallback.panel, { ...props });
|
|
3466
|
+
return /* @__PURE__ */ jsx("p", {
|
|
3467
|
+
"data-testid": "author-mode-inactive",
|
|
3468
|
+
className: "p-3 text-sm text-doom-dim",
|
|
3469
|
+
children: "No file viewer is available for this path."
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3472
|
+
return /* @__PURE__ */ jsx(ActiveAuthorDocumentPanel, { ...props });
|
|
3473
|
+
}
|
|
3474
|
+
function ActiveAuthorDocumentPanel(props) {
|
|
3475
|
+
const { path, sessionId, statuses } = props;
|
|
3476
|
+
const document = useStore(authorWorkspace.store, (state) => sessionId === null ? void 0 : state.documents[authorDocumentKey(sessionId, path)]);
|
|
3477
|
+
const focusGeneration = useRef(void 0);
|
|
3478
|
+
const workspace = useStore(authorWorkspace.store, (state) => sessionId === null ? void 0 : state.sessions[sessionId]);
|
|
3479
|
+
const activeTool = workspace?.activeTool ?? "select";
|
|
3480
|
+
const [markdownPreview, setMarkdownPreview] = useState(true);
|
|
3481
|
+
const [status, setStatus] = useState();
|
|
3482
|
+
const [previewOpen, setPreviewOpen] = useState(false);
|
|
3483
|
+
const kind = document?.kind;
|
|
3484
|
+
const previewSource = document === void 0 ? void 0 : {
|
|
3485
|
+
path: document.path,
|
|
3486
|
+
kind: document.kind,
|
|
3487
|
+
hasUnsavedChanges: document.version !== document.savedVersion,
|
|
3488
|
+
revision: document.savedVersion,
|
|
3489
|
+
sourceSha256: document.sourceSha256
|
|
3490
|
+
};
|
|
3491
|
+
const PreviewPanel = (previewSource === void 0 ? void 0 : (props.slotData?.(authorPreviewActionSlot) ?? []).find(({ data }) => data.embeddedPanel !== void 0 && (data.supportsSource === void 0 || data.supportsSource(previewSource))))?.data.embeddedPanel;
|
|
3492
|
+
useEffect(() => {
|
|
3493
|
+
if (document !== void 0 || sessionId === null) return;
|
|
3494
|
+
const controller = new AbortController();
|
|
3495
|
+
setStatus("loading");
|
|
3496
|
+
loadAuthorDocument(sessionId, path, controller.signal).then((loaded) => {
|
|
3497
|
+
if (controller.signal.aborted) return;
|
|
3498
|
+
putAuthorDocument(sessionId, loaded);
|
|
3499
|
+
setStatus(void 0);
|
|
3500
|
+
}).catch((error) => {
|
|
3501
|
+
if (!controller.signal.aborted) setStatus(error instanceof Error ? error.message : String(error));
|
|
3502
|
+
});
|
|
3503
|
+
return () => controller.abort();
|
|
3504
|
+
}, [
|
|
3505
|
+
document,
|
|
3506
|
+
path,
|
|
3507
|
+
sessionId
|
|
3508
|
+
]);
|
|
3509
|
+
useEffect(() => {
|
|
3510
|
+
if (document === void 0 || sessionId === null) return;
|
|
3511
|
+
const generation = focusAuthorDocument(sessionId, path, document.version, document.sourceSha256);
|
|
3512
|
+
focusGeneration.current = generation;
|
|
3513
|
+
return () => releaseAuthorDocumentFocus(sessionId, generation);
|
|
3514
|
+
}, [
|
|
3515
|
+
kind,
|
|
3516
|
+
document?.sourceSha256,
|
|
3517
|
+
path,
|
|
3518
|
+
sessionId
|
|
3519
|
+
]);
|
|
3520
|
+
useEffect(() => {
|
|
3521
|
+
const generation = focusGeneration.current;
|
|
3522
|
+
if (document === void 0 || sessionId === null || generation === void 0) return;
|
|
3523
|
+
syncAuthorDocumentFocus(sessionId, generation, document.version, document.sourceSha256);
|
|
3524
|
+
}, [
|
|
3525
|
+
document?.sourceSha256,
|
|
3526
|
+
document?.version,
|
|
3527
|
+
sessionId
|
|
3528
|
+
]);
|
|
3529
|
+
useEffect(() => {
|
|
3530
|
+
if (kind === void 0 || sessionId === null) return;
|
|
3531
|
+
const profiles = authorProfilesForDocument(sessionId, path, kind);
|
|
3532
|
+
let release;
|
|
3533
|
+
let cancelled = false;
|
|
3534
|
+
focusAuthorViewport(sessionId, profiles).then((nextRelease) => {
|
|
3535
|
+
if (cancelled) nextRelease();
|
|
3536
|
+
else release = nextRelease;
|
|
3537
|
+
});
|
|
3538
|
+
return () => {
|
|
3539
|
+
cancelled = true;
|
|
3540
|
+
release?.();
|
|
3541
|
+
};
|
|
3542
|
+
}, [
|
|
3543
|
+
kind,
|
|
3544
|
+
path,
|
|
3545
|
+
sessionId
|
|
3546
|
+
]);
|
|
3547
|
+
if (document === void 0 || sessionId === null) return /* @__PURE__ */ jsx("p", {
|
|
3548
|
+
className: "p-4 text-sm text-doom-faint",
|
|
3549
|
+
children: status ?? "Loading document..."
|
|
3550
|
+
});
|
|
3551
|
+
const save = async () => {
|
|
3552
|
+
const savedVersion = document.version;
|
|
3553
|
+
const savedFragments = document.fragments;
|
|
3554
|
+
requestAuthorSave(sessionId, path);
|
|
3555
|
+
setStatus("saving");
|
|
3556
|
+
try {
|
|
3557
|
+
const sha256 = await saveAuthorDocument(sessionId, document);
|
|
3558
|
+
completeAuthorSave(sessionId, path, sha256, savedVersion, savedFragments);
|
|
3559
|
+
setStatus("saved");
|
|
3560
|
+
} catch (error) {
|
|
3561
|
+
failAuthorSave(sessionId, path, savedVersion);
|
|
3562
|
+
setStatus(error instanceof Error ? error.message : String(error));
|
|
3563
|
+
}
|
|
3564
|
+
};
|
|
3565
|
+
const gridVisible = autonomousVoiceGridVisible(statuses);
|
|
3566
|
+
const displayedRegions = displayedAuthorRegions(workspace);
|
|
3567
|
+
const displayedPreviewAnnotations = displayedRegions.flatMap(({ ordinal, region }) => {
|
|
3568
|
+
if (region.anchor.kind === "story-preview-rect") return [{
|
|
3569
|
+
ordinal,
|
|
3570
|
+
mode: "region",
|
|
3571
|
+
point: region.anchor.rect,
|
|
3572
|
+
rect: region.anchor.rect
|
|
3573
|
+
}];
|
|
3574
|
+
if (region.anchor.kind === "story-preview-point") return [{
|
|
3575
|
+
ordinal,
|
|
3576
|
+
mode: "point",
|
|
3577
|
+
point: region.anchor.point
|
|
3578
|
+
}];
|
|
3579
|
+
return [];
|
|
3580
|
+
});
|
|
3581
|
+
const textPanel = /* @__PURE__ */ jsx(AuthorTextView, {
|
|
3582
|
+
sessionId,
|
|
3583
|
+
document,
|
|
3584
|
+
preview: document.kind === "markdown" && markdownPreview && activeTool === "select" && !gridVisible,
|
|
3585
|
+
displayedRegions
|
|
3586
|
+
});
|
|
3587
|
+
const sourcePanel = document.kind === "text" || document.kind === "markdown" || document.kind === "story-preview" ? textPanel : document.structuredFormat !== void 0 ? /* @__PURE__ */ jsx(AuthorStructuredView, {
|
|
3588
|
+
sessionId,
|
|
3589
|
+
document,
|
|
3590
|
+
displayedRegions
|
|
3591
|
+
}) : /* @__PURE__ */ jsx(AuthorMediaView, {
|
|
3592
|
+
sessionId,
|
|
3593
|
+
document,
|
|
3594
|
+
activeTool,
|
|
3595
|
+
displayedRegions,
|
|
3596
|
+
pendingCandidate: workspace?.candidate !== void 0,
|
|
3597
|
+
seekRequest: workspace?.videoSeekRequest
|
|
3598
|
+
}, document.path);
|
|
3599
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
3600
|
+
"data-testid": "author-document",
|
|
3601
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
3602
|
+
children: [
|
|
3603
|
+
/* @__PURE__ */ jsxs("header", {
|
|
3604
|
+
className: "flex items-center gap-2 border-b border-doom-border px-4 py-2",
|
|
3605
|
+
children: [
|
|
3606
|
+
/* @__PURE__ */ jsx("strong", {
|
|
3607
|
+
className: "min-w-0 flex-1 truncate text-base text-doom-hi",
|
|
3608
|
+
children: document.title ?? document.path
|
|
3609
|
+
}),
|
|
3610
|
+
PreviewPanel !== void 0 && previewSource !== void 0 ? /* @__PURE__ */ jsx(Button, {
|
|
3611
|
+
size: "xs",
|
|
3612
|
+
variant: previewOpen ? "primary" : "outline",
|
|
3613
|
+
"data-testid": "author-story-preview-toggle",
|
|
3614
|
+
"aria-pressed": previewOpen,
|
|
3615
|
+
"aria-label": previewOpen ? "show story source" : "show story preview",
|
|
3616
|
+
onClick: () => setPreviewOpen(!previewOpen),
|
|
3617
|
+
children: previewOpen ? "Source" : "Preview"
|
|
3618
|
+
}) : null,
|
|
3619
|
+
document.kind === "markdown" ? /* @__PURE__ */ jsx(Button, {
|
|
3620
|
+
size: "xs",
|
|
3621
|
+
variant: "ghost",
|
|
3622
|
+
"data-testid": "author-markdown-toggle",
|
|
3623
|
+
onClick: () => setMarkdownPreview(!markdownPreview),
|
|
3624
|
+
children: markdownPreview ? "edit" : "preview"
|
|
3625
|
+
}) : null,
|
|
3626
|
+
document.kind === "video" ? /* @__PURE__ */ jsx("span", {
|
|
3627
|
+
className: "text-base text-doom-dim",
|
|
3628
|
+
children: "Video feedback"
|
|
3629
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
3630
|
+
className: "min-h-11 px-4 text-base",
|
|
3631
|
+
variant: "outline",
|
|
3632
|
+
"data-testid": "author-save",
|
|
3633
|
+
disabled: document.revisions.length === 0 || document.savingVersion !== void 0,
|
|
3634
|
+
onClick: () => void save(),
|
|
3635
|
+
children: previewOpen && previewSource?.hasUnsavedChanges ? "Save & refresh" : "Save"
|
|
3636
|
+
})
|
|
3637
|
+
]
|
|
3638
|
+
}),
|
|
3639
|
+
status === void 0 ? null : /* @__PURE__ */ jsx("output", {
|
|
3640
|
+
className: "px-4 py-1 text-xs text-doom-faint",
|
|
3641
|
+
children: status
|
|
3642
|
+
}),
|
|
3643
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3644
|
+
className: "relative flex min-h-0 flex-1 overflow-hidden",
|
|
3645
|
+
children: [previewOpen && PreviewPanel !== void 0 && previewSource !== void 0 ? /* @__PURE__ */ jsx(PreviewPanel, {
|
|
3646
|
+
...props,
|
|
3647
|
+
source: previewSource,
|
|
3648
|
+
activeTool: activeTool === "crop" ? "select" : activeTool,
|
|
3649
|
+
displayedAnnotations: displayedPreviewAnnotations,
|
|
3650
|
+
pendingCandidate: workspace?.candidate !== void 0,
|
|
3651
|
+
onAnnotationCandidate: (candidate) => {
|
|
3652
|
+
setAuthorStoryPreview(sessionId, document.path, candidate.preview);
|
|
3653
|
+
setAuthorRegionCandidate(sessionId, {
|
|
3654
|
+
documentPath: document.path,
|
|
3655
|
+
revision: document.version,
|
|
3656
|
+
sourceSha256: document.sourceSha256,
|
|
3657
|
+
mode: candidate.mode,
|
|
3658
|
+
anchor: candidate.mode === "region" && candidate.rect !== void 0 ? {
|
|
3659
|
+
kind: "story-preview-rect",
|
|
3660
|
+
rect: candidate.rect,
|
|
3661
|
+
preview: candidate.preview
|
|
3662
|
+
} : {
|
|
3663
|
+
kind: "story-preview-point",
|
|
3664
|
+
point: candidate.point,
|
|
3665
|
+
preview: candidate.preview
|
|
3666
|
+
},
|
|
3667
|
+
viewport: candidate.preview.viewport,
|
|
3668
|
+
evidence: candidate.evidence,
|
|
3669
|
+
thumbnailUrl: candidate.thumbnailUrl,
|
|
3670
|
+
createdAt: Date.now()
|
|
3671
|
+
});
|
|
3672
|
+
}
|
|
3673
|
+
}) : sourcePanel, previewOpen && PreviewPanel !== void 0 && previewSource !== void 0 ? null : /* @__PURE__ */ jsx(AuthorGridOverlay, {
|
|
3674
|
+
sessionId,
|
|
3675
|
+
document,
|
|
3676
|
+
visible: gridVisible
|
|
3677
|
+
})]
|
|
3678
|
+
})
|
|
3679
|
+
]
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
function authorFileTab(path) {
|
|
3683
|
+
const normalized = normalizeAuthorPath(path);
|
|
3684
|
+
return {
|
|
3685
|
+
id: tabId(normalized),
|
|
3686
|
+
label: normalized.split("/").at(-1) ?? normalized,
|
|
3687
|
+
retainComposer: true,
|
|
3688
|
+
panel: (props) => /* @__PURE__ */ jsx(AuthorDocumentPanel, {
|
|
3689
|
+
...props,
|
|
3690
|
+
path: normalized
|
|
3691
|
+
})
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3694
|
+
//#endregion
|
|
3695
|
+
//#region src/extensions/workspaces/sessions/(frontend)/file-links/author-document.web.ts
|
|
3696
|
+
var author_document_web_default = defineFileLinks({
|
|
3697
|
+
requiredMinorMode: "author",
|
|
3698
|
+
subscribe(listener) {
|
|
3699
|
+
const subscription = authorWorkspace.store.subscribe(listener);
|
|
3700
|
+
return () => subscription.unsubscribe();
|
|
3701
|
+
},
|
|
3702
|
+
fingerprint(sessionId) {
|
|
3703
|
+
if (sessionId === null) return "";
|
|
3704
|
+
const prefix = `${sessionId}\n`;
|
|
3705
|
+
return Object.keys(authorWorkspace.store.state.documents).filter((key) => key.startsWith(prefix)).sort().join("\n");
|
|
3706
|
+
},
|
|
3707
|
+
resolve(sessionId, path) {
|
|
3708
|
+
if (sessionId === null) return void 0;
|
|
3709
|
+
const normalized = normalizeAuthorPath(path.replace(/:\d+(?::\d+)?$/, ""));
|
|
3710
|
+
return authorWorkspace.store.state.documents[authorDocumentKey(sessionId, normalized)] === void 0 ? void 0 : authorFileTab(normalized);
|
|
3711
|
+
},
|
|
3712
|
+
openPath(sessionId, path) {
|
|
3713
|
+
if (sessionId === null) return void 0;
|
|
3714
|
+
const normalized = normalizeAuthorPath(path);
|
|
3715
|
+
return normalized === "" ? void 0 : authorFileTab(normalized);
|
|
3716
|
+
}
|
|
3717
|
+
});
|
|
3718
|
+
//#endregion
|
|
3719
|
+
//#region src/extensions/workspaces/sessions/(frontend)/leader/author-toggle.web.ts
|
|
3720
|
+
var author_toggle_web_default = defineLeaderBinding({
|
|
3721
|
+
id: "author.toggle",
|
|
3722
|
+
path: [{
|
|
3723
|
+
key: "o",
|
|
3724
|
+
label: "other",
|
|
3725
|
+
detail: "optional modes and tools"
|
|
3726
|
+
}, {
|
|
3727
|
+
key: "a",
|
|
3728
|
+
label: "author",
|
|
3729
|
+
detail: "enable or disable focused document authoring"
|
|
3730
|
+
}],
|
|
3731
|
+
command: "minor author"
|
|
3732
|
+
});
|
|
3733
|
+
//#endregion
|
|
3734
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorAnnotationPersistence.ts
|
|
3735
|
+
const AUTHOR_ANNOTATION_DATABASE = "doompi-author-annotations";
|
|
3736
|
+
const STORE = "documents";
|
|
3737
|
+
const RECORD_VERSION = 1;
|
|
3738
|
+
function object(value) {
|
|
3739
|
+
return typeof value === "object" && value !== null;
|
|
3740
|
+
}
|
|
3741
|
+
function finite(value) {
|
|
3742
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
3743
|
+
}
|
|
3744
|
+
function point(value) {
|
|
3745
|
+
return object(value) && finite(value.x) && finite(value.y) && value.x >= 0 && value.x <= 1 && value.y >= 0 && value.y <= 1;
|
|
3746
|
+
}
|
|
3747
|
+
function rect(value) {
|
|
3748
|
+
return point(value) && object(value) && finite(value.width) && finite(value.height) && value.width > 0 && value.height > 0;
|
|
3749
|
+
}
|
|
3750
|
+
function validAnchor(value) {
|
|
3751
|
+
if (!object(value) || typeof value.kind !== "string") return false;
|
|
3752
|
+
switch (value.kind) {
|
|
3753
|
+
case "text-range": return finite(value.startOffset) && finite(value.endOffset) && finite(value.startLine) && finite(value.endLine);
|
|
3754
|
+
case "cell": return typeof value.fragmentId === "string" && typeof value.location === "string";
|
|
3755
|
+
case "slide-element": return typeof value.fragmentId === "string" && finite(value.slide) && typeof value.location === "string";
|
|
3756
|
+
case "image-rect": return rect(value.rect) && finite(value.naturalWidth) && finite(value.naturalHeight);
|
|
3757
|
+
case "pdf-page-rect": return finite(value.page) && rect(value.rect);
|
|
3758
|
+
case "video-time-rect": return finite(value.timeSeconds) && rect(value.rect);
|
|
3759
|
+
case "story-preview-rect": return rect(value.rect) && object(value.preview);
|
|
3760
|
+
case "image-point": return point(value.point) && finite(value.naturalWidth) && finite(value.naturalHeight);
|
|
3761
|
+
case "pdf-page-point": return finite(value.page) && point(value.point);
|
|
3762
|
+
case "video-time-point": return finite(value.timeSeconds) && point(value.point);
|
|
3763
|
+
case "story-preview-point": return point(value.point) && object(value.preview);
|
|
3764
|
+
default: return false;
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
function validEvidence(value) {
|
|
3768
|
+
return value === void 0 || typeof Blob !== "undefined" && value instanceof Blob && value.size <= 8388608;
|
|
3769
|
+
}
|
|
3770
|
+
function validCandidate(value) {
|
|
3771
|
+
if (!object(value)) return false;
|
|
3772
|
+
return typeof value.documentPath === "string" && normalizeAuthorPath(value.documentPath) !== "" && finite(value.revision) && (value.sourceSha256 === void 0 || typeof value.sourceSha256 === "string") && (value.mode === void 0 || value.mode === "region" || value.mode === "point") && validAnchor(value.anchor) && object(value.viewport) && finite(value.viewport.width) && finite(value.viewport.height) && finite(value.createdAt) && validEvidence(value.evidence);
|
|
3773
|
+
}
|
|
3774
|
+
function validAnnotation(value) {
|
|
3775
|
+
return validCandidate(value) && object(value) && typeof value.id === "string" && value.id !== "" && typeof value.comment === "string" && value.comment.trim() !== "" && (value.version === void 0 || Number.isInteger(value.version) && value.version >= 1);
|
|
3776
|
+
}
|
|
3777
|
+
function utf8Length(value) {
|
|
3778
|
+
return new TextEncoder().encode(value).byteLength;
|
|
3779
|
+
}
|
|
3780
|
+
function validateAuthorAnnotationRecord(input) {
|
|
3781
|
+
if (!object(input) || input.version !== RECORD_VERSION || typeof input.key !== "string") return void 0;
|
|
3782
|
+
if (typeof input.sessionId !== "string" || input.sessionId === "" || typeof input.documentPath !== "string") return void 0;
|
|
3783
|
+
const path = normalizeAuthorPath(input.documentPath);
|
|
3784
|
+
if (path === "" || input.key !== `${input.sessionId}\n${path}` || !object(input.collection)) return void 0;
|
|
3785
|
+
const collection = input.collection;
|
|
3786
|
+
if (!finite(collection.revision) || collection.sourceSha256 !== void 0 && typeof collection.sourceSha256 !== "string" || typeof collection.stale !== "boolean" || typeof collection.candidateText !== "string" || utf8Length(collection.candidateText) > 2048 || !Array.isArray(collection.annotations) || collection.annotations.length > 16 || !collection.annotations.every(validAnnotation) || collection.candidate !== void 0 && !validCandidate(collection.candidate) || !finite(collection.updatedAt)) return void 0;
|
|
3787
|
+
return input;
|
|
3788
|
+
}
|
|
3789
|
+
function durableCandidate(candidate) {
|
|
3790
|
+
const { thumbnailUrl: _thumbnailUrl, ...durable } = candidate;
|
|
3791
|
+
return durable;
|
|
3792
|
+
}
|
|
3793
|
+
function persistenceRecords() {
|
|
3794
|
+
const records = [];
|
|
3795
|
+
for (const [sessionId, session] of Object.entries(authorWorkspace.store.state.sessions)) for (const [documentPath, collection] of Object.entries(session.annotationsByDocument)) {
|
|
3796
|
+
if (collection.annotations.length === 0 && collection.candidate === void 0 && collection.candidateText === "") continue;
|
|
3797
|
+
records.push({
|
|
3798
|
+
key: `${sessionId}\n${documentPath}`,
|
|
3799
|
+
version: RECORD_VERSION,
|
|
3800
|
+
sessionId,
|
|
3801
|
+
documentPath,
|
|
3802
|
+
collection: {
|
|
3803
|
+
...collection,
|
|
3804
|
+
candidate: collection.candidate && durableCandidate(collection.candidate),
|
|
3805
|
+
annotations: collection.annotations.map(durableCandidate)
|
|
3806
|
+
}
|
|
3807
|
+
});
|
|
3808
|
+
}
|
|
3809
|
+
return records.sort((a, b) => b.collection.updatedAt - a.collection.updatedAt).slice(0, 100);
|
|
3810
|
+
}
|
|
3811
|
+
var IndexedDbAuthorAnnotationRepository = class {
|
|
3812
|
+
#database;
|
|
3813
|
+
#writes = Promise.resolve();
|
|
3814
|
+
constructor(factory) {
|
|
3815
|
+
this.#database = new Promise((resolve, reject) => {
|
|
3816
|
+
const request = factory.open(AUTHOR_ANNOTATION_DATABASE, 1);
|
|
3817
|
+
request.onupgradeneeded = () => {
|
|
3818
|
+
if (!request.result.objectStoreNames.contains(STORE)) request.result.createObjectStore(STORE, { keyPath: "key" });
|
|
3819
|
+
};
|
|
3820
|
+
request.onsuccess = () => resolve(request.result);
|
|
3821
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("Unable to open Author annotation storage."));
|
|
3822
|
+
});
|
|
3823
|
+
}
|
|
3824
|
+
async load() {
|
|
3825
|
+
const database = await this.#database;
|
|
3826
|
+
return await new Promise((resolve, reject) => {
|
|
3827
|
+
const request = database.transaction(STORE, "readonly").objectStore(STORE).getAll();
|
|
3828
|
+
request.onsuccess = () => resolve(request.result);
|
|
3829
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("Unable to read Author annotations."));
|
|
3830
|
+
});
|
|
3831
|
+
}
|
|
3832
|
+
replace(records) {
|
|
3833
|
+
const write = async () => {
|
|
3834
|
+
const database = await this.#database;
|
|
3835
|
+
await new Promise((resolve, reject) => {
|
|
3836
|
+
const transaction = database.transaction(STORE, "readwrite");
|
|
3837
|
+
const store = transaction.objectStore(STORE);
|
|
3838
|
+
store.clear();
|
|
3839
|
+
records.forEach((record) => store.put(record));
|
|
3840
|
+
transaction.oncomplete = () => resolve();
|
|
3841
|
+
transaction.onerror = () => reject(transaction.error ?? /* @__PURE__ */ new Error("Unable to write Author annotations."));
|
|
3842
|
+
transaction.onabort = () => reject(transaction.error ?? /* @__PURE__ */ new Error("Author annotation write was aborted."));
|
|
3843
|
+
});
|
|
3844
|
+
};
|
|
3845
|
+
this.#writes = this.#writes.catch(() => void 0).then(write);
|
|
3846
|
+
return this.#writes;
|
|
3847
|
+
}
|
|
3848
|
+
async close() {
|
|
3849
|
+
try {
|
|
3850
|
+
await this.#writes;
|
|
3851
|
+
} finally {
|
|
3852
|
+
(await this.#database).close();
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
};
|
|
3856
|
+
function startAuthorAnnotationPersistence(repository) {
|
|
3857
|
+
const factory = typeof indexedDB === "undefined" ? void 0 : indexedDB;
|
|
3858
|
+
if (repository === void 0 && factory === void 0) return async () => void 0;
|
|
3859
|
+
const storage = repository ?? new IndexedDbAuthorAnnotationRepository(factory);
|
|
3860
|
+
let stopped = false;
|
|
3861
|
+
let hydrated = false;
|
|
3862
|
+
let dirty = false;
|
|
3863
|
+
const subscription = authorWorkspace.store.subscribe(() => {
|
|
3864
|
+
if (!hydrated) dirty = true;
|
|
3865
|
+
else storage.replace(persistenceRecords()).catch(() => void 0);
|
|
3866
|
+
});
|
|
3867
|
+
storage.load().then((values) => {
|
|
3868
|
+
if (stopped) return;
|
|
3869
|
+
const records = values.map(validateAuthorAnnotationRecord).filter((record) => record !== void 0).sort((a, b) => b.collection.updatedAt - a.collection.updatedAt).slice(0, 100);
|
|
3870
|
+
hydrateAuthorAnnotationCollections(records);
|
|
3871
|
+
hydrated = true;
|
|
3872
|
+
if (dirty || records.length > 0) storage.replace(persistenceRecords()).catch(() => void 0);
|
|
3873
|
+
}).catch(() => {
|
|
3874
|
+
if (stopped) return;
|
|
3875
|
+
hydrated = true;
|
|
3876
|
+
if (dirty) storage.replace(persistenceRecords()).catch(() => void 0);
|
|
3877
|
+
});
|
|
3878
|
+
return async () => {
|
|
3879
|
+
stopped = true;
|
|
3880
|
+
subscription.unsubscribe();
|
|
3881
|
+
await storage.close();
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
//#endregion
|
|
3885
|
+
//#region src/extensions/workspaces/sessions/(frontend)/lifecycle/_lib/authorRequestLifecycle.ts
|
|
3886
|
+
function capturePacket(content) {
|
|
3887
|
+
try {
|
|
3888
|
+
const value = JSON.parse(content);
|
|
3889
|
+
if (value.version !== 1 || typeof value.captureId !== "string" || typeof value.capturedAt !== "number" || typeof value.document?.path !== "string" || typeof value.document.revision !== "number" || !Array.isArray(value.regions) || value.regions.length === 0) return;
|
|
3890
|
+
return value;
|
|
3891
|
+
} catch {
|
|
3892
|
+
return;
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
function recordAuthorComposerSubmission(submission) {
|
|
3896
|
+
for (const item of submission.contextItems) {
|
|
3897
|
+
if (item.source !== "author" || item.kind !== "author-capture") continue;
|
|
3898
|
+
const packet = capturePacket(item.metadata ?? item.content);
|
|
3899
|
+
if (packet === void 0 || packet.captureId !== item.id) continue;
|
|
3900
|
+
if (authorSessionWorkspace(submission.sessionId).requests.some((request) => request.captureId === packet.captureId)) continue;
|
|
3901
|
+
const regions = packet.regions.map((region) => ({
|
|
3902
|
+
id: region.id,
|
|
3903
|
+
documentPath: packet.document.path,
|
|
3904
|
+
revision: packet.document.revision,
|
|
3905
|
+
sourceSha256: packet.document.sourceSha256,
|
|
3906
|
+
comment: region.comment,
|
|
3907
|
+
version: region.version ?? 1,
|
|
3908
|
+
mode: region.mode,
|
|
3909
|
+
quote: region.quote,
|
|
3910
|
+
anchor: structuredClone(region.anchor),
|
|
3911
|
+
viewport: { ...region.viewport },
|
|
3912
|
+
voiceGrid: region.voiceGrid && { ...region.voiceGrid },
|
|
3913
|
+
createdAt: packet.capturedAt
|
|
3914
|
+
}));
|
|
3915
|
+
putAuthorRequest(submission.sessionId, {
|
|
3916
|
+
id: crypto.randomUUID(),
|
|
3917
|
+
captureId: packet.captureId,
|
|
3918
|
+
documentPath: packet.document.path,
|
|
3919
|
+
requestText: submission.message,
|
|
3920
|
+
regions,
|
|
3921
|
+
pendingRegions: regions,
|
|
3922
|
+
status: "REQUESTED",
|
|
3923
|
+
createdAt: submission.submittedAt,
|
|
3924
|
+
updatedAt: submission.submittedAt,
|
|
3925
|
+
revision: packet.document.revision,
|
|
3926
|
+
sourceSha256: packet.document.sourceSha256
|
|
3927
|
+
});
|
|
3928
|
+
for (const region of packet.regions) removeAuthorRegion(submission.sessionId, region.id, region.version ?? 1, packet.document.path);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
/** Only the host's correlated execution events may settle a submitted capture. */
|
|
3932
|
+
function recordAuthorCaptureStatus(event) {
|
|
3933
|
+
const request = authorSessionWorkspace(event.sessionId).requests.find((record) => record.captureId === event.captureId);
|
|
3934
|
+
if (request === void 0 || [
|
|
3935
|
+
"COMPLETE",
|
|
3936
|
+
"FAILED",
|
|
3937
|
+
"CANCELLED"
|
|
3938
|
+
].includes(request.status)) return;
|
|
3939
|
+
if (event.status === "queued") return;
|
|
3940
|
+
updateAuthorRequest(event.sessionId, request.id, (record) => ({
|
|
3941
|
+
...record,
|
|
3942
|
+
status: event.status === "working" ? "CHANGING" : event.status === "completed" ? "COMPLETE" : "FAILED",
|
|
3943
|
+
currentOperation: event.status === "working" ? "Agent is working on this request" : void 0,
|
|
3944
|
+
pendingRegions: event.status === "completed" ? [] : record.pendingRegions,
|
|
3945
|
+
error: event.status === "error" ? event.error || "Request execution failed." : void 0,
|
|
3946
|
+
updatedAt: Date.now()
|
|
3947
|
+
}));
|
|
3948
|
+
}
|
|
3949
|
+
//#endregion
|
|
3950
|
+
//#region src/extensions/workspaces/sessions/(frontend)/lifecycle/_lib/authorBrowserLifecycle.ts
|
|
3951
|
+
function startAuthorBrowserLifecycle(runtime) {
|
|
3952
|
+
const stopBridge = startAuthorBrowserBridge(runtime);
|
|
3953
|
+
const stopPersistence = startAuthorAnnotationPersistence();
|
|
3954
|
+
const stopSubmissions = runtime.onComposerSubmitted?.(recordAuthorComposerSubmission) ?? (() => void 0);
|
|
3955
|
+
const stopCaptureStatus = runtime.onCaptureStatus?.(recordAuthorCaptureStatus) ?? (() => void 0);
|
|
3956
|
+
return async () => {
|
|
3957
|
+
stopSubmissions();
|
|
3958
|
+
stopCaptureStatus();
|
|
3959
|
+
stopBridge();
|
|
3960
|
+
await stopPersistence().catch(() => void 0);
|
|
3961
|
+
};
|
|
3962
|
+
}
|
|
3963
|
+
//#endregion
|
|
3964
|
+
//#region src/extensions/workspaces/sessions/(frontend)/lifecycle/start.web.ts
|
|
3965
|
+
var start_web_default = defineWebLifecycle(startAuthorBrowserLifecycle);
|
|
3966
|
+
//#endregion
|
|
3967
|
+
//#region src/extensions/workspaces/sessions/(frontend)/mode/author/mode.web.ts
|
|
3968
|
+
var mode_web_default = defineMinorModeFile({
|
|
3969
|
+
name: "author",
|
|
3970
|
+
keys: "o a",
|
|
3971
|
+
order: 45
|
|
3972
|
+
});
|
|
3973
|
+
//#endregion
|
|
3974
|
+
//#region src/extensions/workspaces/sessions/(frontend)/slot/preview-action.web.ts
|
|
3975
|
+
/** Typed data slot for preview launchers that need Author's focused source. */
|
|
3976
|
+
var preview_action_web_default = defineSlotFile({
|
|
3977
|
+
slot: "author.preview-action",
|
|
3978
|
+
parse: parseAuthorPreviewAction
|
|
3979
|
+
});
|
|
3980
|
+
//#endregion
|
|
3981
|
+
//#region src/extensions/workspaces/sessions/(frontend)/slot/preview-provider.web.ts
|
|
3982
|
+
/**
|
|
3983
|
+
* Optional preview providers render launch controls here. The provider owns its
|
|
3984
|
+
* transient tab and preview lifecycle; Author owns the surrounding mode and
|
|
3985
|
+
* composer submission surface.
|
|
3986
|
+
*/
|
|
3987
|
+
var preview_provider_web_default = defineSlotFile({});
|
|
3988
|
+
//#endregion
|
|
3989
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/describeAuthorToolsToolRender.ts
|
|
3990
|
+
function describeAuthorToolsCallSummary(_args) {
|
|
3991
|
+
return "";
|
|
3992
|
+
}
|
|
3993
|
+
/**
|
|
3994
|
+
* The body as toned lines: what the result's details say, or its text. Pure,
|
|
3995
|
+
* so a unit test can pin it without a DOM; the item decides how many to show.
|
|
3996
|
+
*/
|
|
3997
|
+
function describeAuthorToolsResultLines(result) {
|
|
3998
|
+
if (result === null) return [];
|
|
3999
|
+
return toolResultTextLines(result.content).map((text) => ({
|
|
4000
|
+
text,
|
|
4001
|
+
tone: "dim"
|
|
4002
|
+
}));
|
|
4003
|
+
}
|
|
4004
|
+
//#endregion
|
|
4005
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/DescribeAuthorToolsToolCard.tsx
|
|
4006
|
+
/**
|
|
4007
|
+
* The describe_author_tools tool's timeline item: Describe the current Author viewport capabilities. The shell owns
|
|
4008
|
+
* the frame, the outcome tone, the status badge, and the expand toggle; this
|
|
4009
|
+
* card supplies the header summary and the body lines, and says whether it
|
|
4010
|
+
* hides any so the toggle appears only when it reveals something.
|
|
4011
|
+
*/
|
|
4012
|
+
function DescribeAuthorToolsToolCard({ toolName, args, result, running, isError }) {
|
|
4013
|
+
const lines = describeAuthorToolsResultLines(result);
|
|
4014
|
+
const collapsed = collapseLines(lines, 8, false);
|
|
4015
|
+
return /* @__PURE__ */ jsx(MessageItem, {
|
|
4016
|
+
tone: toolTone({
|
|
4017
|
+
running,
|
|
4018
|
+
isError
|
|
4019
|
+
}),
|
|
4020
|
+
expandable: collapsed.hidden > 0,
|
|
4021
|
+
children: ({ expanded }) => {
|
|
4022
|
+
const view = expanded ? collapseLines(lines, 8, true) : collapsed;
|
|
4023
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
|
|
4024
|
+
title: toolName,
|
|
4025
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
4026
|
+
"data-testid": `tool-call-${toolName}`,
|
|
4027
|
+
className: "min-w-0 flex-1 truncate text-doom-text",
|
|
4028
|
+
children: describeAuthorToolsCallSummary(args)
|
|
4029
|
+
})
|
|
4030
|
+
}), view.shown.length > 0 || running || isError ? /* @__PURE__ */ jsxs(MessageItemBody, {
|
|
4031
|
+
"data-testid": `tool-result-${toolName}`,
|
|
4032
|
+
className: "flex flex-col gap-1",
|
|
4033
|
+
children: [
|
|
4034
|
+
view.shown.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: view.shown }) : null,
|
|
4035
|
+
view.hidden > 0 ? /* @__PURE__ */ jsxs(MessageItemStatus, {
|
|
4036
|
+
expands: true,
|
|
4037
|
+
children: [view.hidden, " more line(s)"]
|
|
4038
|
+
}) : null,
|
|
4039
|
+
running ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
4040
|
+
tone: "running",
|
|
4041
|
+
children: "running"
|
|
4042
|
+
}) : null,
|
|
4043
|
+
isError ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
4044
|
+
tone: "error",
|
|
4045
|
+
children: "failed"
|
|
4046
|
+
}) : null
|
|
4047
|
+
]
|
|
4048
|
+
}) : null] });
|
|
4049
|
+
}
|
|
4050
|
+
});
|
|
4051
|
+
}
|
|
4052
|
+
//#endregion
|
|
4053
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/describe-author-tools.web.tsx
|
|
4054
|
+
var describe_author_tools_web_default = defineToolRenderer({
|
|
4055
|
+
tools: ["describe_author_tools"],
|
|
4056
|
+
message: DescribeAuthorToolsToolCard
|
|
4057
|
+
});
|
|
4058
|
+
//#endregion
|
|
4059
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/OpenAuthoringFileToolCard.tsx
|
|
4060
|
+
function openAuthoringFileTab(path, openTransientTab, openTab) {
|
|
4061
|
+
const tab = authorFileTab(path);
|
|
4062
|
+
openTransientTab(tab);
|
|
4063
|
+
openTab(tab.id);
|
|
4064
|
+
}
|
|
4065
|
+
function resultPath(result) {
|
|
4066
|
+
if (typeof result?.details !== "object" || result.details === null || !("path" in result.details)) return void 0;
|
|
4067
|
+
return typeof result.details.path === "string" ? result.details.path : void 0;
|
|
4068
|
+
}
|
|
4069
|
+
function OpenAuthoringFileToolCard({ args, result, running, isError, output, openTransientTab, openTab }) {
|
|
4070
|
+
const path = resultPath(result);
|
|
4071
|
+
const unavailable = !running && !isError && result !== null && path === void 0;
|
|
4072
|
+
const errorLines = isError ? (output ?? "").trim().split(/\r?\n/u).filter(Boolean).slice(0, 4).map((text) => ({
|
|
4073
|
+
text,
|
|
4074
|
+
tone: "error"
|
|
4075
|
+
})) : [];
|
|
4076
|
+
const requestedPath = typeof args.path === "string" ? args.path : "document";
|
|
4077
|
+
return /* @__PURE__ */ jsx(MessageItem, {
|
|
4078
|
+
tone: toolTone({
|
|
4079
|
+
running,
|
|
4080
|
+
isError: isError || unavailable
|
|
4081
|
+
}),
|
|
4082
|
+
children: () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
|
|
4083
|
+
title: "open_authoring_file",
|
|
4084
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
4085
|
+
"data-testid": "tool-call-open_authoring_file",
|
|
4086
|
+
className: "min-w-0 flex-1 truncate text-doom-text",
|
|
4087
|
+
children: requestedPath
|
|
4088
|
+
})
|
|
4089
|
+
}), /* @__PURE__ */ jsxs(MessageItemBody, {
|
|
4090
|
+
"data-testid": "tool-result-open_authoring_file",
|
|
4091
|
+
className: "flex flex-col gap-1",
|
|
4092
|
+
children: [
|
|
4093
|
+
errorLines.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: errorLines }) : null,
|
|
4094
|
+
/* @__PURE__ */ jsx(MessageItemStatus, {
|
|
4095
|
+
tone: isError || unavailable ? "error" : running ? "running" : void 0,
|
|
4096
|
+
children: isError ? "failed" : running ? "validating" : unavailable ? "unavailable" : path === void 0 ? "waiting" : "ready"
|
|
4097
|
+
}),
|
|
4098
|
+
!running && !isError && path !== void 0 ? /* @__PURE__ */ jsx(Button, {
|
|
4099
|
+
size: "xs",
|
|
4100
|
+
variant: "outline",
|
|
4101
|
+
"data-testid": "author-open-file",
|
|
4102
|
+
onClick: () => openAuthoringFileTab(path, openTransientTab, openTab),
|
|
4103
|
+
children: "open in Author"
|
|
4104
|
+
}) : null
|
|
4105
|
+
]
|
|
4106
|
+
})] })
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4109
|
+
//#endregion
|
|
4110
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/open-authoring-file.web.tsx
|
|
4111
|
+
var open_authoring_file_web_default = defineToolRenderer({
|
|
4112
|
+
tools: ["open_authoring_file"],
|
|
4113
|
+
message: OpenAuthoringFileToolCard,
|
|
4114
|
+
completionTab: ({ result }) => {
|
|
4115
|
+
const path = resultPath(result);
|
|
4116
|
+
return path === void 0 ? void 0 : authorFileTab(path);
|
|
4117
|
+
}
|
|
4118
|
+
});
|
|
4119
|
+
//#endregion
|
|
4120
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/useAuthorToolsToolRender.ts
|
|
4121
|
+
function useAuthorToolsCallSummary(args) {
|
|
4122
|
+
return typeof args.name === "string" ? args.name : "";
|
|
4123
|
+
}
|
|
4124
|
+
/**
|
|
4125
|
+
* The body as toned lines: what the result's details say, or its text. Pure,
|
|
4126
|
+
* so a unit test can pin it without a DOM; the item decides how many to show.
|
|
4127
|
+
*/
|
|
4128
|
+
function useAuthorToolsResultLines(result) {
|
|
4129
|
+
if (result === null) return [];
|
|
4130
|
+
return toolResultTextLines(result.content).map((text) => ({
|
|
4131
|
+
text,
|
|
4132
|
+
tone: "dim"
|
|
4133
|
+
}));
|
|
4134
|
+
}
|
|
4135
|
+
//#endregion
|
|
4136
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/UseAuthorToolsToolCard.tsx
|
|
4137
|
+
/**
|
|
4138
|
+
* The use_author_tools tool's timeline item: Use one capability of the current Author viewport. The shell owns
|
|
4139
|
+
* the frame, the outcome tone, the status badge, and the expand toggle; this
|
|
4140
|
+
* card supplies the header summary and the body lines, and says whether it
|
|
4141
|
+
* hides any so the toggle appears only when it reveals something.
|
|
4142
|
+
*/
|
|
4143
|
+
function UseAuthorToolsToolCard({ toolName, args, result, running, isError }) {
|
|
4144
|
+
const lines = useAuthorToolsResultLines(result);
|
|
4145
|
+
const collapsed = collapseLines(lines, 8, false);
|
|
4146
|
+
return /* @__PURE__ */ jsx(MessageItem, {
|
|
4147
|
+
tone: toolTone({
|
|
4148
|
+
running,
|
|
4149
|
+
isError
|
|
4150
|
+
}),
|
|
4151
|
+
expandable: collapsed.hidden > 0,
|
|
4152
|
+
children: ({ expanded }) => {
|
|
4153
|
+
const view = expanded ? collapseLines(lines, 8, true) : collapsed;
|
|
4154
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
|
|
4155
|
+
title: toolName,
|
|
4156
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
4157
|
+
"data-testid": `tool-call-${toolName}`,
|
|
4158
|
+
className: "min-w-0 flex-1 truncate text-doom-text",
|
|
4159
|
+
children: useAuthorToolsCallSummary(args)
|
|
4160
|
+
})
|
|
4161
|
+
}), view.shown.length > 0 || running || isError ? /* @__PURE__ */ jsxs(MessageItemBody, {
|
|
4162
|
+
"data-testid": `tool-result-${toolName}`,
|
|
4163
|
+
className: "flex flex-col gap-1",
|
|
4164
|
+
children: [
|
|
4165
|
+
view.shown.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: view.shown }) : null,
|
|
4166
|
+
view.hidden > 0 ? /* @__PURE__ */ jsxs(MessageItemStatus, {
|
|
4167
|
+
expands: true,
|
|
4168
|
+
children: [view.hidden, " more line(s)"]
|
|
4169
|
+
}) : null,
|
|
4170
|
+
running ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
4171
|
+
tone: "running",
|
|
4172
|
+
children: "running"
|
|
4173
|
+
}) : null,
|
|
4174
|
+
isError ? /* @__PURE__ */ jsx(MessageItemStatus, {
|
|
4175
|
+
tone: "error",
|
|
4176
|
+
children: "failed"
|
|
4177
|
+
}) : null
|
|
4178
|
+
]
|
|
4179
|
+
}) : null] });
|
|
4180
|
+
}
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
4183
|
+
//#endregion
|
|
4184
|
+
//#region src/extensions/workspaces/sessions/(frontend)/tool/use-author-tools.web.tsx
|
|
4185
|
+
var use_author_tools_web_default = defineToolRenderer({
|
|
4186
|
+
tools: ["use_author_tools"],
|
|
4187
|
+
message: UseAuthorToolsToolCard
|
|
4188
|
+
});
|
|
4189
|
+
//#endregion
|
|
4190
|
+
//#region generated/web.ts
|
|
4191
|
+
const via = (identity, value) => ({
|
|
4192
|
+
...identity,
|
|
4193
|
+
...value
|
|
4194
|
+
});
|
|
4195
|
+
const webPlugin = defineWebPlugin({
|
|
4196
|
+
id: "author",
|
|
4197
|
+
session: {
|
|
4198
|
+
get channels() {
|
|
4199
|
+
return [via({ channel: "author_webmcp" }, author_webmcp_web_default)];
|
|
4200
|
+
},
|
|
4201
|
+
get dockFaces() {
|
|
4202
|
+
return [via({ id: "authoring" }, authoring_web_default)];
|
|
4203
|
+
},
|
|
4204
|
+
get fileLinks() {
|
|
4205
|
+
return author_document_web_default;
|
|
4206
|
+
},
|
|
4207
|
+
get leaderBindings() {
|
|
4208
|
+
return [via({ id: "author.author-toggle" }, author_toggle_web_default)];
|
|
4209
|
+
},
|
|
4210
|
+
get minorModes() {
|
|
4211
|
+
return [via({ name: "author" }, mode_web_default)];
|
|
4212
|
+
},
|
|
4213
|
+
get slots() {
|
|
4214
|
+
return [via({ slot: "author.preview-action" }, preview_action_web_default), via({ slot: "author.preview-provider" }, preview_provider_web_default)];
|
|
4215
|
+
},
|
|
4216
|
+
get start() {
|
|
4217
|
+
return start_web_default;
|
|
4218
|
+
},
|
|
4219
|
+
get toolRenderers() {
|
|
4220
|
+
return [
|
|
4221
|
+
via({ tools: ["describe_author_tools"] }, describe_author_tools_web_default),
|
|
4222
|
+
via({ tools: ["open_authoring_file"] }, open_authoring_file_web_default),
|
|
4223
|
+
via({ tools: ["use_author_tools"] }, use_author_tools_web_default)
|
|
4224
|
+
];
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
});
|
|
4228
|
+
//#endregion
|
|
4229
|
+
export { webPlugin };
|
|
4230
|
+
|
|
4231
|
+
//# sourceMappingURL=web.mjs.map
|