@agimon-ai/doompi-author 0.0.1-alpha.2

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