@ecsplico/editor 3.6.0 → 4.0.0

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 (578) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/dist/adapters/direct-ai.d.ts +4 -4
  3. package/dist/adapters/direct-ai.js +2 -2
  4. package/dist/adapters/direct-ai.js.map +1 -1
  5. package/dist/adapters/document-state.d.ts +1 -1
  6. package/dist/adapters/document-state.js.map +1 -1
  7. package/dist/adapters/forge.d.ts +2 -2
  8. package/dist/adapters/forge.js.map +1 -1
  9. package/dist/adapters/nitro.d.ts +11 -3
  10. package/dist/adapters/nitro.js +3 -1
  11. package/dist/adapters/nitro.js.map +1 -1
  12. package/dist/adapters/shared.d.ts +6 -6
  13. package/dist/adapters/shared.js.map +1 -1
  14. package/dist/config/index.d.ts +14 -14
  15. package/dist/config/index.js +8 -8
  16. package/dist/config/index.js.map +1 -1
  17. package/dist/core/backend-types.d.ts +3 -3
  18. package/dist/core/backend-types.js.map +1 -1
  19. package/dist/core/backend.d.ts +4 -4
  20. package/dist/core/backend.js.map +1 -1
  21. package/dist/core/block-convert.d.ts +1 -1
  22. package/dist/core/block-convert.js +5 -5
  23. package/dist/core/block-convert.js.map +1 -1
  24. package/dist/core/block-transfer.d.ts +8 -4
  25. package/dist/core/block-transfer.js +23 -14
  26. package/dist/core/block-transfer.js.map +1 -1
  27. package/dist/core/capabilities.d.ts +113 -0
  28. package/dist/core/capabilities.js +155 -0
  29. package/dist/core/capabilities.js.map +1 -0
  30. package/dist/core/command-types.d.ts +1 -1
  31. package/dist/core/context.js.map +1 -1
  32. package/dist/core/convert.d.ts +5 -5
  33. package/dist/core/convert.js +5 -5
  34. package/dist/core/convert.js.map +1 -1
  35. package/dist/core/core-feature.js +1 -1
  36. package/dist/core/define-editor.d.ts +2 -2
  37. package/dist/core/define-editor.js +34 -6
  38. package/dist/core/define-editor.js.map +1 -1
  39. package/dist/core/derivation.d.ts +119 -0
  40. package/dist/core/derivation.js +187 -0
  41. package/dist/core/derivation.js.map +1 -0
  42. package/dist/core/derive.d.ts +61 -0
  43. package/dist/core/derive.js +69 -0
  44. package/dist/core/derive.js.map +1 -0
  45. package/dist/core/extension-points.d.ts +16 -0
  46. package/dist/core/feature-context.d.ts +5 -1
  47. package/dist/core/feature-context.js +10 -3
  48. package/dist/core/feature-context.js.map +1 -1
  49. package/dist/core/feature.d.ts +41 -19
  50. package/dist/core/feature.js.map +1 -1
  51. package/dist/core/fences.js +1 -1
  52. package/dist/core/fences.js.map +1 -1
  53. package/dist/core/index.d.ts +6 -2
  54. package/dist/core/index.js +5 -1
  55. package/dist/core/pipeline.d.ts +14 -5
  56. package/dist/core/pipeline.js +19 -7
  57. package/dist/core/pipeline.js.map +1 -1
  58. package/dist/core/properties.d.ts +22 -0
  59. package/dist/core/properties.js +17 -0
  60. package/dist/core/properties.js.map +1 -0
  61. package/dist/core/schema/block-ids.js +5 -5
  62. package/dist/core/schema/block-ids.js.map +1 -1
  63. package/dist/core/schema/index.d.ts +2 -1
  64. package/dist/core/schema/index.js +2 -1
  65. package/dist/core/schema/index.js.map +1 -1
  66. package/dist/core/schema/opaque-fence.d.ts +4 -4
  67. package/dist/core/schema/opaque-fence.js +7 -7
  68. package/dist/core/schema/opaque-fence.js.map +1 -1
  69. package/dist/core/schema/persisted-names.d.ts +58 -0
  70. package/dist/core/schema/persisted-names.js +96 -0
  71. package/dist/core/schema/persisted-names.js.map +1 -0
  72. package/dist/core/toolbar-items.js +1 -1
  73. package/dist/core/toolbar-items.js.map +1 -1
  74. package/dist/core/util.js +5 -1
  75. package/dist/core/util.js.map +1 -1
  76. package/dist/features/ai/agent-ops.js +1 -1
  77. package/dist/features/ai/ai-review.js +1 -1
  78. package/dist/features/ai/assistant.d.ts +4 -4
  79. package/dist/features/ai/assistant.js +1 -1
  80. package/dist/features/ai/assistant.js.map +1 -1
  81. package/dist/features/ai/components/AiSelectionMenu.js +1 -1
  82. package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
  83. package/dist/features/ai/components/ModelQuickSwitch.js +7 -7
  84. package/dist/features/ai/components/ModelQuickSwitch.js.map +1 -1
  85. package/dist/features/ai/components/ModelQuickSwitch.vue.d.ts +4 -4
  86. package/dist/features/ai/components/SettingsAiTab.js +8 -8
  87. package/dist/features/ai/components/SettingsAiTab.js.map +1 -1
  88. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  89. package/dist/features/ai/components/SettingsModelProfiles.js.map +1 -1
  90. package/dist/features/ai/document-chat-model.d.ts +2 -2
  91. package/dist/features/ai/document-chat-model.js +5 -5
  92. package/dist/features/ai/document-chat-model.js.map +1 -1
  93. package/dist/features/ai/document-chat.js +2 -2
  94. package/dist/features/ai/document-chat.js.map +1 -1
  95. package/dist/features/ai/feature.d.ts +2 -2
  96. package/dist/features/ai/feature.js +2 -2
  97. package/dist/features/ai/feature.js.map +1 -1
  98. package/dist/features/ai/ghost-autocomplete.d.ts +4 -4
  99. package/dist/features/ai/ghost-autocomplete.js +1 -1
  100. package/dist/features/ai/ghost-autocomplete.js.map +1 -1
  101. package/dist/features/ai/index.d.ts +3 -3
  102. package/dist/features/ai/index.js +2 -2
  103. package/dist/features/ai/model-profiles.d.ts +9 -9
  104. package/dist/features/ai/model-profiles.js +13 -13
  105. package/dist/features/ai/model-profiles.js.map +1 -1
  106. package/dist/features/ai/protocol/agent-ops.d.ts +2 -2
  107. package/dist/features/ai/protocol/agent-ops.js +1 -1
  108. package/dist/features/ai/protocol/agent-ops.js.map +1 -1
  109. package/dist/features/ai/protocol/block-lines.d.ts +7 -7
  110. package/dist/features/ai/protocol/block-lines.js +10 -10
  111. package/dist/features/ai/protocol/block-lines.js.map +1 -1
  112. package/dist/features/ai/protocol/chat.d.ts +5 -5
  113. package/dist/features/ai/protocol/chat.js +12 -12
  114. package/dist/features/ai/protocol/chat.js.map +1 -1
  115. package/dist/features/ai/protocol/generate.d.ts +1 -1
  116. package/dist/features/ai/protocol/generate.js +1 -1
  117. package/dist/features/ai/protocol/generate.js.map +1 -1
  118. package/dist/features/ai/protocol/index.d.ts +3 -3
  119. package/dist/features/ai/protocol/index.js +2 -2
  120. package/dist/features/ai/protocol/models.d.ts +3 -3
  121. package/dist/features/ai/protocol/models.js +3 -3
  122. package/dist/features/ai/protocol/models.js.map +1 -1
  123. package/dist/features/ai/protocol/schemas.d.ts +1 -1
  124. package/dist/features/ai/protocol/schemas.js +1 -1
  125. package/dist/features/ai/protocol/schemas.js.map +1 -1
  126. package/dist/features/ai/protocol/tasks.d.ts +5 -5
  127. package/dist/features/ai/protocol/tasks.js +12 -12
  128. package/dist/features/ai/protocol/tasks.js.map +1 -1
  129. package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
  130. package/dist/features/ai/react/ModelQuickSwitch.d.ts +4 -4
  131. package/dist/features/ai/react/ModelQuickSwitch.js +6 -6
  132. package/dist/features/ai/react/ModelQuickSwitch.js.map +1 -1
  133. package/dist/features/ai/react/SettingsAiTab.d.ts +1 -1
  134. package/dist/features/ai/react/SettingsAiTab.js +8 -8
  135. package/dist/features/ai/react/SettingsAiTab.js.map +1 -1
  136. package/dist/features/ai/react/SettingsModelProfiles.d.ts +1 -1
  137. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  138. package/dist/features/ai/react/SettingsModelProfiles.js.map +1 -1
  139. package/dist/features/ai/react/use-ai-assistant.d.ts +1 -1
  140. package/dist/features/ai/react/use-ai-assistant.js +1 -1
  141. package/dist/features/ai/react/use-ai-assistant.js.map +1 -1
  142. package/dist/features/ai/react/use-model-profiles.d.ts +5 -5
  143. package/dist/features/ai/react/use-model-profiles.js +2 -2
  144. package/dist/features/ai/react/use-model-profiles.js.map +1 -1
  145. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  146. package/dist/features/ai/use-ai-assistant.js +1 -1
  147. package/dist/features/ai/use-ai-assistant.js.map +1 -1
  148. package/dist/features/ai/use-model-profiles.d.ts +4 -4
  149. package/dist/features/ai/use-model-profiles.js +2 -2
  150. package/dist/features/ai/use-model-profiles.js.map +1 -1
  151. package/dist/features/block-ui/block-actions.d.ts +8 -4
  152. package/dist/features/block-ui/block-actions.js +38 -22
  153. package/dist/features/block-ui/block-actions.js.map +1 -1
  154. package/dist/features/block-ui/block-target.d.ts +29 -0
  155. package/dist/features/block-ui/block-target.js +63 -0
  156. package/dist/features/block-ui/block-target.js.map +1 -0
  157. package/dist/features/block-ui/components/BlockHandles.js +3 -3
  158. package/dist/features/block-ui/components/BlockHandles.js.map +1 -1
  159. package/dist/features/block-ui/components/BlockUi.js +11 -23
  160. package/dist/features/block-ui/components/BlockUi.js.map +1 -1
  161. package/dist/features/block-ui/feature.d.ts +3 -2
  162. package/dist/features/block-ui/feature.js +3 -2
  163. package/dist/features/block-ui/feature.js.map +1 -1
  164. package/dist/features/block-ui/handle-geometry.js +12 -23
  165. package/dist/features/block-ui/handle-geometry.js.map +1 -1
  166. package/dist/features/block-ui/index.d.ts +2 -1
  167. package/dist/features/block-ui/index.js +2 -1
  168. package/dist/features/block-ui/react/BlockHandles.d.ts +1 -1
  169. package/dist/features/block-ui/react/BlockHandles.js +4 -4
  170. package/dist/features/block-ui/react/BlockHandles.js.map +1 -1
  171. package/dist/features/block-ui/react/BlockUi.js +13 -25
  172. package/dist/features/block-ui/react/BlockUi.js.map +1 -1
  173. package/dist/features/comments/model.js +1 -1
  174. package/dist/features/composites/accepted-children.d.ts +5 -4
  175. package/dist/features/composites/accepted-children.js +26 -26
  176. package/dist/features/composites/accepted-children.js.map +1 -1
  177. package/dist/features/composites/ai-brief.d.ts +4 -4
  178. package/dist/features/composites/ai-brief.js +14 -12
  179. package/dist/features/composites/ai-brief.js.map +1 -1
  180. package/dist/features/composites/anchors.d.ts +1 -1
  181. package/dist/features/composites/anchors.js.map +1 -1
  182. package/dist/features/composites/baskets.d.ts +1 -1
  183. package/dist/features/composites/baskets.js.map +1 -1
  184. package/dist/features/composites/bibliography-command.d.ts +1 -1
  185. package/dist/features/composites/bibliography-form.js +1 -1
  186. package/dist/features/composites/bibliography-form.js.map +1 -1
  187. package/dist/features/composites/bibliography.js.map +1 -1
  188. package/dist/features/composites/block-types-json.d.ts +38 -0
  189. package/dist/features/composites/block-types-json.js +122 -0
  190. package/dist/features/composites/block-types-json.js.map +1 -0
  191. package/dist/features/composites/block-types.d.ts +119 -0
  192. package/dist/features/composites/block-types.js +343 -0
  193. package/dist/features/composites/block-types.js.map +1 -0
  194. package/dist/features/composites/capabilities.js +109 -0
  195. package/dist/features/composites/capabilities.js.map +1 -0
  196. package/dist/features/composites/components/BibliographyEditor.js +5 -5
  197. package/dist/features/composites/components/BibliographyEditor.js.map +1 -1
  198. package/dist/features/composites/components/CompositeBasketMenu.js +2 -2
  199. package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -1
  200. package/dist/features/composites/components/CompositeChrome.js +18 -8
  201. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  202. package/dist/features/composites/components/CompositeChrome.vue.d.ts +1 -1
  203. package/dist/features/composites/components/CompositeDetails.js +11 -10
  204. package/dist/features/composites/components/CompositeDetails.js.map +1 -1
  205. package/dist/features/composites/components/CompositeDetails.vue.d.ts +1 -1
  206. package/dist/features/composites/components/CompositeFieldEditor.js +7 -7
  207. package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
  208. package/dist/features/composites/components/CompositeFieldEditor.vue.d.ts +5 -5
  209. package/dist/features/composites/components/CompositeInspector.js +136 -113
  210. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  211. package/dist/features/composites/components/CompositeInspector.vue.d.ts +3 -3
  212. package/dist/features/composites/components/CompositeStatusMenu.js +2 -2
  213. package/dist/features/composites/components/CompositeStatusMenu.js.map +1 -1
  214. package/dist/features/composites/components/CompositeSummaryEditor.js +1 -1
  215. package/dist/features/composites/components/CompositeSummaryEditor.js.map +1 -1
  216. package/dist/features/composites/components/CompositeSummaryEditor.vue.d.ts +1 -1
  217. package/dist/features/composites/components/CompositeTable.js +2 -2
  218. package/dist/features/composites/components/CompositeTable.js.map +1 -1
  219. package/dist/features/composites/components/CompositeTagsEditor.js +1 -1
  220. package/dist/features/composites/components/CompositeTagsEditor.js.map +1 -1
  221. package/dist/features/composites/components/CompositeTagsEditor.vue.d.ts +1 -1
  222. package/dist/features/composites/components/CompositeView.js +19 -17
  223. package/dist/features/composites/components/CompositeView.js.map +1 -1
  224. package/dist/features/composites/components/DerivationBadge.js +15 -6
  225. package/dist/features/composites/components/DerivationBadge.js.map +1 -1
  226. package/dist/features/composites/components/FocusChip.js +1 -1
  227. package/dist/features/composites/components/FocusChip.js.map +1 -1
  228. package/dist/features/composites/components/FocusChip.vue.d.ts +1 -1
  229. package/dist/features/composites/components/ReferenceFieldSelect.js +3 -3
  230. package/dist/features/composites/components/ReferenceFieldSelect.js.map +1 -1
  231. package/dist/features/composites/components/ReferenceView.js +1 -1
  232. package/dist/features/composites/components/ReferenceView.js.map +1 -1
  233. package/dist/features/composites/components/SettingsEditingTab.js +18 -18
  234. package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
  235. package/dist/features/composites/components/SettingsEditingTab.vue.d.ts +3 -3
  236. package/dist/features/composites/convert.d.ts +4 -4
  237. package/dist/features/composites/convert.js +27 -28
  238. package/dist/features/composites/convert.js.map +1 -1
  239. package/dist/features/composites/derived-chrome.js +34 -28
  240. package/dist/features/composites/derived-chrome.js.map +1 -1
  241. package/dist/features/composites/editing-settings-keys.js +6 -6
  242. package/dist/features/composites/editing-settings-keys.js.map +1 -1
  243. package/dist/features/composites/feature.d.ts +10 -10
  244. package/dist/features/composites/feature.js +58 -46
  245. package/dist/features/composites/feature.js.map +1 -1
  246. package/dist/features/composites/fences.d.ts +3 -3
  247. package/dist/features/composites/fences.js +18 -13
  248. package/dist/features/composites/fences.js.map +1 -1
  249. package/dist/features/composites/focus.d.ts +10 -10
  250. package/dist/features/composites/focus.js +11 -11
  251. package/dist/features/composites/focus.js.map +1 -1
  252. package/dist/features/composites/heading-composites.d.ts +5 -5
  253. package/dist/features/composites/heading-composites.js +13 -16
  254. package/dist/features/composites/heading-composites.js.map +1 -1
  255. package/dist/features/composites/index.d.ts +20 -18
  256. package/dist/features/composites/index.js +9 -7
  257. package/dist/features/composites/inspector-model.d.ts +45 -22
  258. package/dist/features/composites/inspector-model.js +53 -14
  259. package/dist/features/composites/inspector-model.js.map +1 -1
  260. package/dist/features/composites/legacy.d.ts +5 -4
  261. package/dist/features/composites/legacy.js +16 -23
  262. package/dist/features/composites/legacy.js.map +1 -1
  263. package/dist/features/composites/markdown-export.js +3 -3
  264. package/dist/features/composites/markdown-export.js.map +1 -1
  265. package/dist/features/composites/merge.d.ts +8 -8
  266. package/dist/features/composites/merge.js +23 -22
  267. package/dist/features/composites/merge.js.map +1 -1
  268. package/dist/features/composites/meta-display.js +5 -5
  269. package/dist/features/composites/meta-display.js.map +1 -1
  270. package/dist/features/composites/meta-keys.d.ts +5 -5
  271. package/dist/features/composites/meta-keys.js +8 -8
  272. package/dist/features/composites/meta-keys.js.map +1 -1
  273. package/dist/features/composites/node-views.d.ts +3 -3
  274. package/dist/features/composites/node-views.js +2 -2
  275. package/dist/features/composites/node-views.js.map +1 -1
  276. package/dist/features/composites/nodes.d.ts +13 -13
  277. package/dist/features/composites/nodes.js +100 -77
  278. package/dist/features/composites/nodes.js.map +1 -1
  279. package/dist/features/composites/outline.d.ts +15 -12
  280. package/dist/features/composites/outline.js +24 -21
  281. package/dist/features/composites/outline.js.map +1 -1
  282. package/dist/features/composites/properties.d.ts +51 -0
  283. package/dist/features/composites/properties.js +89 -0
  284. package/dist/features/composites/properties.js.map +1 -0
  285. package/dist/features/composites/react/BibliographyEditor.js +5 -5
  286. package/dist/features/composites/react/BibliographyEditor.js.map +1 -1
  287. package/dist/features/composites/react/CompositeBasketMenu.js +2 -2
  288. package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -1
  289. package/dist/features/composites/react/CompositeChrome.d.ts +1 -1
  290. package/dist/features/composites/react/CompositeChrome.js +15 -6
  291. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  292. package/dist/features/composites/react/CompositeDetails.d.ts +1 -1
  293. package/dist/features/composites/react/CompositeDetails.js +11 -10
  294. package/dist/features/composites/react/CompositeDetails.js.map +1 -1
  295. package/dist/features/composites/react/CompositeFieldEditor.d.ts +5 -5
  296. package/dist/features/composites/react/CompositeFieldEditor.js +6 -6
  297. package/dist/features/composites/react/CompositeFieldEditor.js.map +1 -1
  298. package/dist/features/composites/react/CompositeInspector.d.ts +3 -3
  299. package/dist/features/composites/react/CompositeInspector.js +142 -128
  300. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  301. package/dist/features/composites/react/CompositeStatusMenu.js +2 -2
  302. package/dist/features/composites/react/CompositeStatusMenu.js.map +1 -1
  303. package/dist/features/composites/react/CompositeSummaryEditor.d.ts +1 -1
  304. package/dist/features/composites/react/CompositeSummaryEditor.js +1 -1
  305. package/dist/features/composites/react/CompositeSummaryEditor.js.map +1 -1
  306. package/dist/features/composites/react/CompositeTable.d.ts +1 -1
  307. package/dist/features/composites/react/CompositeTable.js +3 -3
  308. package/dist/features/composites/react/CompositeTable.js.map +1 -1
  309. package/dist/features/composites/react/CompositeTagsEditor.d.ts +1 -1
  310. package/dist/features/composites/react/CompositeTagsEditor.js +1 -1
  311. package/dist/features/composites/react/CompositeTagsEditor.js.map +1 -1
  312. package/dist/features/composites/react/CompositeView.js +17 -15
  313. package/dist/features/composites/react/CompositeView.js.map +1 -1
  314. package/dist/features/composites/react/DerivationBadge.js +13 -4
  315. package/dist/features/composites/react/DerivationBadge.js.map +1 -1
  316. package/dist/features/composites/react/FocusChip.d.ts +1 -1
  317. package/dist/features/composites/react/FocusChip.js +1 -1
  318. package/dist/features/composites/react/FocusChip.js.map +1 -1
  319. package/dist/features/composites/react/ReferenceFieldSelect.js +3 -3
  320. package/dist/features/composites/react/ReferenceFieldSelect.js.map +1 -1
  321. package/dist/features/composites/react/ReferenceView.d.ts +1 -1
  322. package/dist/features/composites/react/ReferenceView.js +1 -1
  323. package/dist/features/composites/react/ReferenceView.js.map +1 -1
  324. package/dist/features/composites/react/SettingsEditingTab.d.ts +4 -4
  325. package/dist/features/composites/react/SettingsEditingTab.js +20 -20
  326. package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
  327. package/dist/features/composites/react.js +1 -1
  328. package/dist/features/composites/react.js.map +1 -1
  329. package/dist/features/composites/reference-ai.js +1 -1
  330. package/dist/features/composites/reference-ai.js.map +1 -1
  331. package/dist/features/composites/reference-label.d.ts +3 -3
  332. package/dist/features/composites/reference-label.js +6 -6
  333. package/dist/features/composites/reference-label.js.map +1 -1
  334. package/dist/features/composites/reference-suggestion.d.ts +1 -1
  335. package/dist/features/composites/reference-suggestion.js +3 -3
  336. package/dist/features/composites/reference-suggestion.js.map +1 -1
  337. package/dist/features/composites/reference-text.d.ts +2 -2
  338. package/dist/features/composites/reference-text.js +4 -4
  339. package/dist/features/composites/reference-text.js.map +1 -1
  340. package/dist/features/composites/reference.d.ts +3 -3
  341. package/dist/features/composites/reference.js +1 -1
  342. package/dist/features/composites/reference.js.map +1 -1
  343. package/dist/features/composites/reveal-source.d.ts +3 -3
  344. package/dist/features/composites/reveal-source.js +6 -6
  345. package/dist/features/composites/reveal-source.js.map +1 -1
  346. package/dist/features/composites/rows-model.d.ts +2 -2
  347. package/dist/features/composites/rows-model.js +4 -3
  348. package/dist/features/composites/rows-model.js.map +1 -1
  349. package/dist/features/composites/rows.d.ts +16 -28
  350. package/dist/features/composites/rows.js +20 -54
  351. package/dist/features/composites/rows.js.map +1 -1
  352. package/dist/features/composites/schema.d.ts +12 -11
  353. package/dist/features/composites/schema.js +10 -9
  354. package/dist/features/composites/schema.js.map +1 -1
  355. package/dist/features/composites/source.d.ts +20 -0
  356. package/dist/features/composites/source.js +40 -0
  357. package/dist/features/composites/source.js.map +1 -0
  358. package/dist/features/composites/split.d.ts +11 -11
  359. package/dist/features/composites/split.js +20 -20
  360. package/dist/features/composites/split.js.map +1 -1
  361. package/dist/features/composites/structure-mode.js +3 -3
  362. package/dist/features/composites/structure-mode.js.map +1 -1
  363. package/dist/features/composites/table-model.js +2 -2
  364. package/dist/features/composites/table-model.js.map +1 -1
  365. package/dist/features/composites/use-composite-inspector.js +3 -1
  366. package/dist/features/composites/use-composite-inspector.js.map +1 -1
  367. package/dist/features/composites/use-composite-rows.d.ts +2 -2
  368. package/dist/features/composites/use-composite-rows.js.map +1 -1
  369. package/dist/features/composites/vue.js +1 -1
  370. package/dist/features/composites/vue.js.map +1 -1
  371. package/dist/features/image-studio/components/ImageStudioDialog.js +1 -1
  372. package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -1
  373. package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +1 -1
  374. package/dist/features/image-studio/components/ImageStudioView.js.map +1 -1
  375. package/dist/features/image-studio/feature.d.ts +1 -1
  376. package/dist/features/image-studio/feature.js +1 -1
  377. package/dist/features/image-studio/feature.js.map +1 -1
  378. package/dist/features/image-studio/node-core.d.ts +3 -3
  379. package/dist/features/image-studio/node-core.js +1 -1
  380. package/dist/features/image-studio/node-core.js.map +1 -1
  381. package/dist/features/image-studio/node-store.d.ts +1 -1
  382. package/dist/features/image-studio/node-store.js +1 -1
  383. package/dist/features/image-studio/node-store.js.map +1 -1
  384. package/dist/features/image-studio/react/ImageStudioDialog.d.ts +1 -1
  385. package/dist/features/image-studio/react/ImageStudioDialog.js +1 -1
  386. package/dist/features/image-studio/react/ImageStudioDialog.js.map +1 -1
  387. package/dist/features/image-studio/react/ImageStudioView.js +1 -1
  388. package/dist/features/image-studio/react/ImageStudioView.js.map +1 -1
  389. package/dist/features/image-studio/react/store.d.ts +3 -3
  390. package/dist/features/image-studio/react/store.js.map +1 -1
  391. package/dist/features/image-studio/react.d.ts +1 -1
  392. package/dist/features/image-studio/react.js +1 -1
  393. package/dist/features/image-studio/react.js.map +1 -1
  394. package/dist/features/image-studio/schema.d.ts +1 -1
  395. package/dist/features/image-studio/schema.js +1 -1
  396. package/dist/features/image-studio/schema.js.map +1 -1
  397. package/dist/features/image-studio/state.d.ts +3 -3
  398. package/dist/features/image-studio/state.js +2 -2
  399. package/dist/features/image-studio/state.js.map +1 -1
  400. package/dist/features/image-studio/store.d.ts +3 -3
  401. package/dist/features/image-studio/vue.d.ts +1 -1
  402. package/dist/features/image-studio/vue.js +1 -1
  403. package/dist/features/image-studio/vue.js.map +1 -1
  404. package/dist/features/media/index.js +1 -1
  405. package/dist/features/media/react/AudioRecorderModal.js +1 -1
  406. package/dist/features/media/react.js +1 -1
  407. package/dist/features/mentions/tag.js +2 -1
  408. package/dist/features/mentions/tag.js.map +1 -1
  409. package/dist/features/themes/components/SettingsAppearanceTab.js +1 -1
  410. package/dist/features/themes/components/SettingsAppearanceTab.js.map +1 -1
  411. package/dist/features/themes/feature.d.ts +1 -1
  412. package/dist/features/themes/feature.js.map +1 -1
  413. package/dist/features/themes/react/SettingsAppearanceTab.d.ts +1 -1
  414. package/dist/features/themes/react/SettingsAppearanceTab.js +1 -1
  415. package/dist/features/themes/react/SettingsAppearanceTab.js.map +1 -1
  416. package/dist/features/themes/theme-runtime.js +1 -1
  417. package/dist/features/themes/theme-runtime.js.map +1 -1
  418. package/dist/features/transcript/TranscriptSegmentView.js +1 -1
  419. package/dist/features/transcript/TranscriptSegmentView.js.map +1 -1
  420. package/dist/features/transcript/react/TranscriptSegmentView.js +1 -1
  421. package/dist/features/transcript/react/TranscriptSegmentView.js.map +1 -1
  422. package/dist/features/transcript/segment-node.js.map +1 -1
  423. package/dist/features/transfer/feature.js +1 -1
  424. package/dist/features/transfer/feature.js.map +1 -1
  425. package/dist/features/transfer/transforms.d.ts +4 -4
  426. package/dist/features/transfer/transforms.js +12 -12
  427. package/dist/features/transfer/transforms.js.map +1 -1
  428. package/dist/i18n/de/ai.js +4 -4
  429. package/dist/i18n/de/ai.js.map +1 -1
  430. package/dist/i18n/de/authors.js +1 -1
  431. package/dist/i18n/de/authors.js.map +1 -1
  432. package/dist/i18n/de/comments.js +1 -1
  433. package/dist/i18n/de/comments.js.map +1 -1
  434. package/dist/i18n/de/composites.js +62 -59
  435. package/dist/i18n/de/composites.js.map +1 -1
  436. package/dist/i18n/de/image-studio.js +2 -2
  437. package/dist/i18n/de/image-studio.js.map +1 -1
  438. package/dist/i18n/de/integrations.js +20 -20
  439. package/dist/i18n/de/integrations.js.map +1 -1
  440. package/dist/i18n/de/markdown.js +1 -1
  441. package/dist/i18n/de/markdown.js.map +1 -1
  442. package/dist/i18n/de/palette.js +1 -1
  443. package/dist/i18n/de/palette.js.map +1 -1
  444. package/dist/i18n/de/themes.js +3 -3
  445. package/dist/i18n/de/themes.js.map +1 -1
  446. package/dist/i18n/de/transfer.js +2 -2
  447. package/dist/i18n/de/transfer.js.map +1 -1
  448. package/dist/i18n/de/tree.js +6 -6
  449. package/dist/i18n/de/tree.js.map +1 -1
  450. package/dist/i18n/de.d.ts +101 -98
  451. package/dist/i18n/en/ai.js +2 -2
  452. package/dist/i18n/en/ai.js.map +1 -1
  453. package/dist/i18n/en/composites.js +64 -61
  454. package/dist/i18n/en/composites.js.map +1 -1
  455. package/dist/i18n/en/integrations.js +9 -9
  456. package/dist/i18n/en/integrations.js.map +1 -1
  457. package/dist/i18n/en/markdown.js +1 -1
  458. package/dist/i18n/en/markdown.js.map +1 -1
  459. package/dist/i18n/en/palette.js +1 -1
  460. package/dist/i18n/en/palette.js.map +1 -1
  461. package/dist/i18n/en/themes.js +4 -4
  462. package/dist/i18n/en/themes.js.map +1 -1
  463. package/dist/i18n/en/tree.js +5 -5
  464. package/dist/i18n/en/tree.js.map +1 -1
  465. package/dist/index.d.ts +4 -1
  466. package/dist/integrations/composite-ai/bibliography-fetch.d.ts +6 -6
  467. package/dist/integrations/composite-ai/bibliography-fetch.js +19 -19
  468. package/dist/integrations/composite-ai/bibliography-fetch.js.map +1 -1
  469. package/dist/integrations/composite-ai/composite-fill.d.ts +4 -4
  470. package/dist/integrations/composite-ai/composite-fill.js +5 -5
  471. package/dist/integrations/composite-ai/composite-fill.js.map +1 -1
  472. package/dist/integrations/composite-ai/composite-summary.d.ts +2 -2
  473. package/dist/integrations/composite-ai/composite-summary.js +5 -5
  474. package/dist/integrations/composite-ai/composite-summary.js.map +1 -1
  475. package/dist/integrations/composite-ai/derive.d.ts +17 -17
  476. package/dist/integrations/composite-ai/derive.js +27 -26
  477. package/dist/integrations/composite-ai/derive.js.map +1 -1
  478. package/dist/integrations/composite-ai/index.d.ts +14 -15
  479. package/dist/integrations/composite-ai/index.js +98 -77
  480. package/dist/integrations/composite-ai/index.js.map +1 -1
  481. package/dist/integrations/composite-ai/outline-sanitize.d.ts +1 -1
  482. package/dist/integrations/composite-ai/outline-sanitize.js +1 -1
  483. package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -1
  484. package/dist/integrations/composite-ai/rendition.d.ts +9 -9
  485. package/dist/integrations/composite-ai/rendition.js +27 -27
  486. package/dist/integrations/composite-ai/rendition.js.map +1 -1
  487. package/dist/integrations/composite-ai/split.d.ts +8 -8
  488. package/dist/integrations/composite-ai/split.js +12 -12
  489. package/dist/integrations/composite-ai/split.js.map +1 -1
  490. package/dist/integrations/composite-ai/transfer-transforms.d.ts +2 -2
  491. package/dist/integrations/composite-ai/transfer-transforms.js +12 -12
  492. package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -1
  493. package/dist/integrations/composite-media/index.d.ts +4 -3
  494. package/dist/integrations/composite-media/index.js +20 -14
  495. package/dist/integrations/composite-media/index.js.map +1 -1
  496. package/dist/markdown/document.js +1 -1
  497. package/dist/markdown/document.js.map +1 -1
  498. package/dist/nuxt/module.d.ts +30 -24
  499. package/dist/nuxt/module.js.map +1 -1
  500. package/dist/nuxt/runtime/server/plugins/editor-server.js +3 -2
  501. package/dist/nuxt/runtime/server/plugins/editor-server.js.map +1 -1
  502. package/dist/presets/compose.d.ts +6 -6
  503. package/dist/presets/compose.js +8 -5
  504. package/dist/presets/compose.js.map +1 -1
  505. package/dist/react/AdvancedEditor.d.ts +2 -2
  506. package/dist/react/AdvancedEditor.js +1 -1
  507. package/dist/react/AdvancedEditor.js.map +1 -1
  508. package/dist/react/compose.d.ts +1 -1
  509. package/dist/react/compose.js +2 -2
  510. package/dist/react/compose.js.map +1 -1
  511. package/dist/react/composed-surface.d.ts +5 -5
  512. package/dist/react/composed-surface.js +2 -2
  513. package/dist/react/composed-surface.js.map +1 -1
  514. package/dist/server/ai.d.ts +7 -7
  515. package/dist/server/ai.js +1 -1
  516. package/dist/server/ai.js.map +1 -1
  517. package/dist/server/auth.d.ts +1 -1
  518. package/dist/server/collab-state.d.ts +1 -1
  519. package/dist/server/collab-state.js.map +1 -1
  520. package/dist/server/collab.d.ts +4 -4
  521. package/dist/server/collab.js +22 -17
  522. package/dist/server/collab.js.map +1 -1
  523. package/dist/server/handlers/ai.d.ts +7 -4
  524. package/dist/server/handlers/ai.js +13 -17
  525. package/dist/server/handlers/ai.js.map +1 -1
  526. package/dist/server/handlers/context.d.ts +4 -0
  527. package/dist/server/handlers/context.js.map +1 -1
  528. package/dist/server/handlers/editor.d.ts +33 -32
  529. package/dist/server/handlers/editor.js +13 -8
  530. package/dist/server/handlers/editor.js.map +1 -1
  531. package/dist/server/handlers/images.d.ts +1 -1
  532. package/dist/server/handlers/images.js +1 -1
  533. package/dist/server/handlers/images.js.map +1 -1
  534. package/dist/server/host.d.ts +4 -0
  535. package/dist/server/host.js +10 -2
  536. package/dist/server/host.js.map +1 -1
  537. package/dist/server/index.d.ts +3 -3
  538. package/dist/server/ports.d.ts +8 -8
  539. package/dist/server/ports.js.map +1 -1
  540. package/dist/settings/index.d.ts +20 -19
  541. package/dist/settings/index.js +25 -24
  542. package/dist/settings/index.js.map +1 -1
  543. package/dist/settings/migrate.d.ts +18 -0
  544. package/dist/settings/migrate.js +46 -0
  545. package/dist/settings/migrate.js.map +1 -0
  546. package/dist/settings/values.d.ts +12 -12
  547. package/dist/settings/values.js +11 -11
  548. package/dist/settings/values.js.map +1 -1
  549. package/dist/styles/composites.css +10 -10
  550. package/dist/tree/DocumentTree.js +12 -5
  551. package/dist/tree/DocumentTree.js.map +1 -1
  552. package/dist/tree/drop-rules.js +5 -5
  553. package/dist/tree/drop-rules.js.map +1 -1
  554. package/dist/tree/react/DocumentTree.js +14 -5
  555. package/dist/tree/react/DocumentTree.js.map +1 -1
  556. package/dist/tree/tree-host.js +6 -6
  557. package/dist/tree/tree-host.js.map +1 -1
  558. package/dist/tree/tree-items.js +34 -28
  559. package/dist/tree/tree-items.js.map +1 -1
  560. package/dist/tree/tree-model.js +16 -16
  561. package/dist/tree/tree-model.js.map +1 -1
  562. package/dist/tree/tree-rows.js +2 -2
  563. package/dist/tree/tree-rows.js.map +1 -1
  564. package/dist/tree/tree-view-model.d.ts +2 -2
  565. package/dist/tree/tree-view-model.js.map +1 -1
  566. package/dist/vue/AdvancedEditor.js +2 -2
  567. package/dist/vue/AdvancedEditor.js.map +1 -1
  568. package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
  569. package/dist/vue/compose.d.ts +1 -1
  570. package/dist/vue/compose.js +2 -2
  571. package/dist/vue/compose.js.map +1 -1
  572. package/package.json +1 -1
  573. package/dist/features/composites/derivation.d.ts +0 -62
  574. package/dist/features/composites/derivation.js +0 -102
  575. package/dist/features/composites/derivation.js.map +0 -1
  576. package/dist/features/composites/kinds.d.ts +0 -89
  577. package/dist/features/composites/kinds.js +0 -285
  578. package/dist/features/composites/kinds.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tasks.js","names":[],"sources":["../../../../src/features/ai/protocol/tasks.ts"],"sourcesContent":["import type { AiBlock } from '../../../core/feature'\nimport type { MessageKey, Translate } from '../../../i18n'\nimport { de } from '../../../i18n/de'\nimport type { AiLanguage } from '../../../settings'\n\nexport const AI_TASK_IDS = ['reformulate', 'generate', 'transcript-summary', 'transcript-minutes', 'transcript-action-items', 'fix', 'shorter', 'formal', 'casual', 'translate', 'summarize', 'custom', 'continue', 'fill-block', 'split-block', 'outline-sanitize', 'transfer-summary', 'transfer-bullets'] as const\nexport type AiTaskId = (typeof AI_TASK_IDS)[number]\n/**\n * How „Übernehmen“ applies the result: client-side over the captured selection, or server-side after the caret's block\n * or after the document's last block.\n */\nexport type AiApplyMode = 'replace-selection' | 'insert-after-block' | 'append-to-document'\n/** The block a `fill-block` run writes (the brief of `compositeAiBrief` plus the host's `aiBlockData`). */\nexport interface AiBlockInput {\n kind: { id: string, label: string, purpose?: string }\n path: readonly string[]\n title: string\n description: string\n fields: readonly { label: string, value: string }[]\n instructions: string\n skeleton: string\n body: string\n data: readonly { label: string, text: string }[]\n}\n/**\n * `users`: registered usernames, filled in by the generate route for tasks with `needsUsers` (never sent by the client).\n * `language`: target language of tasks with `needsLanguage`. `block`: the block of a `needsBlock` task.\n */\nexport interface AiInput { prompt: string, selection: string, context: string, users?: readonly string[], language?: AiLanguage, blocks?: readonly AiBlock[], block?: AiBlockInput }\nexport interface AiMessage { role: 'system' | 'user', content: string }\nexport interface AiTask {\n id: AiTaskId\n /** Catalog key of the task's name in the menus and the sidebar. */\n labelKey: MessageKey\n icon: string\n needsSelection: boolean\n needsPrompt: boolean\n /** Catalog key of the input placeholder of a task with `needsPrompt`; the other tasks name an optional hint. */\n promptLabelKey?: MessageKey\n apply: AiApplyMode\n /** Model slot the task runs on: `rewrite` (in-block AI, text inside one block) or `generate` (block generation). */\n slot: 'rewrite' | 'generate'\n /** The context is the whole document (up to `AI_LIMITS.transcript`) and must not be empty. */\n wholeDocument?: boolean\n /** The generate route passes the registered usernames as `users`, so the model can write `@username` mentions. */\n needsUsers?: boolean\n /**\n * Catalog key of the title of the result: its `##` heading as shown and applied (`withTaskHeading`, in the editor's\n * language). A feature may format the applied result differently through `formatAiResult` (composite AI wraps it in\n * a `section` composite). The model is told not to write one.\n */\n headingKey?: MessageKey\n /** The request must name a target `language` (see `AI_LANGUAGES`). */\n needsLanguage?: boolean\n /** Offered as quick action in the selection's AI menu (AiSelectionMenu). */\n selectionMenu?: boolean\n /** Offered as quick action in the AI menu at the caret (`/ai`, AiSelectionMenu), not in the AI sidebar. */\n caretMenu?: boolean\n /** The request must carry `block`; offered only through a block action, never in the sidebar or menus. */\n needsBlock?: boolean\n /** Run only by a block action or command that sends its own request; never listed in the sidebar or the menus. */\n programmatic?: boolean\n messages: (input: AiInput) => AiMessage[]\n}\n\n/** `markdown` under the `##` title `heading`; it replaces a top-level (#/##) heading the model wrote anyway. */\nexport function withHeading(heading: string, markdown: string): string {\n return `## ${heading}\\n\\n${markdown.replace(/^\\s*#{1,2}[ \\t][^\\n]*/, '').trimStart()}`\n}\n\n/** Result Markdown as shown: for tasks with a heading, that heading (in the language of `t`) replaces a top-level (#/##) heading the model wrote anyway. */\nexport const withTaskHeading = (task: AiTask, markdown: string, t: Translate): string => task.headingKey ? withHeading(t(task.headingKey), markdown) : markdown\n\n/** Maximum input lengths (characters), enforced by the generate route; the client truncates the context to fit. */\nexport const AI_LIMITS = { prompt: 4000, selection: 20000, context: 12000, transcript: 60000, block: { text: 4000, skeleton: 8000, body: 20000, data: 20000 } } as const\n\n// i18n-ignore-start: model-facing prompt\n/** The system prompt of all tasks; `blocks` (the editor's host blocks) are listed after the core Markdown subset. */\nfunction aiSystem(blocks: readonly AiBlock[] = []): string {\n return [\n 'Du bist ein Schreibassistent in einem Texteditor.',\n 'Antworte ausschließlich mit dem fertigen Text als Markdown.',\n 'Erlaubt sind: Absätze, Überschriften (#, ##, ###), **fett**, *kursiv*, ~~durchgestrichen~~, `Code`, Codeblöcke mit ```, Aufzählungen, nummerierte Listen, Zitate mit > und Trennlinien (---)'\n + (blocks.length\n ? ` sowie diese eigenen Blöcke, wenn sie inhaltlich passen: ${blocks.map(block => `${block.label} – ${block.description} Markdown-Form: „${block.syntax.replace(/\\n/g, '\\\\n')}“, Beispiel: „${block.example.replace(/\\n/g, '\\\\n')}“`).join('; ')}.`\n : '.'),\n 'Transkript-Passagen (Redebeiträge von Sprecher:innen) schreibst du als Sprecherzeilen: jeder Redebeitrag ist ein eigener Absatz, der mit dem fett gesetzten Namen und Doppelpunkt beginnt, also **Name:** Text – eine Zeile pro Redebeitrag, dazwischen eine Leerzeile. Vorhandene Sprechernamen übernimmst du unverändert.',\n 'Beginne andere Absätze nicht mit fett gesetztem Text, der auf einen Doppelpunkt endet.',\n 'Keine Einleitung, keine Erklärungen, keine Rückfragen.',\n 'Schreibe in der Sprache des Dokuments.',\n ].join('\\n')\n}\n\nconst contextBlock = (context: string) => `Dokument (Kontext):\\n\"\"\"\\n${context}\\n\"\"\"`\n// i18n-ignore-end\n\n/**\n * Whole-transcript task whose result is appended to the document under the title `headingKey`. The prompt names that\n * title in German (prompts stay German whatever the editor's language).\n */\nfunction transcriptTask(\n id: AiTaskId,\n headingKey: MessageKey,\n icon: string,\n instruction: (users: readonly string[]) => string[],\n needsUsers = false,\n): AiTask {\n return {\n id,\n labelKey: headingKey,\n icon,\n needsSelection: false,\n needsPrompt: false,\n promptLabelKey: 'ai.task.transcript.promptLabel',\n apply: 'append-to-document',\n slot: 'generate',\n wholeDocument: true,\n needsUsers,\n headingKey,\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, context, users = [], blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n ...instruction(users),\n `Beginne ohne eigene Überschrift – die Überschrift „${de[headingKey]}“ wird automatisch vorangestellt. Zwischenüberschriften nur mit ### (z. B. „### Beschlüsse“), nie als fett gesetzte Zeile wie **Beschlüsse:**.`,\n 'Schreibe keine Sprecherzeilen (**Name:** …), sondern nenne Personen im Fließtext.',\n ...(prompt.trim() ? [`Hinweis: ${prompt.trim()}`] : []),\n `Transkript:\\n\"\"\"\\n${context}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n }\n}\n\n/** Quick action of the selection's AI menu: rewrites the selected excerpt according to `instruction`. */\nfunction selectionTask(\n id: AiTaskId,\n labelKey: MessageKey,\n icon: string,\n instruction: (input: AiInput) => string,\n extra: Partial<Pick<AiTask, 'needsPrompt' | 'promptLabelKey' | 'needsLanguage'>> = {},\n): AiTask {\n return {\n id,\n labelKey,\n icon,\n needsSelection: true,\n needsPrompt: false,\n apply: 'replace-selection',\n slot: 'rewrite',\n selectionMenu: true,\n ...extra,\n // i18n-ignore-start: model-facing prompt\n messages: input => [\n { role: 'system', content: aiSystem(input.blocks) },\n {\n role: 'user',\n content: [\n instruction(input),\n 'Gib nur den Text zurück, der den Textausschnitt ersetzen soll, ohne zusätzliche Überschrift. Behalte die Markdown-Formatierung und Sprecherzeilen bei.',\n contextBlock(input.context),\n `Textausschnitt:\\n\"\"\"\\n${input.selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n }\n}\n\nexport const AI_TASKS: Record<AiTaskId, AiTask> = {\n reformulate: {\n id: 'reformulate',\n labelKey: 'ai.task.reformulate.label',\n icon: 'i-lucide-refresh-cw',\n needsSelection: true,\n needsPrompt: false,\n promptLabelKey: 'ai.task.reformulate.promptLabel',\n apply: 'replace-selection',\n slot: 'rewrite',\n selectionMenu: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, selection, context, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Formuliere den folgenden Textausschnitt um. Behalte Bedeutung, Formatierung und ungefähre Länge bei.',\n 'Gib nur den Text zurück, der den Textausschnitt ersetzen soll, ohne zusätzliche Überschrift.',\n ...(prompt.trim() ? [`Hinweis: ${prompt.trim()}`] : []),\n contextBlock(context),\n `Textausschnitt:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n generate: {\n id: 'generate',\n labelKey: 'ai.task.generate.label',\n icon: 'i-lucide-sparkles',\n needsSelection: false,\n needsPrompt: true,\n promptLabelKey: 'ai.task.generate.promptLabel',\n apply: 'insert-after-block',\n slot: 'generate',\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, context, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Schreibe neuen Text für das Dokument nach folgender Anweisung. Gib nur den Text zurück, der eingefügt werden soll.',\n `Anweisung: ${prompt.trim()}`,\n contextBlock(context),\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n // i18n-ignore-start: model-facing prompt\n 'transcript-summary': transcriptTask('transcript-summary', 'ai.task.transcriptSummary.heading', 'i-lucide-text-quote', () => [\n 'Fasse das folgende Transkript zusammen: die wichtigsten Themen, Ergebnisse und offenen Punkte, knapp in wenigen Absätzen oder als Aufzählung.',\n ]),\n 'transcript-minutes': transcriptTask('transcript-minutes', 'ai.task.transcriptMinutes.heading', 'i-lucide-notebook-pen', () => [\n 'Erstelle ein Protokoll des folgenden Transkripts mit den Abschnitten „Teilnehmende“, „Themen“ (je Thema die wesentlichen Punkte), „Beschlüsse“ und „Nächste Schritte“ als Zwischenüberschriften und Aufzählungen darunter. Lass Abschnitte ohne Inhalt weg.',\n ]),\n 'transcript-action-items': transcriptTask('transcript-action-items', 'ai.task.transcriptActionItems.heading', 'i-lucide-list-checks', users => [\n 'Liste alle Aufgaben (Action Items) aus dem folgenden Transkript als Aufzählung auf: je Aufgabe ein Punkt der Form „Verantwortliche Person: Aufgabe (bis Frist)“; die Frist nur, wenn sie genannt wird. Ohne klar verantwortliche Person schreibe „Offen:“. Gibt es keine Aufgaben, antworte nur mit „Keine Aufgaben gefunden.“',\n users.length\n ? `Registrierte Benutzer:innen: ${users.map(u => `@${u}`).join(', ')}. Ist die verantwortliche Person eine davon (z. B. gleicher Vor- oder Nachname), schreibe sie genau als @benutzername; alle anderen Personen schreibe mit ihrem Namen ohne @.`\n : 'Schreibe verantwortliche Personen mit ihrem Namen, ohne @.',\n ], true),\n fix: selectionTask('fix', 'ai.task.fix.label', 'i-lucide-spell-check', () =>\n 'Korrigiere Rechtschreibung, Grammatik und Zeichensetzung im folgenden Textausschnitt. Ändere Wortwahl, Stil und Inhalt nicht. Ist nichts zu korrigieren, gib den Textausschnitt unverändert zurück.'),\n shorter: selectionTask('shorter', 'ai.task.shorter.label', 'i-lucide-shrink', () =>\n 'Kürze den folgenden Textausschnitt deutlich, etwa auf die Hälfte, ohne wesentliche Aussagen zu verlieren.'),\n formal: selectionTask('formal', 'ai.task.formal.label', 'i-lucide-briefcase', () =>\n 'Formuliere den folgenden Textausschnitt förmlicher und sachlicher. Behalte Bedeutung und ungefähre Länge bei.'),\n casual: selectionTask('casual', 'ai.task.casual.label', 'i-lucide-smile', () =>\n 'Formuliere den folgenden Textausschnitt lockerer und umgangssprachlicher. Behalte Bedeutung und ungefähre Länge bei.'),\n translate: selectionTask('translate', 'ai.task.translate.label', 'i-lucide-languages', ({ language }) =>\n `Übersetze den folgenden Textausschnitt in diese Sprache: ${language}. Schreibe abweichend von der Dokumentsprache auf ${language}; Sprechernamen bleiben unverändert.`, { needsLanguage: true }),\n summarize: selectionTask('summarize', 'ai.task.summarize.label', 'i-lucide-text-quote', () =>\n 'Fasse den folgenden Textausschnitt knapp zusammen.'),\n custom: selectionTask('custom', 'ai.task.custom.label', 'i-lucide-message-square-text', ({ prompt }) =>\n `Bearbeite den folgenden Textausschnitt nach dieser Anweisung: ${prompt.trim()}`, { needsPrompt: true, promptLabelKey: 'ai.task.custom.promptLabel' }),\n // i18n-ignore-end\n // `selection`: the document up to the caret line; `context`: the document after it (see AiSelectionMenu's caret mode).\n continue: {\n id: 'continue',\n labelKey: 'ai.task.continue.label',\n icon: 'i-lucide-pen-line',\n needsSelection: true,\n needsPrompt: false,\n apply: 'insert-after-block',\n slot: 'generate',\n caretMenu: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ selection, context, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Setze den folgenden Text an seinem Ende fort: schreibe die nächsten ein bis drei Absätze, passend zu Inhalt, Stil und Formatierung. Wiederhole nichts vom vorhandenen Text. Gib nur den neuen Text zurück, der am Ende eingefügt werden soll.',\n `Text bis zur Einfügestelle:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ...(context.trim() ? [`Danach folgt im Dokument (nur als Kontext, nicht fortsetzen oder wiederholen):\\n\"\"\"\\n${context}\\n\"\"\"`] : []),\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n 'fill-block': {\n id: 'fill-block',\n labelKey: 'ai.task.fillBlock.label',\n icon: 'i-lucide-wand-sparkles',\n needsSelection: false,\n needsPrompt: false,\n promptLabelKey: 'ai.task.fillBlock.promptLabel',\n apply: 'replace-selection',\n slot: 'generate',\n needsBlock: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, context, blocks, block }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n `Schreibe den Inhalt des Bausteins „${block!.title || block!.kind.label}“ (Art: ${block!.kind.label}${block!.kind.purpose ? ` – ${block!.kind.purpose}` : ''}).`,\n ...(block!.path.length ? [`Der Baustein steht unter: ${block!.path.join(' › ')}.`] : []),\n ...(block!.description.trim() ? [`Beschreibung des Bausteins: ${block!.description.trim()}`] : []),\n ...(block!.fields.length ? [`Angaben zum Baustein:\\n${block!.fields.map(f => `- ${f.label}: ${f.value}`).join('\\n')}`] : []),\n ...(block!.instructions.trim() ? [`Anweisung: ${block!.instructions.trim()}`] : []),\n ...(block!.skeleton.trim() ? [`Zieltext (Vorlage; Platzhalter in geschweiften Klammern ausfüllen, Struktur und Reihenfolge beibehalten):\\n\"\"\"\\n${block!.skeleton.trim()}\\n\"\"\"`] : []),\n ...block!.data.map(d => `${d.label}:\\n\"\"\"\\n${d.text}\\n\"\"\"`),\n ...(block!.body.trim() ? [`Bisheriger Inhalt des Bausteins (wird ersetzt; Brauchbares übernehmen):\\n\"\"\"\\n${block!.body.trim()}\\n\"\"\"`] : []),\n ...(prompt.trim() ? [`Hinweis: ${prompt.trim()}`] : []),\n contextBlock(context),\n 'Gib nur die Inhaltsblöcke des Bausteins als Markdown zurück: keine Titelzeile, keine Felder, keinen :::-Zaun.',\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n // `prompt`: the target size in characters, `selection`: the Baustein's body as Markdown (see `splitCompositeWithAi`).\n 'split-block': {\n id: 'split-block',\n labelKey: 'ai.task.splitBlock.label',\n icon: 'i-lucide-sparkles',\n needsSelection: true,\n needsPrompt: true,\n apply: 'replace-selection',\n slot: 'generate',\n programmatic: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, selection, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n `Gliedere den folgenden Text in Abschnitte von etwa ${prompt.trim()} Zeichen: füge an sinnvollen Stellen Überschriften der Ebene 2 („## “) mit knappen, treffenden Titeln ein. Verändere den Text selbst nicht: kein Wort hinzufügen, entfernen oder umstellen, Formatierung und Absätze beibehalten. Gib den vollständigen Text mit den eingefügten Überschriften als Markdown zurück, ohne weitere Überschrift.`,\n `Text:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n // `selection`: the document's headings as a JSON list (see `sanitizeOutline`); the answer is a JSON list, not Markdown.\n 'outline-sanitize': {\n id: 'outline-sanitize',\n labelKey: 'ai.task.outlineSanitize.label',\n icon: 'i-lucide-list-tree',\n needsSelection: true,\n needsPrompt: false,\n apply: 'replace-selection',\n slot: 'generate',\n programmatic: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ selection }) => [\n { role: 'system', content: 'Du bist ein Assistent für die Gliederung von Dokumenten. Antworte ausschließlich mit der geforderten JSON-Liste, ohne Erklärung und ohne Codezaun.' },\n {\n role: 'user',\n content: [\n 'Hier ist die Gliederung eines Dokuments als JSON-Liste von Überschriften (i = Index, level = Ebene, text, chars = Zeichen des Textes darunter). Bereinige die Struktur: keine übersprungenen Ebenen; höchstens eine Überschrift der Ebene 1; Nummerierungen, Satzzeichen am Ende und doppelte Leerzeichen entfernen; einheitliche Schreibweise; eine Überschrift ohne Text darunter (chars = 0) mit der folgenden zusammenlegen, indem du ihren text leer lässt (sie wird entfernt). Ändere Bedeutung nicht. Antworte nur mit der JSON-Liste gleicher Länge und Reihenfolge: [{\"i\":0,\"level\":1,\"text\":\"…\"}, …]',\n `Gliederung:\\n${selection}`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n // `selection`: the transferred Baustein as Markdown (see `transferRequest` of the transfer feature).\n 'transfer-summary': {\n id: 'transfer-summary',\n labelKey: 'ai.task.transferSummary.label',\n icon: 'i-lucide-text-quote',\n needsSelection: true,\n needsPrompt: false,\n apply: 'insert-after-block',\n slot: 'generate',\n programmatic: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ selection, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Fasse den folgenden Baustein knapp in ein bis drei Absätzen zusammen. Behalte Fachbegriffe bei. Gib nur die Zusammenfassung als Markdown zurück, ohne Überschrift.',\n `Baustein:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n 'transfer-bullets': {\n id: 'transfer-bullets',\n labelKey: 'ai.task.transferBullets.label',\n icon: 'i-lucide-list',\n needsSelection: true,\n needsPrompt: false,\n apply: 'insert-after-block',\n slot: 'generate',\n programmatic: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ selection, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Gib die Kernaussagen des folgenden Bausteins als Aufzählung zurück: eine Markdown-Liste, ein Punkt je Aussage, knapp und vollständig. Gib nur die Liste zurück, ohne Überschrift.',\n `Baustein:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n}\n\n/** In-block AI tasks (`rewrite` slot): rewrite text inside one block or a selection. */\nexport const inBlockTasks = (): AiTask[] => Object.values(AI_TASKS).filter(task => task.slot === 'rewrite')\n\n/** Block generation tasks (`generate` slot): produce new blocks after the caret or at the end of the document. */\nexport const blockTasks = (): AiTask[] => Object.values(AI_TASKS).filter(task => task.slot === 'generate' && !task.needsBlock && !task.programmatic)\n"],"mappings":"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"}
1
+ {"version":3,"file":"tasks.js","names":[],"sources":["../../../../src/features/ai/protocol/tasks.ts"],"sourcesContent":["import type { AiBlock } from '../../../core/feature'\nimport type { MessageKey, Translate } from '../../../i18n'\nimport { de } from '../../../i18n/de'\nimport type { AiLanguage } from '../../../settings'\n\nexport const AI_TASK_IDS = ['reformulate', 'generate', 'transcript-summary', 'transcript-minutes', 'transcript-action-items', 'fix', 'shorter', 'formal', 'casual', 'translate', 'summarize', 'custom', 'continue', 'fill-block', 'split-block', 'outline-sanitize', 'transfer-summary', 'transfer-bullets'] as const\nexport type AiTaskId = (typeof AI_TASK_IDS)[number]\n/**\n * How „Übernehmen“ applies the result: client-side over the captured selection, or server-side after the caret's block\n * or after the document's last block.\n */\nexport type AiApplyMode = 'replace-selection' | 'insert-after-block' | 'append-to-document'\n/** The block a `fill-block` run writes (the brief of `compositeAiBrief` plus the host's `aiBlockData`). */\nexport interface AiBlockInput {\n kind: { id: string, label: string, purpose?: string }\n path: readonly string[]\n title: string\n description: string\n fields: readonly { label: string, value: string }[]\n instructions: string\n contentTemplate: string\n body: string\n data: readonly { label: string, text: string }[]\n}\n/**\n * `users`: registered usernames, filled in by the generate route for tasks with `needsUsers` (never sent by the client).\n * `language`: target language of tasks with `needsLanguage`. `block`: the block of a `needsBlock` task.\n */\nexport interface AiInput { prompt: string, selection: string, context: string, users?: readonly string[], language?: AiLanguage, blocks?: readonly AiBlock[], block?: AiBlockInput }\nexport interface AiMessage { role: 'system' | 'user', content: string }\nexport interface AiTask {\n id: AiTaskId\n /** Catalog key of the task's name in the menus and the sidebar. */\n labelKey: MessageKey\n icon: string\n needsSelection: boolean\n needsPrompt: boolean\n /** Catalog key of the input placeholder of a task with `needsPrompt`; the other tasks name an optional hint. */\n promptLabelKey?: MessageKey\n apply: AiApplyMode\n /** Model role the task runs on: `rewrite` (in-block AI, text inside one block) or `generate` (block generation). */\n slot: 'rewrite' | 'generate'\n /** The context is the whole document (up to `AI_LIMITS.transcript`) and must not be empty. */\n wholeDocument?: boolean\n /** The generate route passes the registered usernames as `users`, so the model can write `@username` mentions. */\n needsUsers?: boolean\n /**\n * Catalog key of the title of the result: its `##` heading as shown and applied (`withTaskHeading`, in the editor's\n * language). A feature may format the applied result differently through `formatAiResult` (composite AI wraps it in\n * a `section` composite). The model is told not to write one.\n */\n headingKey?: MessageKey\n /** The request must name a target `language` (see `AI_LANGUAGES`). */\n needsLanguage?: boolean\n /** Offered as quick action in the selection's AI menu (AiSelectionMenu). */\n selectionMenu?: boolean\n /** Offered as quick action in the AI menu at the caret (`/ai`, AiSelectionMenu), not in the AI sidebar. */\n caretMenu?: boolean\n /** The request must carry `block`; offered only through a block action, never in the sidebar or menus. */\n needsBlock?: boolean\n /** Run only by a block action or command that sends its own request; never listed in the sidebar or the menus. */\n programmatic?: boolean\n messages: (input: AiInput) => AiMessage[]\n}\n\n/** `markdown` under the `##` title `heading`; it replaces a top-level (#/##) heading the model wrote anyway. */\nexport function withHeading(heading: string, markdown: string): string {\n return `## ${heading}\\n\\n${markdown.replace(/^\\s*#{1,2}[ \\t][^\\n]*/, '').trimStart()}`\n}\n\n/** Result Markdown as shown: for tasks with a heading, that heading (in the language of `t`) replaces a top-level (#/##) heading the model wrote anyway. */\nexport const withTaskHeading = (task: AiTask, markdown: string, t: Translate): string => task.headingKey ? withHeading(t(task.headingKey), markdown) : markdown\n\n/** Maximum input lengths (characters), enforced by the generate route; the client truncates the context to fit. */\nexport const AI_LIMITS = { prompt: 4000, selection: 20000, context: 12000, transcript: 60000, block: { text: 4000, contentTemplate: 8000, body: 20000, data: 20000 } } as const\n\n// i18n-ignore-start: model-facing prompt\n/** The system prompt of all tasks; `blocks` (the editor's host blocks) are listed after the core Markdown subset. */\nfunction aiSystem(blocks: readonly AiBlock[] = []): string {\n return [\n 'Du bist ein Schreibassistent in einem Texteditor.',\n 'Antworte ausschließlich mit dem fertigen Text als Markdown.',\n 'Erlaubt sind: Absätze, Überschriften (#, ##, ###), **fett**, *kursiv*, ~~durchgestrichen~~, `Code`, Codeblöcke mit ```, Aufzählungen, nummerierte Listen, Zitate mit > und Trennlinien (---)'\n + (blocks.length\n ? ` sowie diese eigenen Blöcke, wenn sie inhaltlich passen: ${blocks.map(block => `${block.label} – ${block.description} Markdown-Form: „${block.syntax.replace(/\\n/g, '\\\\n')}“, Beispiel: „${block.example.replace(/\\n/g, '\\\\n')}“`).join('; ')}.`\n : '.'),\n 'Transkript-Passagen (Redebeiträge von Sprecher:innen) schreibst du als Sprecherzeilen: jeder Redebeitrag ist ein eigener Absatz, der mit dem fett gesetzten Namen und Doppelpunkt beginnt, also **Name:** Text – eine Zeile pro Redebeitrag, dazwischen eine Leerzeile. Vorhandene Sprechernamen übernimmst du unverändert.',\n 'Beginne andere Absätze nicht mit fett gesetztem Text, der auf einen Doppelpunkt endet.',\n 'Keine Einleitung, keine Erklärungen, keine Rückfragen.',\n 'Schreibe in der Sprache des Dokuments.',\n ].join('\\n')\n}\n\nconst contextBlock = (context: string) => `Dokument (Kontext):\\n\"\"\"\\n${context}\\n\"\"\"`\n// i18n-ignore-end\n\n/**\n * Whole-transcript task whose result is appended to the document under the title `headingKey`. The prompt names that\n * title in German (prompts stay German whatever the editor's language).\n */\nfunction transcriptTask(\n id: AiTaskId,\n headingKey: MessageKey,\n icon: string,\n instruction: (users: readonly string[]) => string[],\n needsUsers = false,\n): AiTask {\n return {\n id,\n labelKey: headingKey,\n icon,\n needsSelection: false,\n needsPrompt: false,\n promptLabelKey: 'ai.task.transcript.promptLabel',\n apply: 'append-to-document',\n slot: 'generate',\n wholeDocument: true,\n needsUsers,\n headingKey,\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, context, users = [], blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n ...instruction(users),\n `Beginne ohne eigene Überschrift – die Überschrift „${de[headingKey]}“ wird automatisch vorangestellt. Zwischenüberschriften nur mit ### (z. B. „### Beschlüsse“), nie als fett gesetzte Zeile wie **Beschlüsse:**.`,\n 'Schreibe keine Sprecherzeilen (**Name:** …), sondern nenne Personen im Fließtext.',\n ...(prompt.trim() ? [`Hinweis: ${prompt.trim()}`] : []),\n `Transkript:\\n\"\"\"\\n${context}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n }\n}\n\n/** Quick action of the selection's AI menu: rewrites the selected excerpt according to `instruction`. */\nfunction selectionTask(\n id: AiTaskId,\n labelKey: MessageKey,\n icon: string,\n instruction: (input: AiInput) => string,\n extra: Partial<Pick<AiTask, 'needsPrompt' | 'promptLabelKey' | 'needsLanguage'>> = {},\n): AiTask {\n return {\n id,\n labelKey,\n icon,\n needsSelection: true,\n needsPrompt: false,\n apply: 'replace-selection',\n slot: 'rewrite',\n selectionMenu: true,\n ...extra,\n // i18n-ignore-start: model-facing prompt\n messages: input => [\n { role: 'system', content: aiSystem(input.blocks) },\n {\n role: 'user',\n content: [\n instruction(input),\n 'Gib nur den Text zurück, der den Textausschnitt ersetzen soll, ohne zusätzliche Überschrift. Behalte die Markdown-Formatierung und Sprecherzeilen bei.',\n contextBlock(input.context),\n `Textausschnitt:\\n\"\"\"\\n${input.selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n }\n}\n\nexport const AI_TASKS: Record<AiTaskId, AiTask> = {\n reformulate: {\n id: 'reformulate',\n labelKey: 'ai.task.reformulate.label',\n icon: 'i-lucide-refresh-cw',\n needsSelection: true,\n needsPrompt: false,\n promptLabelKey: 'ai.task.reformulate.promptLabel',\n apply: 'replace-selection',\n slot: 'rewrite',\n selectionMenu: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, selection, context, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Formuliere den folgenden Textausschnitt um. Behalte Bedeutung, Formatierung und ungefähre Länge bei.',\n 'Gib nur den Text zurück, der den Textausschnitt ersetzen soll, ohne zusätzliche Überschrift.',\n ...(prompt.trim() ? [`Hinweis: ${prompt.trim()}`] : []),\n contextBlock(context),\n `Textausschnitt:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n generate: {\n id: 'generate',\n labelKey: 'ai.task.generate.label',\n icon: 'i-lucide-sparkles',\n needsSelection: false,\n needsPrompt: true,\n promptLabelKey: 'ai.task.generate.promptLabel',\n apply: 'insert-after-block',\n slot: 'generate',\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, context, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Schreibe neuen Text für das Dokument nach folgender Anweisung. Gib nur den Text zurück, der eingefügt werden soll.',\n `Anweisung: ${prompt.trim()}`,\n contextBlock(context),\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n // i18n-ignore-start: model-facing prompt\n 'transcript-summary': transcriptTask('transcript-summary', 'ai.task.transcriptSummary.heading', 'i-lucide-text-quote', () => [\n 'Fasse das folgende Transkript zusammen: die wichtigsten Themen, Ergebnisse und offenen Punkte, knapp in wenigen Absätzen oder als Aufzählung.',\n ]),\n 'transcript-minutes': transcriptTask('transcript-minutes', 'ai.task.transcriptMinutes.heading', 'i-lucide-notebook-pen', () => [\n 'Erstelle ein Protokoll des folgenden Transkripts mit den Abschnitten „Teilnehmende“, „Themen“ (je Thema die wesentlichen Punkte), „Beschlüsse“ und „Nächste Schritte“ als Zwischenüberschriften und Aufzählungen darunter. Lass Abschnitte ohne Inhalt weg.',\n ]),\n 'transcript-action-items': transcriptTask('transcript-action-items', 'ai.task.transcriptActionItems.heading', 'i-lucide-list-checks', users => [\n 'Liste alle Aufgaben (Action Items) aus dem folgenden Transkript als Aufzählung auf: je Aufgabe ein Punkt der Form „Verantwortliche Person: Aufgabe (bis Frist)“; die Frist nur, wenn sie genannt wird. Ohne klar verantwortliche Person schreibe „Offen:“. Gibt es keine Aufgaben, antworte nur mit „Keine Aufgaben gefunden.“',\n users.length\n ? `Registrierte Benutzer:innen: ${users.map(u => `@${u}`).join(', ')}. Ist die verantwortliche Person eine davon (z. B. gleicher Vor- oder Nachname), schreibe sie genau als @benutzername; alle anderen Personen schreibe mit ihrem Namen ohne @.`\n : 'Schreibe verantwortliche Personen mit ihrem Namen, ohne @.',\n ], true),\n fix: selectionTask('fix', 'ai.task.fix.label', 'i-lucide-spell-check', () =>\n 'Korrigiere Rechtschreibung, Grammatik und Zeichensetzung im folgenden Textausschnitt. Ändere Wortwahl, Stil und Inhalt nicht. Ist nichts zu korrigieren, gib den Textausschnitt unverändert zurück.'),\n shorter: selectionTask('shorter', 'ai.task.shorter.label', 'i-lucide-shrink', () =>\n 'Kürze den folgenden Textausschnitt deutlich, etwa auf die Hälfte, ohne wesentliche Aussagen zu verlieren.'),\n formal: selectionTask('formal', 'ai.task.formal.label', 'i-lucide-briefcase', () =>\n 'Formuliere den folgenden Textausschnitt förmlicher und sachlicher. Behalte Bedeutung und ungefähre Länge bei.'),\n casual: selectionTask('casual', 'ai.task.casual.label', 'i-lucide-smile', () =>\n 'Formuliere den folgenden Textausschnitt lockerer und umgangssprachlicher. Behalte Bedeutung und ungefähre Länge bei.'),\n translate: selectionTask('translate', 'ai.task.translate.label', 'i-lucide-languages', ({ language }) =>\n `Übersetze den folgenden Textausschnitt in diese Sprache: ${language}. Schreibe abweichend von der Dokumentsprache auf ${language}; Sprechernamen bleiben unverändert.`, { needsLanguage: true }),\n summarize: selectionTask('summarize', 'ai.task.summarize.label', 'i-lucide-text-quote', () =>\n 'Fasse den folgenden Textausschnitt knapp zusammen.'),\n custom: selectionTask('custom', 'ai.task.custom.label', 'i-lucide-message-square-text', ({ prompt }) =>\n `Bearbeite den folgenden Textausschnitt nach dieser Anweisung: ${prompt.trim()}`, { needsPrompt: true, promptLabelKey: 'ai.task.custom.promptLabel' }),\n // i18n-ignore-end\n // `selection`: the document up to the caret line; `context`: the document after it (see AiSelectionMenu's caret mode).\n continue: {\n id: 'continue',\n labelKey: 'ai.task.continue.label',\n icon: 'i-lucide-pen-line',\n needsSelection: true,\n needsPrompt: false,\n apply: 'insert-after-block',\n slot: 'generate',\n caretMenu: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ selection, context, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Setze den folgenden Text an seinem Ende fort: schreibe die nächsten ein bis drei Absätze, passend zu Inhalt, Stil und Formatierung. Wiederhole nichts vom vorhandenen Text. Gib nur den neuen Text zurück, der am Ende eingefügt werden soll.',\n `Text bis zur Einfügestelle:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ...(context.trim() ? [`Danach folgt im Dokument (nur als Kontext, nicht fortsetzen oder wiederholen):\\n\"\"\"\\n${context}\\n\"\"\"`] : []),\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n 'fill-block': {\n id: 'fill-block',\n labelKey: 'ai.task.fillBlock.label',\n icon: 'i-lucide-wand-sparkles',\n needsSelection: false,\n needsPrompt: false,\n promptLabelKey: 'ai.task.fillBlock.promptLabel',\n apply: 'replace-selection',\n slot: 'generate',\n needsBlock: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, context, blocks, block }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n `Schreibe den Inhalt des Blocks „${block!.title || block!.kind.label}“ (Blocktyp: ${block!.kind.label}${block!.kind.purpose ? ` – ${block!.kind.purpose}` : ''}).`,\n ...(block!.path.length ? [`Der Block steht unter: ${block!.path.join(' › ')}.`] : []),\n ...(block!.description.trim() ? [`Beschreibung des Blocks: ${block!.description.trim()}`] : []),\n ...(block!.fields.length ? [`Angaben zum Block:\\n${block!.fields.map(f => `- ${f.label}: ${f.value}`).join('\\n')}`] : []),\n ...(block!.instructions.trim() ? [`Anweisung: ${block!.instructions.trim()}`] : []),\n ...(block!.contentTemplate.trim() ? [`Zieltext (Inhaltsvorlage; Platzhalter in geschweiften Klammern ausfüllen, Struktur und Reihenfolge beibehalten):\\n\"\"\"\\n${block!.contentTemplate.trim()}\\n\"\"\"`] : []),\n ...block!.data.map(d => `${d.label}:\\n\"\"\"\\n${d.text}\\n\"\"\"`),\n ...(block!.body.trim() ? [`Bisheriger Inhalt des Blocks (wird ersetzt; Brauchbares übernehmen):\\n\"\"\"\\n${block!.body.trim()}\\n\"\"\"`] : []),\n ...(prompt.trim() ? [`Hinweis: ${prompt.trim()}`] : []),\n contextBlock(context),\n 'Gib nur die Inhaltsblöcke des Blocks als Markdown zurück: keine Titelzeile, keine Felder, keinen :::-Zaun.',\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n // `prompt`: the target size in characters, `selection`: the Typed block's body as Markdown (see `splitCompositeWithAi`).\n 'split-block': {\n id: 'split-block',\n labelKey: 'ai.task.splitBlock.label',\n icon: 'i-lucide-sparkles',\n needsSelection: true,\n needsPrompt: true,\n apply: 'replace-selection',\n slot: 'generate',\n programmatic: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ prompt, selection, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n `Gliedere den folgenden Text in Abschnitte von etwa ${prompt.trim()} Zeichen: füge an sinnvollen Stellen Überschriften der Ebene 2 („## “) mit knappen, treffenden Titeln ein. Verändere den Text selbst nicht: kein Wort hinzufügen, entfernen oder umstellen, Formatierung und Absätze beibehalten. Gib den vollständigen Text mit den eingefügten Überschriften als Markdown zurück, ohne weitere Überschrift.`,\n `Text:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n // `selection`: the document's headings as a JSON list (see `sanitizeOutline`); the answer is a JSON list, not Markdown.\n 'outline-sanitize': {\n id: 'outline-sanitize',\n labelKey: 'ai.task.outlineSanitize.label',\n icon: 'i-lucide-list-tree',\n needsSelection: true,\n needsPrompt: false,\n apply: 'replace-selection',\n slot: 'generate',\n programmatic: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ selection }) => [\n { role: 'system', content: 'Du bist ein Assistent für die Gliederung von Dokumenten. Antworte ausschließlich mit der geforderten JSON-Liste, ohne Erklärung und ohne Codezaun.' },\n {\n role: 'user',\n content: [\n 'Hier ist die Gliederung eines Dokuments als JSON-Liste von Überschriften (i = Index, level = Ebene, text, chars = Zeichen des Textes darunter). Bereinige die Struktur: keine übersprungenen Ebenen; höchstens eine Überschrift der Ebene 1; Nummerierungen, Satzzeichen am Ende und doppelte Leerzeichen entfernen; einheitliche Schreibweise; eine Überschrift ohne Text darunter (chars = 0) mit der folgenden zusammenlegen, indem du ihren text leer lässt (sie wird entfernt). Ändere Bedeutung nicht. Antworte nur mit der JSON-Liste gleicher Länge und Reihenfolge: [{\"i\":0,\"level\":1,\"text\":\"…\"}, …]',\n `Gliederung:\\n${selection}`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n // `selection`: the transferred Typed block as Markdown (see `transferRequest` of the transfer feature).\n 'transfer-summary': {\n id: 'transfer-summary',\n labelKey: 'ai.task.transferSummary.label',\n icon: 'i-lucide-text-quote',\n needsSelection: true,\n needsPrompt: false,\n apply: 'insert-after-block',\n slot: 'generate',\n programmatic: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ selection, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Fasse den folgenden Block knapp in ein bis drei Absätzen zusammen. Behalte Fachbegriffe bei. Gib nur die Zusammenfassung als Markdown zurück, ohne Überschrift.',\n `Block:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n 'transfer-bullets': {\n id: 'transfer-bullets',\n labelKey: 'ai.task.transferBullets.label',\n icon: 'i-lucide-list',\n needsSelection: true,\n needsPrompt: false,\n apply: 'insert-after-block',\n slot: 'generate',\n programmatic: true,\n // i18n-ignore-start: model-facing prompt\n messages: ({ selection, blocks }) => [\n { role: 'system', content: aiSystem(blocks) },\n {\n role: 'user',\n content: [\n 'Gib die Kernaussagen des folgenden Blocks als Aufzählung zurück: eine Markdown-Liste, ein Punkt je Aussage, knapp und vollständig. Gib nur die Liste zurück, ohne Überschrift.',\n `Block:\\n\"\"\"\\n${selection}\\n\"\"\"`,\n ].join('\\n\\n'),\n },\n ],\n // i18n-ignore-end\n },\n}\n\n/** In-block AI tasks (`rewrite` Model role): rewrite text inside one block or a selection. */\nexport const inBlockTasks = (): AiTask[] => Object.values(AI_TASKS).filter(task => task.slot === 'rewrite')\n\n/** Block generation tasks (`generate` Model role): produce new blocks after the caret or at the end of the document. */\nexport const blockTasks = (): AiTask[] => Object.values(AI_TASKS).filter(task => task.slot === 'generate' && !task.needsBlock && !task.programmatic)\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AiSelectionMenu.js","names":[],"sources":["../../../../src/features/ai/react/AiSelectionMenu.tsx"],"sourcesContent":["import type { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'\nimport type { Editor } from '@tiptap/core'\nimport type { EditorState } from '@tiptap/pm/state'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { BubbleMenu } from '@tiptap/react/menus'\nimport { type FormEvent, useEffect, useRef, useState } from 'react'\nimport { parseBlocks, resolveRange } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Alert, AlertTitle } from '../../../react/ui/alert'\nimport { Button } from '../../../react/ui/button'\nimport { InputGroup, InputGroupAddon, InputGroupInput } from '../../../react/ui/input-group'\nimport { Popover, PopoverAnchor, PopoverContent } from '../../../react/ui/popover'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { useStore } from '../../../react/use-store'\nimport { AI_LANGUAGES } from '../../../settings'\nimport { aiSelectionHighlightKey } from '../ai-selection-highlight'\nimport { previewExtensions } from '../preview-extensions'\nimport { AI_LANGUAGE_KEYS } from '../protocol'\nimport { AI_SELECTION_MENU_KEY, type AiSelectionFlow } from '../selection-flow'\nimport { ModelQuickSwitch } from './ModelQuickSwitch'\nimport { useAiAssistant } from './use-ai-assistant'\nimport { useAiSelectionFlow } from './use-ai-selection-flow'\nimport { useNotifier } from '../../../react/use-notifier'\n\n\n/**\n * AI quick actions on a text selection: a bubble menu entry opens a popover with the selection tasks of the registry\n * (`selectionMenu`), the result streams into a preview and is applied over the range captured when the menu opened.\n * Caret mode (`/ai`, see `openAiMenuAtCaret`): the same popover opens at the caret with the `caretMenu` tasks and\n * `generate` as free prompt; the result is inserted at the caret line (`insertAtCaretLine`).\n * Needs the AiSelectionHighlight extension on the editor. The flow (`createAiSelectionFlow`) owns every step; this\n * component renders its state, places the popover and shows the preview.\n */\nexport function AiSelectionMenu({ editor }: { editor: Editor }) {\n const notify = useNotifier(editor)\n const { t } = useEditorI18n()\n const { model: assistant } = useAiAssistant(editor)\n const { settings } = useEditorSettings()\n const flow = useAiSelectionFlow({ editor, assistant, notify, t, language: () => settings.ai.translateLanguage })\n return flow && <AiSelectionMenuView editor={editor} flow={flow} />\n}\n\nfunction AiSelectionMenuView({ editor, flow }: { editor: Editor, flow: AiSelectionFlow }) {\n const state = useStore(flow.state)\n const { mode, picking, canApply, status, output, request } = state\n const promptInput = useRef<HTMLInputElement>(null)\n const latest = useRef({ editor, output })\n latest.current = { editor, output }\n\n const { t } = useEditorI18n()\n const requestLabel = request\n ? (request.language ? t('ai.menu.requestWithLanguage', { task: t(request.task.labelKey), language: t(AI_LANGUAGE_KEYS[request.language]) }) : t(request.task.labelKey))\n : undefined\n // Extra rows other features contribute (`aiMenuActions` of their extensions), offered while the selection qualifies.\n const menuActions = state.open ? flow.menuActions() : []\n\n const shouldShow: NonNullable<BubbleMenuPluginProps['shouldShow']> = ({ element, view }) =>\n (view.hasFocus() || element.contains(document.activeElement)) && flow.offersSelection()\n\n /** Caret mode: the prompt input gets the focus, so the instruction can be typed right away. */\n function onOpenAutoFocus(event: Event) {\n if (flow.state.get().mode !== 'caret') return\n event.preventDefault()\n promptInput.current?.focus()\n }\n\n\n // Popover anchor: the highlighted range (its plugin state follows local and remote edits); in caret mode the caret.\n const [anchor] = useState(() => {\n let anchorRect = new DOMRect()\n // The caret's position is resolved from its anchor once per editor state (the anchor is measured every frame).\n let caretAt: { state: EditorState, pos: number | null } | null = null\n const caretRect = (instance: Editor): DOMRect => {\n if (caretAt?.state !== instance.state) caretAt = { state: instance.state, pos: flow.range()?.from ?? null }\n if (caretAt.pos === null) return anchorRect\n const coords = instance.view.coordsAtPos(caretAt.pos)\n anchorRect = new DOMRect(coords.left, coords.top, 1, coords.bottom - coords.top)\n return anchorRect\n }\n return {\n current: {\n getBoundingClientRect(): DOMRect {\n const instance = latest.current.editor\n if (instance.isDestroyed) return anchorRect\n if (flow.state.get().mode === 'caret') return caretRect(instance)\n const highlight = aiSelectionHighlightKey.getState(instance.state)\n const highlighted = highlight && resolveRange(instance.state, highlight.anchor)\n if (!highlighted || highlighted.from >= highlighted.to) return anchorRect\n const { view } = instance\n const start = view.coordsAtPos(highlighted.from, 1)\n const end = view.coordsAtPos(highlighted.to, -1)\n // Across lines the range spans the editor's width.\n const bounds = view.dom.getBoundingClientRect()\n const oneLine = end.top < start.bottom\n const left = oneLine ? start.left : bounds.left\n const right = oneLine ? end.right : bounds.right\n anchorRect = new DOMRect(left, start.top, Math.max(right - left, 1), end.bottom - start.top)\n return anchorRect\n },\n get contextElement() {\n return latest.current.editor.isDestroyed ? undefined : latest.current.editor.view.dom\n },\n },\n }\n })\n\n const [extensions] = useState(() => previewExtensions(editor))\n const preview = useEditor({\n editable: false,\n extensions,\n immediatelyRender: true,\n shouldRerenderOnTransaction: false,\n editorProps: { attributes: { class: 'tiptap ai-selection-preview' } },\n })\n\n // Coalesce streamed deltas: re-parse the Markdown at most every 80 ms; speaker lines show as segments, as applied.\n const previewTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n useEffect(() => {\n if (previewTimer.current) return\n previewTimer.current = setTimeout(() => {\n previewTimer.current = undefined\n if (!preview || preview.isDestroyed) return\n const { editor: source, output: text } = latest.current\n preview.commands.setContent({ type: 'doc', content: parseBlocks(source, text).blocks }, { emitUpdate: false })\n }, 80)\n }, [output, preview])\n\n useEffect(() => () => {\n clearTimeout(previewTimer.current)\n previewTimer.current = undefined\n }, [])\n\n // Not dismissible while a result is shown or streams: only „Verwerfen“ ends it.\n const guard = (event: Event) => { if (!picking) event.preventDefault() }\n\n return (\n <>\n <BubbleMenu editor={editor} pluginKey={AI_SELECTION_MENU_KEY} shouldShow={shouldShow} options={{ placement: 'top', offset: 8 }} className=\"z-10\">\n <div className=\"flex rounded-md border border-border bg-background p-0.5 shadow-md\">\n <Button size=\"sm\" variant=\"ghost\" onClick={() => flow.openForSelection()}>\n <Icon name=\"i-lucide-sparkles\" />\n {t('ai.menu.trigger')}\n </Button>\n </div>\n </BubbleMenu>\n\n <Popover open={state.open} onOpenChange={(value) => { if (!value) flow.close(flow.state.get().mode === 'caret') }}>\n <PopoverAnchor virtualRef={anchor} />\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={4}\n updatePositionStrategy=\"always\"\n className=\"w-[30rem] max-w-[calc(100vw-2rem)] gap-0 p-0\"\n onOpenAutoFocus={onOpenAutoFocus}\n onCloseAutoFocus={event => event.preventDefault()}\n onEscapeKeyDown={guard}\n // The model and language menus (portalled) count as inside.\n onInteractOutside={(event) => {\n if (!picking || (event.target instanceof Element && event.target.closest('[role=\"listbox\"]'))) event.preventDefault()\n }}\n >\n {picking\n ? (\n <div className=\"flex flex-col gap-1 p-2\">\n {/* Caret mode generates blocks, selection mode rewrites text in the block: each has its own model slot. */}\n <div className=\"flex items-center justify-end gap-2 px-1 pb-1\">\n <span className=\"text-xs text-muted-foreground\">{t('ai.menu.model')}</span>\n <ModelQuickSwitch slot={mode === 'caret' ? 'generate' : 'rewrite'} size=\"xs\" className=\"w-56\" />\n </div>\n {state.actions.map(task => (\n <div key={task.id} className=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n className=\"flex-1 justify-start\"\n disabled={mode === 'caret' && task.needsSelection && !state.hasTextBefore}\n onClick={() => void flow.start(task)}\n >\n <Icon name={task.icon} />\n {t(task.labelKey)}\n </Button>\n {task.needsLanguage && (\n <Select value={state.language} onValueChange={value => flow.setLanguage(AI_LANGUAGES.find(candidate => candidate === value) ?? state.language)}>\n <SelectTrigger size=\"sm\" aria-label={t('ai.menu.targetLanguage')} className=\"w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" align=\"end\">\n {AI_LANGUAGES.map(item => <SelectItem key={item} value={item}>{t(AI_LANGUAGE_KEYS[item])}</SelectItem>)}\n </SelectContent>\n </Select>\n )}\n </div>\n ))}\n {menuActions.map(action => (\n <Button\n key={action.id}\n variant=\"ghost\"\n className=\"justify-start\"\n disabled={state.runningAction !== null && state.runningAction !== action.id}\n onClick={() => void flow.runAction(action)}\n >\n <Icon name={state.runningAction === action.id ? 'i-lucide-loader-circle' : action.icon} className={state.runningAction === action.id ? 'animate-spin' : undefined} />\n {action.label}\n </Button>\n ))}\n <form\n className=\"mt-1 flex gap-1 border-t border-border pt-2\"\n onSubmit={(event: FormEvent) => {\n event.preventDefault()\n void flow.submitPrompt()\n }}\n >\n <InputGroup className=\"flex-1\">\n <InputGroupAddon>\n <Icon name={state.freePrompt.icon} />\n </InputGroupAddon>\n <InputGroupInput ref={promptInput} value={state.prompt} placeholder={state.freePrompt.promptLabelKey && t(state.freePrompt.promptLabelKey)} onChange={event => flow.setPrompt(event.target.value)} />\n </InputGroup>\n <Button type=\"submit\" size=\"icon\" disabled={!state.prompt.trim()} aria-label={t('ai.menu.sendInstruction')}>\n <Icon name=\"i-lucide-arrow-up\" />\n </Button>\n </form>\n </div>\n )\n : (\n <div className=\"flex flex-col gap-3 p-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium\">\n {request && <Icon name={request.task.icon} />}\n <span className=\"flex-1 truncate\">{requestLabel}</span>\n {status === 'streaming' && <Icon name=\"i-lucide-loader-circle\" className=\"animate-spin text-muted-foreground\" />}\n </div>\n {state.error && (\n <Alert variant=\"destructive\">\n <AlertTitle>{state.error}</AlertTitle>\n </Alert>\n )}\n <div className=\"max-h-72 min-h-16 overflow-auto rounded-md border border-border p-3 text-sm\">\n {preview && <EditorContent editor={preview} />}\n </div>\n <div className=\"flex flex-wrap justify-end gap-2\">\n {status === 'streaming' && <Button size=\"sm\" variant=\"ghost\" onClick={flow.abort}>{t('ai.menu.cancel')}</Button>}\n <Button size=\"sm\" variant=\"ghost\" onClick={() => flow.close(true)}>{t('ai.menu.discard')}</Button>\n <Button size=\"sm\" variant=\"outline\" disabled={status === 'streaming'} onClick={() => void flow.retry()}>\n <Icon name=\"i-lucide-rotate-ccw\" />\n {t('ai.menu.retry')}\n </Button>\n {mode === 'caret'\n ? (\n <Button size=\"sm\" disabled={!canApply} onClick={flow.insertAtCaret}>\n <Icon name=\"i-lucide-text-cursor-input\" />\n {t('ai.menu.insert')}\n </Button>\n )\n : (\n <>\n <Button size=\"sm\" variant=\"outline\" disabled={!canApply} onClick={() => flow.apply('insert-below')}>\n <Icon name=\"i-lucide-list-plus\" />\n {t('ai.menu.insertBelow')}\n </Button>\n <Button size=\"sm\" disabled={!canApply} onClick={() => flow.apply('replace')}>\n <Icon name=\"i-lucide-replace\" />\n {t('ai.menu.replace')}\n </Button>\n </>\n )}\n </div>\n </div>\n )}\n </PopoverContent>\n </Popover>\n </>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AiSelectionMenu.js","names":[],"sources":["../../../../src/features/ai/react/AiSelectionMenu.tsx"],"sourcesContent":["import type { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu'\nimport type { Editor } from '@tiptap/core'\nimport type { EditorState } from '@tiptap/pm/state'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { BubbleMenu } from '@tiptap/react/menus'\nimport { type FormEvent, useEffect, useRef, useState } from 'react'\nimport { parseBlocks, resolveRange } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Alert, AlertTitle } from '../../../react/ui/alert'\nimport { Button } from '../../../react/ui/button'\nimport { InputGroup, InputGroupAddon, InputGroupInput } from '../../../react/ui/input-group'\nimport { Popover, PopoverAnchor, PopoverContent } from '../../../react/ui/popover'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { useStore } from '../../../react/use-store'\nimport { AI_LANGUAGES } from '../../../settings'\nimport { aiSelectionHighlightKey } from '../ai-selection-highlight'\nimport { previewExtensions } from '../preview-extensions'\nimport { AI_LANGUAGE_KEYS } from '../protocol'\nimport { AI_SELECTION_MENU_KEY, type AiSelectionFlow } from '../selection-flow'\nimport { ModelQuickSwitch } from './ModelQuickSwitch'\nimport { useAiAssistant } from './use-ai-assistant'\nimport { useAiSelectionFlow } from './use-ai-selection-flow'\nimport { useNotifier } from '../../../react/use-notifier'\n\n\n/**\n * AI quick actions on a text selection: a bubble menu entry opens a popover with the selection tasks of the registry\n * (`selectionMenu`), the result streams into a preview and is applied over the range captured when the menu opened.\n * Caret mode (`/ai`, see `openAiMenuAtCaret`): the same popover opens at the caret with the `caretMenu` tasks and\n * `generate` as free prompt; the result is inserted at the caret line (`insertAtCaretLine`).\n * Needs the AiSelectionHighlight extension on the editor. The flow (`createAiSelectionFlow`) owns every step; this\n * component renders its state, places the popover and shows the preview.\n */\nexport function AiSelectionMenu({ editor }: { editor: Editor }) {\n const notify = useNotifier(editor)\n const { t } = useEditorI18n()\n const { model: assistant } = useAiAssistant(editor)\n const { settings } = useEditorSettings()\n const flow = useAiSelectionFlow({ editor, assistant, notify, t, language: () => settings.ai.translateLanguage })\n return flow && <AiSelectionMenuView editor={editor} flow={flow} />\n}\n\nfunction AiSelectionMenuView({ editor, flow }: { editor: Editor, flow: AiSelectionFlow }) {\n const state = useStore(flow.state)\n const { mode, picking, canApply, status, output, request } = state\n const promptInput = useRef<HTMLInputElement>(null)\n const latest = useRef({ editor, output })\n latest.current = { editor, output }\n\n const { t } = useEditorI18n()\n const requestLabel = request\n ? (request.language ? t('ai.menu.requestWithLanguage', { task: t(request.task.labelKey), language: t(AI_LANGUAGE_KEYS[request.language]) }) : t(request.task.labelKey))\n : undefined\n // Extra rows other features contribute (`aiMenuActions` of their extensions), offered while the selection qualifies.\n const menuActions = state.open ? flow.menuActions() : []\n\n const shouldShow: NonNullable<BubbleMenuPluginProps['shouldShow']> = ({ element, view }) =>\n (view.hasFocus() || element.contains(document.activeElement)) && flow.offersSelection()\n\n /** Caret mode: the prompt input gets the focus, so the instruction can be typed right away. */\n function onOpenAutoFocus(event: Event) {\n if (flow.state.get().mode !== 'caret') return\n event.preventDefault()\n promptInput.current?.focus()\n }\n\n\n // Popover anchor: the highlighted range (its plugin state follows local and remote edits); in caret mode the caret.\n const [anchor] = useState(() => {\n let anchorRect = new DOMRect()\n // The caret's position is resolved from its anchor once per editor state (the anchor is measured every frame).\n let caretAt: { state: EditorState, pos: number | null } | null = null\n const caretRect = (instance: Editor): DOMRect => {\n if (caretAt?.state !== instance.state) caretAt = { state: instance.state, pos: flow.range()?.from ?? null }\n if (caretAt.pos === null) return anchorRect\n const coords = instance.view.coordsAtPos(caretAt.pos)\n anchorRect = new DOMRect(coords.left, coords.top, 1, coords.bottom - coords.top)\n return anchorRect\n }\n return {\n current: {\n getBoundingClientRect(): DOMRect {\n const instance = latest.current.editor\n if (instance.isDestroyed) return anchorRect\n if (flow.state.get().mode === 'caret') return caretRect(instance)\n const highlight = aiSelectionHighlightKey.getState(instance.state)\n const highlighted = highlight && resolveRange(instance.state, highlight.anchor)\n if (!highlighted || highlighted.from >= highlighted.to) return anchorRect\n const { view } = instance\n const start = view.coordsAtPos(highlighted.from, 1)\n const end = view.coordsAtPos(highlighted.to, -1)\n // Across lines the range spans the editor's width.\n const bounds = view.dom.getBoundingClientRect()\n const oneLine = end.top < start.bottom\n const left = oneLine ? start.left : bounds.left\n const right = oneLine ? end.right : bounds.right\n anchorRect = new DOMRect(left, start.top, Math.max(right - left, 1), end.bottom - start.top)\n return anchorRect\n },\n get contextElement() {\n return latest.current.editor.isDestroyed ? undefined : latest.current.editor.view.dom\n },\n },\n }\n })\n\n const [extensions] = useState(() => previewExtensions(editor))\n const preview = useEditor({\n editable: false,\n extensions,\n immediatelyRender: true,\n shouldRerenderOnTransaction: false,\n editorProps: { attributes: { class: 'tiptap ai-selection-preview' } },\n })\n\n // Coalesce streamed deltas: re-parse the Markdown at most every 80 ms; speaker lines show as segments, as applied.\n const previewTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n useEffect(() => {\n if (previewTimer.current) return\n previewTimer.current = setTimeout(() => {\n previewTimer.current = undefined\n if (!preview || preview.isDestroyed) return\n const { editor: source, output: text } = latest.current\n preview.commands.setContent({ type: 'doc', content: parseBlocks(source, text).blocks }, { emitUpdate: false })\n }, 80)\n }, [output, preview])\n\n useEffect(() => () => {\n clearTimeout(previewTimer.current)\n previewTimer.current = undefined\n }, [])\n\n // Not dismissible while a result is shown or streams: only „Verwerfen“ ends it.\n const guard = (event: Event) => { if (!picking) event.preventDefault() }\n\n return (\n <>\n <BubbleMenu editor={editor} pluginKey={AI_SELECTION_MENU_KEY} shouldShow={shouldShow} options={{ placement: 'top', offset: 8 }} className=\"z-10\">\n <div className=\"flex rounded-md border border-border bg-background p-0.5 shadow-md\">\n <Button size=\"sm\" variant=\"ghost\" onClick={() => flow.openForSelection()}>\n <Icon name=\"i-lucide-sparkles\" />\n {t('ai.menu.trigger')}\n </Button>\n </div>\n </BubbleMenu>\n\n <Popover open={state.open} onOpenChange={(value) => { if (!value) flow.close(flow.state.get().mode === 'caret') }}>\n <PopoverAnchor virtualRef={anchor} />\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={4}\n updatePositionStrategy=\"always\"\n className=\"w-[30rem] max-w-[calc(100vw-2rem)] gap-0 p-0\"\n onOpenAutoFocus={onOpenAutoFocus}\n onCloseAutoFocus={event => event.preventDefault()}\n onEscapeKeyDown={guard}\n // The model and language menus (portalled) count as inside.\n onInteractOutside={(event) => {\n if (!picking || (event.target instanceof Element && event.target.closest('[role=\"listbox\"]'))) event.preventDefault()\n }}\n >\n {picking\n ? (\n <div className=\"flex flex-col gap-1 p-2\">\n {/* Caret mode generates blocks, selection mode rewrites text in the block: each has its own Model role. */}\n <div className=\"flex items-center justify-end gap-2 px-1 pb-1\">\n <span className=\"text-xs text-muted-foreground\">{t('ai.menu.model')}</span>\n <ModelQuickSwitch slot={mode === 'caret' ? 'generate' : 'rewrite'} size=\"xs\" className=\"w-56\" />\n </div>\n {state.actions.map(task => (\n <div key={task.id} className=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n className=\"flex-1 justify-start\"\n disabled={mode === 'caret' && task.needsSelection && !state.hasTextBefore}\n onClick={() => void flow.start(task)}\n >\n <Icon name={task.icon} />\n {t(task.labelKey)}\n </Button>\n {task.needsLanguage && (\n <Select value={state.language} onValueChange={value => flow.setLanguage(AI_LANGUAGES.find(candidate => candidate === value) ?? state.language)}>\n <SelectTrigger size=\"sm\" aria-label={t('ai.menu.targetLanguage')} className=\"w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" align=\"end\">\n {AI_LANGUAGES.map(item => <SelectItem key={item} value={item}>{t(AI_LANGUAGE_KEYS[item])}</SelectItem>)}\n </SelectContent>\n </Select>\n )}\n </div>\n ))}\n {menuActions.map(action => (\n <Button\n key={action.id}\n variant=\"ghost\"\n className=\"justify-start\"\n disabled={state.runningAction !== null && state.runningAction !== action.id}\n onClick={() => void flow.runAction(action)}\n >\n <Icon name={state.runningAction === action.id ? 'i-lucide-loader-circle' : action.icon} className={state.runningAction === action.id ? 'animate-spin' : undefined} />\n {action.label}\n </Button>\n ))}\n <form\n className=\"mt-1 flex gap-1 border-t border-border pt-2\"\n onSubmit={(event: FormEvent) => {\n event.preventDefault()\n void flow.submitPrompt()\n }}\n >\n <InputGroup className=\"flex-1\">\n <InputGroupAddon>\n <Icon name={state.freePrompt.icon} />\n </InputGroupAddon>\n <InputGroupInput ref={promptInput} value={state.prompt} placeholder={state.freePrompt.promptLabelKey && t(state.freePrompt.promptLabelKey)} onChange={event => flow.setPrompt(event.target.value)} />\n </InputGroup>\n <Button type=\"submit\" size=\"icon\" disabled={!state.prompt.trim()} aria-label={t('ai.menu.sendInstruction')}>\n <Icon name=\"i-lucide-arrow-up\" />\n </Button>\n </form>\n </div>\n )\n : (\n <div className=\"flex flex-col gap-3 p-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium\">\n {request && <Icon name={request.task.icon} />}\n <span className=\"flex-1 truncate\">{requestLabel}</span>\n {status === 'streaming' && <Icon name=\"i-lucide-loader-circle\" className=\"animate-spin text-muted-foreground\" />}\n </div>\n {state.error && (\n <Alert variant=\"destructive\">\n <AlertTitle>{state.error}</AlertTitle>\n </Alert>\n )}\n <div className=\"max-h-72 min-h-16 overflow-auto rounded-md border border-border p-3 text-sm\">\n {preview && <EditorContent editor={preview} />}\n </div>\n <div className=\"flex flex-wrap justify-end gap-2\">\n {status === 'streaming' && <Button size=\"sm\" variant=\"ghost\" onClick={flow.abort}>{t('ai.menu.cancel')}</Button>}\n <Button size=\"sm\" variant=\"ghost\" onClick={() => flow.close(true)}>{t('ai.menu.discard')}</Button>\n <Button size=\"sm\" variant=\"outline\" disabled={status === 'streaming'} onClick={() => void flow.retry()}>\n <Icon name=\"i-lucide-rotate-ccw\" />\n {t('ai.menu.retry')}\n </Button>\n {mode === 'caret'\n ? (\n <Button size=\"sm\" disabled={!canApply} onClick={flow.insertAtCaret}>\n <Icon name=\"i-lucide-text-cursor-input\" />\n {t('ai.menu.insert')}\n </Button>\n )\n : (\n <>\n <Button size=\"sm\" variant=\"outline\" disabled={!canApply} onClick={() => flow.apply('insert-below')}>\n <Icon name=\"i-lucide-list-plus\" />\n {t('ai.menu.insertBelow')}\n </Button>\n <Button size=\"sm\" disabled={!canApply} onClick={() => flow.apply('replace')}>\n <Icon name=\"i-lucide-replace\" />\n {t('ai.menu.replace')}\n </Button>\n </>\n )}\n </div>\n </div>\n )}\n </PopoverContent>\n </Popover>\n </>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,gBAAgB,EAAE,UAA8B;CAC9D,MAAM,SAAS,YAAY,MAAM;CACjC,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,EAAE,OAAO,cAAc,eAAe,MAAM;CAClD,MAAM,EAAE,aAAa,kBAAkB;CACvC,MAAM,OAAO,mBAAmB;EAAE;EAAQ;EAAW;EAAQ;EAAG,gBAAgB,SAAS,GAAG;CAAkB,CAAC;CAC/G,OAAO,QAAQ,oBAAC,qBAAD;EAA6B;EAAc;CAAO,CAAA;AACnE;AAEA,SAAS,oBAAoB,EAAE,QAAQ,QAAmD;CACxF,MAAM,QAAQ,SAAS,KAAK,KAAK;CACjC,MAAM,EAAE,MAAM,SAAS,UAAU,QAAQ,QAAQ,YAAY;CAC7D,MAAM,cAAc,OAAyB,IAAI;CACjD,MAAM,SAAS,OAAO;EAAE;EAAQ;CAAO,CAAC;CACxC,OAAO,UAAU;EAAE;EAAQ;CAAO;CAElC,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,eAAe,UAChB,QAAQ,WAAW,EAAE,+BAA+B;EAAE,MAAM,EAAE,QAAQ,KAAK,QAAQ;EAAG,UAAU,EAAE,iBAAiB,QAAQ,SAAS;CAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,IACnK,KAAA;CAEJ,MAAM,cAAc,MAAM,OAAO,KAAK,YAAY,IAAI,CAAC;CAEvD,MAAM,cAAgE,EAAE,SAAS,YAC9E,KAAK,SAAS,KAAK,QAAQ,SAAS,SAAS,aAAa,MAAM,KAAK,gBAAgB;;CAGxF,SAAS,gBAAgB,OAAc;EACrC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,SAAS,SAAS;EACvC,MAAM,eAAe;EACrB,YAAY,SAAS,MAAM;CAC7B;CAIA,MAAM,CAAC,UAAU,eAAe;EAC9B,IAAI,aAAa,IAAI,QAAQ;EAE7B,IAAI,UAA6D;EACjE,MAAM,aAAa,aAA8B;GAC/C,IAAI,SAAS,UAAU,SAAS,OAAO,UAAU;IAAE,OAAO,SAAS;IAAO,KAAK,KAAK,MAAM,CAAC,EAAE,QAAQ;GAAK;GAC1G,IAAI,QAAQ,QAAQ,MAAM,OAAO;GACjC,MAAM,SAAS,SAAS,KAAK,YAAY,QAAQ,GAAG;GACpD,aAAa,IAAI,QAAQ,OAAO,MAAM,OAAO,KAAK,GAAG,OAAO,SAAS,OAAO,GAAG;GAC/E,OAAO;EACT;EACA,OAAO,EACL,SAAS;GACP,wBAAiC;IAC/B,MAAM,WAAW,OAAO,QAAQ;IAChC,IAAI,SAAS,aAAa,OAAO;IACjC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,SAAS,SAAS,OAAO,UAAU,QAAQ;IAChE,MAAM,YAAY,wBAAwB,SAAS,SAAS,KAAK;IACjE,MAAM,cAAc,aAAa,aAAa,SAAS,OAAO,UAAU,MAAM;IAC9E,IAAI,CAAC,eAAe,YAAY,QAAQ,YAAY,IAAI,OAAO;IAC/D,MAAM,EAAE,SAAS;IACjB,MAAM,QAAQ,KAAK,YAAY,YAAY,MAAM,CAAC;IAClD,MAAM,MAAM,KAAK,YAAY,YAAY,IAAI,EAAE;IAE/C,MAAM,SAAS,KAAK,IAAI,sBAAsB;IAC9C,MAAM,UAAU,IAAI,MAAM,MAAM;IAChC,MAAM,OAAO,UAAU,MAAM,OAAO,OAAO;IAC3C,MAAM,QAAQ,UAAU,IAAI,QAAQ,OAAO;IAC3C,aAAa,IAAI,QAAQ,MAAM,MAAM,KAAK,KAAK,IAAI,QAAQ,MAAM,CAAC,GAAG,IAAI,SAAS,MAAM,GAAG;IAC3F,OAAO;GACT;GACA,IAAI,iBAAiB;IACnB,OAAO,OAAO,QAAQ,OAAO,cAAc,KAAA,IAAY,OAAO,QAAQ,OAAO,KAAK;GACpF;EACF,EACF;CACF,CAAC;CAED,MAAM,CAAC,cAAc,eAAe,kBAAkB,MAAM,CAAC;CAC7D,MAAM,UAAU,UAAU;EACxB,UAAU;EACV;EACA,mBAAmB;EACnB,6BAA6B;EAC7B,aAAa,EAAE,YAAY,EAAE,OAAO,8BAA8B,EAAE;CACtE,CAAC;CAGD,MAAM,eAAe,OAAkD,KAAA,CAAS;CAChF,gBAAgB;EACd,IAAI,aAAa,SAAS;EAC1B,aAAa,UAAU,iBAAiB;GACtC,aAAa,UAAU,KAAA;GACvB,IAAI,CAAC,WAAW,QAAQ,aAAa;GACrC,MAAM,EAAE,QAAQ,QAAQ,QAAQ,SAAS,OAAO;GAChD,QAAQ,SAAS,WAAW;IAAE,MAAM;IAAO,SAAS,YAAY,QAAQ,IAAI,CAAC,CAAC;GAAO,GAAG,EAAE,YAAY,MAAM,CAAC;EAC/G,GAAG,EAAE;CACP,GAAG,CAAC,QAAQ,OAAO,CAAC;CAEpB,sBAAsB;EACpB,aAAa,aAAa,OAAO;EACjC,aAAa,UAAU,KAAA;CACzB,GAAG,CAAC,CAAC;CAGL,MAAM,SAAS,UAAiB;EAAE,IAAI,CAAC,SAAS,MAAM,eAAe;CAAE;CAEvE,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,YAAD;EAAoB;EAAQ,WAAW;EAAmC;EAAY,SAAS;GAAE,WAAW;GAAO,QAAQ;EAAE;EAAG,WAAU;EACxI,UAAA,oBAAC,OAAD;GAAK,WAAU;GACb,UAAA,qBAAC,QAAD;IAAQ,MAAK;IAAK,SAAQ;IAAQ,eAAe,KAAK,iBAAiB;IAAvE,UAAA,CACE,oBAAC,MAAD,EAAM,MAAK,oBAAqB,CAAA,GAC/B,EAAE,iBAAiB,CACd;;EACL,CAAA;CACK,CAAA,GAEZ,qBAAC,SAAD;EAAS,MAAM,MAAM;EAAM,eAAe,UAAU;GAAE,IAAI,CAAC,OAAO,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,SAAS,OAAO;EAAE;EAAhH,UAAA,CACE,oBAAC,eAAD,EAAe,YAAY,OAAS,CAAA,GACpC,oBAAC,gBAAD;GACE,MAAK;GACL,OAAM;GACN,YAAY;GACZ,wBAAuB;GACvB,WAAU;GACO;GACjB,mBAAkB,UAAS,MAAM,eAAe;GAChD,iBAAiB;GAEjB,oBAAoB,UAAU;IAC5B,IAAI,CAAC,WAAY,MAAM,kBAAkB,WAAW,MAAM,OAAO,QAAQ,oBAAkB,GAAI,MAAM,eAAe;GACtH;GAEC,UAAA,UAEK,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KAEE,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,oBAAC,QAAD;OAAM,WAAU;OAAiC,UAAA,EAAE,eAAe;MAAQ,CAAA,GAC1E,oBAAC,kBAAD;OAAkB,MAAM,SAAS,UAAU,aAAa;OAAW,MAAK;OAAK,WAAU;MAAQ,CAAA,CAC5F;;KACJ,MAAM,QAAQ,KAAI,SACjB,qBAAC,OAAD;MAAmB,WAAU;MAA7B,UAAA,CACE,qBAAC,QAAD;OACE,SAAQ;OACR,WAAU;OACV,UAAU,SAAS,WAAW,KAAK,kBAAkB,CAAC,MAAM;OAC5D,eAAe,KAAK,KAAK,MAAM,IAAI;OAJrC,UAAA,CAME,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA,GACvB,EAAE,KAAK,QAAQ,CACV;MACP,CAAA,GAAA,KAAK,iBACJ,qBAAC,QAAD;OAAQ,OAAO,MAAM;OAAU,gBAAe,UAAS,KAAK,YAAY,aAAa,MAAK,cAAa,cAAc,KAAK,KAAK,MAAM,QAAQ;OAA7I,UAAA,CACE,oBAAC,eAAD;QAAe,MAAK;QAAK,cAAY,EAAE,wBAAwB;QAAG,WAAU;QAC1E,UAAA,oBAAC,aAAD,CAAc,CAAA;OACD,CAAA,GACf,oBAAC,eAAD;QAAe,UAAS;QAAS,OAAM;QACpC,UAAA,aAAa,KAAI,SAAQ,oBAAC,YAAD;SAAuB,OAAO;SAAO,UAAA,EAAE,iBAAiB,KAAK;QAAc,GAA1D,IAA0D,CAAC;OACzF,CAAA,CACT;MAEP,CAAA,CAAA;KApBK,GAAA,KAAK,EAoBV,CACN;KACA,YAAY,KAAI,WACf,qBAAC,QAAD;MAEE,SAAQ;MACR,WAAU;MACV,UAAU,MAAM,kBAAkB,QAAQ,MAAM,kBAAkB,OAAO;MACzE,eAAe,KAAK,KAAK,UAAU,MAAM;MAL3C,UAAA,CAOE,oBAAC,MAAD;OAAM,MAAM,MAAM,kBAAkB,OAAO,KAAK,2BAA2B,OAAO;OAAM,WAAW,MAAM,kBAAkB,OAAO,KAAK,iBAAiB,KAAA;MAAY,CAAA,GACnK,OAAO,KACF;KARD,GAAA,OAAO,EAQN,CACT;KACD,qBAAC,QAAD;MACE,WAAU;MACV,WAAW,UAAqB;OAC9B,MAAM,eAAe;OACrB,KAAU,aAAa;MACzB;MALF,UAAA,CAOE,qBAAC,YAAD;OAAY,WAAU;OAAtB,UAAA,CACE,oBAAC,iBAAD,EAAA,UACE,oBAAC,MAAD,EAAM,MAAM,MAAM,WAAW,KAAO,CAAA,EACrB,CAAA,GACjB,oBAAC,iBAAD;QAAiB,KAAK;QAAa,OAAO,MAAM;QAAQ,aAAa,MAAM,WAAW,kBAAkB,EAAE,MAAM,WAAW,cAAc;QAAG,WAAU,UAAS,KAAK,UAAU,MAAM,OAAO,KAAK;OAAI,CAAA,CAC1L;MACZ,CAAA,GAAA,oBAAC,QAAD;OAAQ,MAAK;OAAS,MAAK;OAAO,UAAU,CAAC,MAAM,OAAO,KAAK;OAAG,cAAY,EAAE,yBAAyB;OACvG,UAAA,oBAAC,MAAD,EAAM,MAAK,oBAAqB,CAAA;MAC1B,CAAA,CACJ;;IACH;GAGL,CAAA,IAAA,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACG,WAAW,oBAAC,MAAD,EAAM,MAAM,QAAQ,KAAK,KAAO,CAAA;OAC5C,oBAAC,QAAD;QAAM,WAAU;QAAmB,UAAA;OAAmB,CAAA;OACrD,WAAW,eAAe,oBAAC,MAAD;QAAM,MAAK;QAAyB,WAAU;OAAsC,CAAA;MAC5G;;KACJ,MAAM,SACL,oBAAC,OAAD;MAAO,SAAQ;MACb,UAAA,oBAAC,YAAD,EAAA,UAAa,MAAM,MAAkB,CAAA;KAChC,CAAA;KAET,oBAAC,OAAD;MAAK,WAAU;MACZ,UAAA,WAAW,oBAAC,eAAD,EAAe,QAAQ,QAAU,CAAA;KAC1C,CAAA;KACL,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACG,WAAW,eAAe,oBAAC,QAAD;QAAQ,MAAK;QAAK,SAAQ;QAAQ,SAAS,KAAK;QAAQ,UAAA,EAAE,gBAAgB;OAAU,CAAA;OAC/G,oBAAC,QAAD;QAAQ,MAAK;QAAK,SAAQ;QAAQ,eAAe,KAAK,MAAM,IAAI;QAAI,UAAA,EAAE,iBAAiB;OAAU,CAAA;OACjG,qBAAC,QAAD;QAAQ,MAAK;QAAK,SAAQ;QAAU,UAAU,WAAW;QAAa,eAAe,KAAK,KAAK,MAAM;QAArG,UAAA,CACE,oBAAC,MAAD,EAAM,MAAK,sBAAuB,CAAA,GACjC,EAAE,eAAe,CACZ;;OACP,SAAS,UAEJ,qBAAC,QAAD;QAAQ,MAAK;QAAK,UAAU,CAAC;QAAU,SAAS,KAAK;QAArD,UAAA,CACE,oBAAC,MAAD,EAAM,MAAK,6BAA8B,CAAA,GACxC,EAAE,gBAAgB,CACb;OAGR,CAAA,IAAA,qBAAA,YAAA,EAAA,UAAA,CACE,qBAAC,QAAD;QAAQ,MAAK;QAAK,SAAQ;QAAU,UAAU,CAAC;QAAU,eAAe,KAAK,MAAM,cAAc;QAAjG,UAAA,CACE,oBAAC,MAAD,EAAM,MAAK,qBAAsB,CAAA,GAChC,EAAE,qBAAqB,CAClB;OACR,CAAA,GAAA,qBAAC,QAAD;QAAQ,MAAK;QAAK,UAAU,CAAC;QAAU,eAAe,KAAK,MAAM,SAAS;QAA1E,UAAA,CACE,oBAAC,MAAD,EAAM,MAAK,mBAAoB,CAAA,GAC9B,EAAE,iBAAiB,CACd;OACR,CAAA,CAAA,EAAA,CAAA;MAEL;;IACF;;EAEG,CAAA,CACT;CACT,CAAA,CAAA,EAAA,CAAA;AAEN"}
@@ -1,15 +1,15 @@
1
- import { ModelSlot } from "../../../settings/values.js";
1
+ import { ModelRole } from "../../../settings/values.js";
2
2
  import "../../../settings/index.js";
3
3
  import { Editor } from "@tiptap/core";
4
4
  //#region src/features/ai/react/ModelQuickSwitch.d.ts
5
5
  /**
6
- * Model profile an AI slot runs on. A quick switch *is* the slot setting (`settings.ai.slots[slot]`): the choice applies
7
- * to every feature of the slot, not just to the surrounding dialog. The trailing „Modelle verwalten…“ opens the
6
+ * Model profile an AI Model role runs on. A quick switch *is* the Model role setting (`settings.ai.roles[slot]`): the choice applies
7
+ * to every feature of the Model role, not just to the surrounding dialog. The trailing „Modelle verwalten…“ opens the
8
8
  * settings on the „KI“ tab. Inside an editor surface the chrome comes from its context; mounted beside one, pass its
9
9
  * `editor`.
10
10
  */
11
11
  export declare function ModelQuickSwitch({ slot, size, editor, className }: {
12
- slot: ModelSlot;
12
+ slot: ModelRole;
13
13
  size?: 'xs' | 'sm';
14
14
  /** The editor whose settings modal „Modelle verwalten…“ opens, when not mounted inside its editor surface. */
15
15
  editor?: Editor;
@@ -1,4 +1,4 @@
1
- import { MODEL_SLOT_KEYS } from "../protocol/models.js";
1
+ import { MODEL_ROLE_KEYS } from "../protocol/models.js";
2
2
  import { useEditorI18n } from "../../../react/use-editor-i18n.js";
3
3
  import { cn } from "../../../react/ui/utils.js";
4
4
  import { Icon } from "../../../react/Icon.js";
@@ -9,11 +9,11 @@ import { useModelProfiles } from "./use-model-profiles.js";
9
9
  import { useState } from "react";
10
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
11
  //#region src/features/ai/react/ModelQuickSwitch.tsx
12
- /** Item value of „Modelle verwalten…“; profile ids are preset ids (camelCase) or UUIDs, so it never collides. */
12
+ /** Item value of „Modelle verwalten…“; profile ids are Model preset ids (camelCase) or UUIDs, so it never collides. */
13
13
  const MANAGE = "__manage__";
14
14
  /**
15
- * Model profile an AI slot runs on. A quick switch *is* the slot setting (`settings.ai.slots[slot]`): the choice applies
16
- * to every feature of the slot, not just to the surrounding dialog. The trailing „Modelle verwalten…“ opens the
15
+ * Model profile an AI Model role runs on. A quick switch *is* the Model role setting (`settings.ai.roles[slot]`): the choice applies
16
+ * to every feature of the Model role, not just to the surrounding dialog. The trailing „Modelle verwalten…“ opens the
17
17
  * settings on the „KI“ tab. Inside an editor surface the chrome comes from its context; mounted beside one, pass its
18
18
  * `editor`.
19
19
  */
@@ -24,7 +24,7 @@ function ModelQuickSwitch({ slot, size = "sm", editor, className }) {
24
24
  const editorUi = useEditorUi(editor);
25
25
  const [open, setOpen] = useState(false);
26
26
  const current = slotProfile(slot);
27
- const slotLabel = t(MODEL_SLOT_KEYS[slot].labelKey);
27
+ const slotLabel = t(MODEL_ROLE_KEYS[slot].labelKey);
28
28
  function onValueChange(id) {
29
29
  if (id === MANAGE) {
30
30
  setOpen(false);
@@ -32,7 +32,7 @@ function ModelQuickSwitch({ slot, size = "sm", editor, className }) {
32
32
  return;
33
33
  }
34
34
  update((draft) => {
35
- draft.ai.slots[slot] = id;
35
+ draft.ai.roles[slot] = id;
36
36
  });
37
37
  }
38
38
  return /* @__PURE__ */ jsxs(Select, {
@@ -1 +1 @@
1
- {"version":3,"file":"ModelQuickSwitch.js","names":[],"sources":["../../../../src/features/ai/react/ModelQuickSwitch.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useState } from 'react'\nimport { useEditorUi } from '../../../react/editor-ui'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { cn } from '../../../react/ui/utils'\nimport type { ModelSlot } from '../../../settings'\nimport { MODEL_SLOT_KEYS } from '../protocol'\nimport { useModelProfiles } from './use-model-profiles'\n\n/** Item value of „Modelle verwalten…“; profile ids are preset ids (camelCase) or UUIDs, so it never collides. */\nconst MANAGE = '__manage__'\n\n/**\n * Model profile an AI slot runs on. A quick switch *is* the slot setting (`settings.ai.slots[slot]`): the choice applies\n * to every feature of the slot, not just to the surrounding dialog. The trailing „Modelle verwalten…“ opens the\n * settings on the „KI“ tab. Inside an editor surface the chrome comes from its context; mounted beside one, pass its\n * `editor`.\n */\nexport function ModelQuickSwitch({ slot, size = 'sm', editor, className }: {\n slot: ModelSlot\n size?: 'xs' | 'sm'\n /** The editor whose settings modal „Modelle verwalten…“ opens, when not mounted inside its editor surface. */\n editor?: Editor\n className?: string\n}) {\n const { update } = useEditorSettings()\n const { t } = useEditorI18n()\n const { profiles, slotProfile } = useModelProfiles()\n const editorUi = useEditorUi(editor)\n const [open, setOpen] = useState(false)\n const current = slotProfile(slot)\n const slotLabel = t(MODEL_SLOT_KEYS[slot].labelKey)\n\n function onValueChange(id: string) {\n if (id === MANAGE) {\n // Not a selection: keep the slot as it is.\n setOpen(false)\n editorUi?.openSettings('ai')\n return\n }\n update((draft) => { draft.ai.slots[slot] = id })\n }\n\n return (\n <Select value={current?.id ?? ''} onValueChange={onValueChange} open={open} onOpenChange={setOpen}>\n <SelectTrigger size=\"sm\" aria-label={t('ai.slot.ariaLabel', { slot: slotLabel })} className={cn('max-w-full', size === 'xs' && 'data-[size=sm]:h-6 text-xs', className)}>\n <SelectValue placeholder={slotLabel}>\n {current && (\n <>\n <Icon name={current.source === 'user' ? 'i-lucide-key-round' : 'i-lucide-server'} className=\"text-muted-foreground\" />\n <span className=\"truncate\">{current.label}</span>\n </>\n )}\n </SelectValue>\n </SelectTrigger>\n <SelectContent position=\"popper\" align=\"start\" className=\"min-w-72\">\n {profiles.map(profile => (\n <SelectItem key={profile.id} value={profile.id}>\n <Icon name={profile.source === 'preset' ? 'i-lucide-server' : 'i-lucide-key-round'} className=\"text-muted-foreground\" />\n <span className=\"flex min-w-0 flex-col items-start\">\n <span className=\"truncate\">{profile.label}</span>\n <span className=\"truncate text-xs text-muted-foreground\">{profile.model}</span>\n </span>\n </SelectItem>\n ))}\n {editorUi && (\n <>\n <SelectSeparator />\n <SelectItem value={MANAGE}>\n <Icon name=\"i-lucide-settings-2\" className=\"text-muted-foreground\" />\n {t('ai.slot.manage')}\n </SelectItem>\n </>\n )}\n </SelectContent>\n </Select>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAaA,MAAM,SAAS;;;;;;;AAQf,SAAgB,iBAAiB,EAAE,MAAM,OAAO,MAAM,QAAQ,aAM3D;CACD,MAAM,EAAE,WAAW,kBAAkB;CACrC,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,EAAE,UAAU,gBAAgB,iBAAiB;CACnD,MAAM,WAAW,YAAY,MAAM;CACnC,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,YAAY,EAAE,gBAAgB,KAAK,CAAC,QAAQ;CAElD,SAAS,cAAc,IAAY;EACjC,IAAI,OAAO,QAAQ;GAEjB,QAAQ,KAAK;GACb,UAAU,aAAa,IAAI;GAC3B;EACF;EACA,QAAQ,UAAU;GAAE,MAAM,GAAG,MAAM,QAAQ;EAAG,CAAC;CACjD;CAEA,OACE,qBAAC,QAAD;EAAQ,OAAO,SAAS,MAAM;EAAmB;EAAqB;EAAM,cAAc;EAA1F,UAAA,CACE,oBAAC,eAAD;GAAe,MAAK;GAAK,cAAY,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;GAAG,WAAW,GAAG,cAAc,SAAS,QAAQ,8BAA8B,SAAS;GACpK,UAAA,oBAAC,aAAD;IAAa,aAAa;IACvB,UAAA,WACC,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,MAAD;KAAM,MAAM,QAAQ,WAAW,SAAS,uBAAuB;KAAmB,WAAU;IAAyB,CAAA,GACrH,oBAAC,QAAD;KAAM,WAAU;KAAY,UAAA,QAAQ;IAAY,CAAA,CAChD,EAAA,CAAA;GAEO,CAAA;EACA,CAAA,GACf,qBAAC,eAAD;GAAe,UAAS;GAAS,OAAM;GAAQ,WAAU;GAAzD,UAAA,CACG,SAAS,KAAI,YACZ,qBAAC,YAAD;IAA6B,OAAO,QAAQ;IAA5C,UAAA,CACE,oBAAC,MAAD;KAAM,MAAM,QAAQ,WAAW,WAAW,oBAAoB;KAAsB,WAAU;IAAyB,CAAA,GACvH,qBAAC,QAAD;KAAM,WAAU;KAAhB,UAAA,CACE,oBAAC,QAAD;MAAM,WAAU;MAAY,UAAA,QAAQ;KAAY,CAAA,GAChD,oBAAC,QAAD;MAAM,WAAU;MAA0C,UAAA,QAAQ;KAAY,CAAA,CAC1E;IACI,CAAA,CAAA;GANK,GAAA,QAAQ,EAMb,CACb,GACA,YACC,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,iBAAD,CAAkB,CAAA,GAClB,qBAAC,YAAD;IAAY,OAAO;IAAnB,UAAA,CACE,oBAAC,MAAD;KAAM,MAAK;KAAsB,WAAU;IAAyB,CAAA,GACnE,EAAE,gBAAgB,CACT;GACZ,CAAA,CAAA,EAAA,CAAA,CAES;EACT,CAAA,CAAA;;AAEZ"}
1
+ {"version":3,"file":"ModelQuickSwitch.js","names":[],"sources":["../../../../src/features/ai/react/ModelQuickSwitch.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useState } from 'react'\nimport { useEditorUi } from '../../../react/editor-ui'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { cn } from '../../../react/ui/utils'\nimport type { ModelRole } from '../../../settings'\nimport { MODEL_ROLE_KEYS } from '../protocol'\nimport { useModelProfiles } from './use-model-profiles'\n\n/** Item value of „Modelle verwalten…“; profile ids are Model preset ids (camelCase) or UUIDs, so it never collides. */\nconst MANAGE = '__manage__'\n\n/**\n * Model profile an AI Model role runs on. A quick switch *is* the Model role setting (`settings.ai.roles[slot]`): the choice applies\n * to every feature of the Model role, not just to the surrounding dialog. The trailing „Modelle verwalten…“ opens the\n * settings on the „KI“ tab. Inside an editor surface the chrome comes from its context; mounted beside one, pass its\n * `editor`.\n */\nexport function ModelQuickSwitch({ slot, size = 'sm', editor, className }: {\n slot: ModelRole\n size?: 'xs' | 'sm'\n /** The editor whose settings modal „Modelle verwalten…“ opens, when not mounted inside its editor surface. */\n editor?: Editor\n className?: string\n}) {\n const { update } = useEditorSettings()\n const { t } = useEditorI18n()\n const { profiles, slotProfile } = useModelProfiles()\n const editorUi = useEditorUi(editor)\n const [open, setOpen] = useState(false)\n const current = slotProfile(slot)\n const slotLabel = t(MODEL_ROLE_KEYS[slot].labelKey)\n\n function onValueChange(id: string) {\n if (id === MANAGE) {\n // Not a selection: keep the Model role as it is.\n setOpen(false)\n editorUi?.openSettings('ai')\n return\n }\n update((draft) => { draft.ai.roles[slot] = id })\n }\n\n return (\n <Select value={current?.id ?? ''} onValueChange={onValueChange} open={open} onOpenChange={setOpen}>\n <SelectTrigger size=\"sm\" aria-label={t('ai.slot.ariaLabel', { slot: slotLabel })} className={cn('max-w-full', size === 'xs' && 'data-[size=sm]:h-6 text-xs', className)}>\n <SelectValue placeholder={slotLabel}>\n {current && (\n <>\n <Icon name={current.source === 'user' ? 'i-lucide-key-round' : 'i-lucide-server'} className=\"text-muted-foreground\" />\n <span className=\"truncate\">{current.label}</span>\n </>\n )}\n </SelectValue>\n </SelectTrigger>\n <SelectContent position=\"popper\" align=\"start\" className=\"min-w-72\">\n {profiles.map(profile => (\n <SelectItem key={profile.id} value={profile.id}>\n <Icon name={profile.source === 'preset' ? 'i-lucide-server' : 'i-lucide-key-round'} className=\"text-muted-foreground\" />\n <span className=\"flex min-w-0 flex-col items-start\">\n <span className=\"truncate\">{profile.label}</span>\n <span className=\"truncate text-xs text-muted-foreground\">{profile.model}</span>\n </span>\n </SelectItem>\n ))}\n {editorUi && (\n <>\n <SelectSeparator />\n <SelectItem value={MANAGE}>\n <Icon name=\"i-lucide-settings-2\" className=\"text-muted-foreground\" />\n {t('ai.slot.manage')}\n </SelectItem>\n </>\n )}\n </SelectContent>\n </Select>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAaA,MAAM,SAAS;;;;;;;AAQf,SAAgB,iBAAiB,EAAE,MAAM,OAAO,MAAM,QAAQ,aAM3D;CACD,MAAM,EAAE,WAAW,kBAAkB;CACrC,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,EAAE,UAAU,gBAAgB,iBAAiB;CACnD,MAAM,WAAW,YAAY,MAAM;CACnC,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,MAAM,UAAU,YAAY,IAAI;CAChC,MAAM,YAAY,EAAE,gBAAgB,KAAK,CAAC,QAAQ;CAElD,SAAS,cAAc,IAAY;EACjC,IAAI,OAAO,QAAQ;GAEjB,QAAQ,KAAK;GACb,UAAU,aAAa,IAAI;GAC3B;EACF;EACA,QAAQ,UAAU;GAAE,MAAM,GAAG,MAAM,QAAQ;EAAG,CAAC;CACjD;CAEA,OACE,qBAAC,QAAD;EAAQ,OAAO,SAAS,MAAM;EAAmB;EAAqB;EAAM,cAAc;EAA1F,UAAA,CACE,oBAAC,eAAD;GAAe,MAAK;GAAK,cAAY,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;GAAG,WAAW,GAAG,cAAc,SAAS,QAAQ,8BAA8B,SAAS;GACpK,UAAA,oBAAC,aAAD;IAAa,aAAa;IACvB,UAAA,WACC,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,MAAD;KAAM,MAAM,QAAQ,WAAW,SAAS,uBAAuB;KAAmB,WAAU;IAAyB,CAAA,GACrH,oBAAC,QAAD;KAAM,WAAU;KAAY,UAAA,QAAQ;IAAY,CAAA,CAChD,EAAA,CAAA;GAEO,CAAA;EACA,CAAA,GACf,qBAAC,eAAD;GAAe,UAAS;GAAS,OAAM;GAAQ,WAAU;GAAzD,UAAA,CACG,SAAS,KAAI,YACZ,qBAAC,YAAD;IAA6B,OAAO,QAAQ;IAA5C,UAAA,CACE,oBAAC,MAAD;KAAM,MAAM,QAAQ,WAAW,WAAW,oBAAoB;KAAsB,WAAU;IAAyB,CAAA,GACvH,qBAAC,QAAD;KAAM,WAAU;KAAhB,UAAA,CACE,oBAAC,QAAD;MAAM,WAAU;MAAY,UAAA,QAAQ;KAAY,CAAA,GAChD,oBAAC,QAAD;MAAM,WAAU;MAA0C,UAAA,QAAQ;KAAY,CAAA,CAC1E;IACI,CAAA,CAAA;GANK,GAAA,QAAQ,EAMb,CACb,GACA,YACC,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,iBAAD,CAAkB,CAAA,GAClB,qBAAC,YAAD;IAAY,OAAO;IAAnB,UAAA,CACE,oBAAC,MAAD;KAAM,MAAK;KAAsB,WAAU;IAAyB,CAAA,GACnE,EAAE,gBAAgB,CACT;GACZ,CAAA,CAAA,EAAA,CAAA,CAES;EACT,CAAA,CAAA;;AAEZ"}
@@ -1,5 +1,5 @@
1
1
  //#region src/features/ai/react/SettingsAiTab.d.ts
2
- /** „KI“: writing aids, the model profile per slot, and model profiles (server presets and own profiles). */
2
+ /** „KI“: writing aids, the model profile per Model role, and model profiles (Model presets and own profiles). */
3
3
  export declare function SettingsAiTab(): import("react").JSX.Element;
4
4
  //#endregion
5
5
  //# sourceMappingURL=SettingsAiTab.d.ts.map
@@ -1,5 +1,5 @@
1
- import { AI_LANGUAGE_KEYS, MODEL_SLOT_KEYS } from "../protocol/models.js";
2
- import { AI_LANGUAGES, MODEL_SLOTS } from "../../../settings/values.js";
1
+ import { AI_LANGUAGE_KEYS, MODEL_ROLE_KEYS } from "../protocol/models.js";
2
+ import { AI_LANGUAGES, MODEL_ROLES } from "../../../settings/values.js";
3
3
  import { useEditorI18n } from "../../../react/use-editor-i18n.js";
4
4
  import { useEditorSettings } from "../../../react/settings.js";
5
5
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
@@ -28,7 +28,7 @@ function Row({ label, description, htmlFor, children }) {
28
28
  }), children]
29
29
  });
30
30
  }
31
- /** „KI“: writing aids, the model profile per slot, and model profiles (server presets and own profiles). */
31
+ /** „KI“: writing aids, the model profile per Model role, and model profiles (Model presets and own profiles). */
32
32
  function SettingsAiTab() {
33
33
  const { settings, update } = useEditorSettings();
34
34
  const { t } = useEditorI18n();
@@ -78,11 +78,11 @@ function SettingsAiTab() {
78
78
  })]
79
79
  }),
80
80
  /* @__PURE__ */ jsx(SettingsSection, {
81
- title: t("ai.settings.slots.title"),
82
- description: t("ai.settings.slots.description"),
83
- children: MODEL_SLOTS.map((slot) => /* @__PURE__ */ jsx(Row, {
84
- label: t(MODEL_SLOT_KEYS[slot].labelKey),
85
- description: t(MODEL_SLOT_KEYS[slot].descriptionKey),
81
+ title: t("ai.settings.roles.title"),
82
+ description: t("ai.settings.roles.description"),
83
+ children: MODEL_ROLES.map((slot) => /* @__PURE__ */ jsx(Row, {
84
+ label: t(MODEL_ROLE_KEYS[slot].labelKey),
85
+ description: t(MODEL_ROLE_KEYS[slot].descriptionKey),
86
86
  children: /* @__PURE__ */ jsx(ModelQuickSwitch, {
87
87
  slot,
88
88
  className: "w-64"
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsAiTab.js","names":[],"sources":["../../../../src/features/ai/react/SettingsAiTab.tsx"],"sourcesContent":["import { type ReactNode, useId } from 'react'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { SettingsSection } from '../../../react/SettingsSection'\nimport { Label } from '../../../react/ui/label'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { Switch } from '../../../react/ui/switch'\nimport { AI_LANGUAGES, MODEL_SLOTS } from '../../../settings'\nimport { AI_LANGUAGE_KEYS, MODEL_SLOT_KEYS } from '../protocol'\nimport { ModelQuickSwitch } from './ModelQuickSwitch'\nimport { SettingsModelProfiles } from './SettingsModelProfiles'\n\n/** A settings row: label and description left, the control right (the counterpart of `UFormField orientation=\"horizontal\"`). */\nfunction Row({ label, description, htmlFor, children }: { label: string, description: string, htmlFor?: string, children: ReactNode }) {\n return (\n <div className=\"flex items-start justify-between gap-4 py-3\">\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <Label htmlFor={htmlFor} className=\"text-sm font-medium text-foreground\">{label}</Label>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n {children}\n </div>\n )\n}\n\n/** „KI“: writing aids, the model profile per slot, and model profiles (server presets and own profiles). */\nexport function SettingsAiTab() {\n const { settings, update } = useEditorSettings()\n const { t } = useEditorI18n()\n const autocompleteId = useId()\n const languageId = useId()\n\n return (\n <div className=\"flex flex-col gap-8\">\n <SettingsSection title={t('ai.settings.writing.title')}>\n <Row label={t('ai.settings.autocomplete.label')} description={t('ai.settings.autocomplete.description')} htmlFor={autocompleteId}>\n <Switch\n id={autocompleteId}\n checked={settings.ai.autocomplete}\n onCheckedChange={checked => update((draft) => { draft.ai.autocomplete = checked })}\n />\n </Row>\n <Row label={t('ai.settings.language.label')} description={t('ai.settings.language.description')} htmlFor={languageId}>\n <Select\n value={settings.ai.translateLanguage}\n onValueChange={(value) => {\n const language = AI_LANGUAGES.find(candidate => candidate === value)\n if (language) update((draft) => { draft.ai.translateLanguage = language })\n }}\n >\n <SelectTrigger id={languageId} className=\"w-64\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" align=\"end\">\n {AI_LANGUAGES.map(language => <SelectItem key={language} value={language}>{t(AI_LANGUAGE_KEYS[language])}</SelectItem>)}\n </SelectContent>\n </Select>\n </Row>\n </SettingsSection>\n\n <SettingsSection title={t('ai.settings.slots.title')} description={t('ai.settings.slots.description')}>\n {MODEL_SLOTS.map(slot => (\n <Row key={slot} label={t(MODEL_SLOT_KEYS[slot].labelKey)} description={t(MODEL_SLOT_KEYS[slot].descriptionKey)}>\n <ModelQuickSwitch slot={slot} className=\"w-64\" />\n </Row>\n ))}\n </SettingsSection>\n\n <SettingsSection\n title={t('ai.settings.profiles.title')}\n description={t('ai.settings.profiles.description')}\n >\n <SettingsModelProfiles />\n </SettingsSection>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAS,IAAI,EAAE,OAAO,aAAa,SAAS,YAA2F;CACrI,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,oBAAC,OAAD;IAAgB;IAAS,WAAU;IAAuC,UAAA;GAAa,CAAA,GACvF,oBAAC,KAAD;IAAG,WAAU;IAAiC,UAAA;GAAe,CAAA,CAC1D;EACJ,CAAA,GAAA,QACE;;AAET;;AAGA,SAAgB,gBAAgB;CAC9B,MAAM,EAAE,UAAU,WAAW,kBAAkB;CAC/C,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,iBAAiB,MAAM;CAC7B,MAAM,aAAa,MAAM;CAEzB,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,qBAAC,iBAAD;IAAiB,OAAO,EAAE,2BAA2B;IAArD,UAAA,CACE,oBAAC,KAAD;KAAK,OAAO,EAAE,gCAAgC;KAAG,aAAa,EAAE,sCAAsC;KAAG,SAAS;KAChH,UAAA,oBAAC,QAAD;MACE,IAAI;MACJ,SAAS,SAAS,GAAG;MACrB,kBAAiB,YAAW,QAAQ,UAAU;OAAE,MAAM,GAAG,eAAe;MAAQ,CAAC;KAClF,CAAA;IACE,CAAA,GACL,oBAAC,KAAD;KAAK,OAAO,EAAE,4BAA4B;KAAG,aAAa,EAAE,kCAAkC;KAAG,SAAS;KACxG,UAAA,qBAAC,QAAD;MACE,OAAO,SAAS,GAAG;MACnB,gBAAgB,UAAU;OACxB,MAAM,WAAW,aAAa,MAAK,cAAa,cAAc,KAAK;OACnE,IAAI,UAAU,QAAQ,UAAU;QAAE,MAAM,GAAG,oBAAoB;OAAS,CAAC;MAC3E;MALF,UAAA,CAOE,oBAAC,eAAD;OAAe,IAAI;OAAY,WAAU;OACvC,UAAA,oBAAC,aAAD,CAAc,CAAA;MACD,CAAA,GACf,oBAAC,eAAD;OAAe,UAAS;OAAS,OAAM;OACpC,UAAA,aAAa,KAAI,aAAY,oBAAC,YAAD;QAA2B,OAAO;QAAW,UAAA,EAAE,iBAAiB,SAAS;OAAc,GAAtE,QAAsE,CAAC;MACzG,CAAA,CACT;;IACL,CAAA,CACU;;GAEjB,oBAAC,iBAAD;IAAiB,OAAO,EAAE,yBAAyB;IAAG,aAAa,EAAE,+BAA+B;IACjG,UAAA,YAAY,KAAI,SACf,oBAAC,KAAD;KAAgB,OAAO,EAAE,gBAAgB,KAAK,CAAC,QAAQ;KAAG,aAAa,EAAE,gBAAgB,KAAK,CAAC,cAAc;KAC3G,UAAA,oBAAC,kBAAD;MAAwB;MAAM,WAAU;KAAQ,CAAA;IAC7C,GAFK,IAEL,CACN;GACc,CAAA;GAEjB,oBAAC,iBAAD;IACE,OAAO,EAAE,4BAA4B;IACrC,aAAa,EAAE,kCAAkC;IAEjD,UAAA,oBAAC,uBAAD,CAAwB,CAAA;GACT,CAAA;EACd;;AAET"}
1
+ {"version":3,"file":"SettingsAiTab.js","names":[],"sources":["../../../../src/features/ai/react/SettingsAiTab.tsx"],"sourcesContent":["import { type ReactNode, useId } from 'react'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { SettingsSection } from '../../../react/SettingsSection'\nimport { Label } from '../../../react/ui/label'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../react/ui/select'\nimport { Switch } from '../../../react/ui/switch'\nimport { AI_LANGUAGES, MODEL_ROLES } from '../../../settings'\nimport { AI_LANGUAGE_KEYS, MODEL_ROLE_KEYS } from '../protocol'\nimport { ModelQuickSwitch } from './ModelQuickSwitch'\nimport { SettingsModelProfiles } from './SettingsModelProfiles'\n\n/** A settings row: label and description left, the control right (the counterpart of `UFormField orientation=\"horizontal\"`). */\nfunction Row({ label, description, htmlFor, children }: { label: string, description: string, htmlFor?: string, children: ReactNode }) {\n return (\n <div className=\"flex items-start justify-between gap-4 py-3\">\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <Label htmlFor={htmlFor} className=\"text-sm font-medium text-foreground\">{label}</Label>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n {children}\n </div>\n )\n}\n\n/** „KI“: writing aids, the model profile per Model role, and model profiles (Model presets and own profiles). */\nexport function SettingsAiTab() {\n const { settings, update } = useEditorSettings()\n const { t } = useEditorI18n()\n const autocompleteId = useId()\n const languageId = useId()\n\n return (\n <div className=\"flex flex-col gap-8\">\n <SettingsSection title={t('ai.settings.writing.title')}>\n <Row label={t('ai.settings.autocomplete.label')} description={t('ai.settings.autocomplete.description')} htmlFor={autocompleteId}>\n <Switch\n id={autocompleteId}\n checked={settings.ai.autocomplete}\n onCheckedChange={checked => update((draft) => { draft.ai.autocomplete = checked })}\n />\n </Row>\n <Row label={t('ai.settings.language.label')} description={t('ai.settings.language.description')} htmlFor={languageId}>\n <Select\n value={settings.ai.translateLanguage}\n onValueChange={(value) => {\n const language = AI_LANGUAGES.find(candidate => candidate === value)\n if (language) update((draft) => { draft.ai.translateLanguage = language })\n }}\n >\n <SelectTrigger id={languageId} className=\"w-64\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" align=\"end\">\n {AI_LANGUAGES.map(language => <SelectItem key={language} value={language}>{t(AI_LANGUAGE_KEYS[language])}</SelectItem>)}\n </SelectContent>\n </Select>\n </Row>\n </SettingsSection>\n\n <SettingsSection title={t('ai.settings.roles.title')} description={t('ai.settings.roles.description')}>\n {MODEL_ROLES.map(slot => (\n <Row key={slot} label={t(MODEL_ROLE_KEYS[slot].labelKey)} description={t(MODEL_ROLE_KEYS[slot].descriptionKey)}>\n <ModelQuickSwitch slot={slot} className=\"w-64\" />\n </Row>\n ))}\n </SettingsSection>\n\n <SettingsSection\n title={t('ai.settings.profiles.title')}\n description={t('ai.settings.profiles.description')}\n >\n <SettingsModelProfiles />\n </SettingsSection>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAS,IAAI,EAAE,OAAO,aAAa,SAAS,YAA2F;CACrI,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,oBAAC,OAAD;IAAgB;IAAS,WAAU;IAAuC,UAAA;GAAa,CAAA,GACvF,oBAAC,KAAD;IAAG,WAAU;IAAiC,UAAA;GAAe,CAAA,CAC1D;EACJ,CAAA,GAAA,QACE;;AAET;;AAGA,SAAgB,gBAAgB;CAC9B,MAAM,EAAE,UAAU,WAAW,kBAAkB;CAC/C,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,iBAAiB,MAAM;CAC7B,MAAM,aAAa,MAAM;CAEzB,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,qBAAC,iBAAD;IAAiB,OAAO,EAAE,2BAA2B;IAArD,UAAA,CACE,oBAAC,KAAD;KAAK,OAAO,EAAE,gCAAgC;KAAG,aAAa,EAAE,sCAAsC;KAAG,SAAS;KAChH,UAAA,oBAAC,QAAD;MACE,IAAI;MACJ,SAAS,SAAS,GAAG;MACrB,kBAAiB,YAAW,QAAQ,UAAU;OAAE,MAAM,GAAG,eAAe;MAAQ,CAAC;KAClF,CAAA;IACE,CAAA,GACL,oBAAC,KAAD;KAAK,OAAO,EAAE,4BAA4B;KAAG,aAAa,EAAE,kCAAkC;KAAG,SAAS;KACxG,UAAA,qBAAC,QAAD;MACE,OAAO,SAAS,GAAG;MACnB,gBAAgB,UAAU;OACxB,MAAM,WAAW,aAAa,MAAK,cAAa,cAAc,KAAK;OACnE,IAAI,UAAU,QAAQ,UAAU;QAAE,MAAM,GAAG,oBAAoB;OAAS,CAAC;MAC3E;MALF,UAAA,CAOE,oBAAC,eAAD;OAAe,IAAI;OAAY,WAAU;OACvC,UAAA,oBAAC,aAAD,CAAc,CAAA;MACD,CAAA,GACf,oBAAC,eAAD;OAAe,UAAS;OAAS,OAAM;OACpC,UAAA,aAAa,KAAI,aAAY,oBAAC,YAAD;QAA2B,OAAO;QAAW,UAAA,EAAE,iBAAiB,SAAS;OAAc,GAAtE,QAAsE,CAAC;MACzG,CAAA,CACT;;IACL,CAAA,CACU;;GAEjB,oBAAC,iBAAD;IAAiB,OAAO,EAAE,yBAAyB;IAAG,aAAa,EAAE,+BAA+B;IACjG,UAAA,YAAY,KAAI,SACf,oBAAC,KAAD;KAAgB,OAAO,EAAE,gBAAgB,KAAK,CAAC,QAAQ;KAAG,aAAa,EAAE,gBAAgB,KAAK,CAAC,cAAc;KAC3G,UAAA,oBAAC,kBAAD;MAAwB;MAAM,WAAU;KAAQ,CAAA;IAC7C,GAFK,IAEL,CACN;GACc,CAAA;GAEjB,oBAAC,iBAAD;IACE,OAAO,EAAE,4BAA4B;IACrC,aAAa,EAAE,kCAAkC;IAEjD,UAAA,oBAAC,uBAAD,CAAwB,CAAA;GACT,CAAA;EACd;;AAET"}
@@ -1,6 +1,6 @@
1
1
  //#region src/features/ai/react/SettingsModelProfiles.d.ts
2
2
  /**
3
- * Section of the „KI“ tab: the server's presets and this browser's own profiles. Profiles are assigned to slots in the
3
+ * Section of the „KI“ tab: the server's Model presets and this browser's own profiles. Profiles are assigned to Model roles in the
4
4
  * section above.
5
5
  */
6
6
  export declare function SettingsModelProfiles(): import("react").JSX.Element;
@@ -34,7 +34,7 @@ function Field({ label, description, required, htmlFor, children }) {
34
34
  });
35
35
  }
36
36
  /**
37
- * Section of the „KI“ tab: the server's presets and this browser's own profiles. Profiles are assigned to slots in the
37
+ * Section of the „KI“ tab: the server's Model presets and this browser's own profiles. Profiles are assigned to Model roles in the
38
38
  * section above.
39
39
  */
40
40
  function SettingsModelProfiles() {
@@ -1 +1 @@
1
- {"version":3,"file":"SettingsModelProfiles.js","names":[],"sources":["../../../../src/features/ai/react/SettingsModelProfiles.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react'\nimport { fetchErrorMessage } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorContext } from '../../../react/context'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Alert, AlertTitle } from '../../../react/ui/alert'\nimport { Badge } from '../../../react/ui/badge'\nimport { Button } from '../../../react/ui/button'\nimport { Input } from '../../../react/ui/input'\nimport { Label } from '../../../react/ui/label'\nimport { modelRefSchema, type UserModelProfile } from '../protocol'\nimport { isRateLimited } from '../rate-limit'\nimport { useModelProfiles } from './use-model-profiles'\n\n/** Result of „Testen“; replaced (not mutated) per run, so a stale run recognizes it was superseded. */\ntype TestResult = { state: 'running' } | { state: 'ok' } | { state: 'error', message: string }\n\n/** A labelled form row (the counterpart of Nuxt UI's `UFormField`). */\nfunction Field({ label, description, required, htmlFor, children }: { label: string, description?: string, required?: boolean, htmlFor: string, children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor={htmlFor}>\n {label}\n {required && <span className=\"text-destructive\">*</span>}\n </Label>\n {description && <p className=\"text-xs text-muted-foreground\">{description}</p>}\n {children}\n </div>\n )\n}\n\n/**\n * Section of the „KI“ tab: the server's presets and this browser's own profiles. Profiles are assigned to slots in the\n * section above.\n */\nexport function SettingsModelProfiles() {\n const app = useEditorContext()\n const { t } = useEditorI18n()\n const { presets, userProfiles, saveUserProfile, removeUserProfile } = useModelProfiles()\n\n /** The profile being added or edited; null while the form is closed. */\n const [draft, setDraft] = useState<UserModelProfile | null>(null)\n const [test, setTest] = useState<TestResult | null>(null)\n // The result object of the latest run, so a stale run recognizes it was superseded.\n const latestTest = useRef<TestResult | null>(null)\n\n const draftRef = useMemo(() => draft && { endpoint: draft.endpoint.trim(), model: draft.model.trim(), apiKey: draft.apiKey.trim() }, [draft])\n const draftValid = !!draft?.label.trim() && modelRefSchema.safeParse(draftRef).success\n // A test result belongs to the values it ran with.\n const endpoint = draftRef?.endpoint\n const model = draftRef?.model\n const apiKey = draftRef?.apiKey\n useEffect(() => {\n latestTest.current = null\n setTest(null)\n }, [endpoint, model, apiKey])\n\n function edit(profile?: UserModelProfile) {\n setDraft(profile ? { ...profile } : { id: crypto.randomUUID(), label: '', endpoint: '', model: '', apiKey: '' })\n latestTest.current = null\n setTest(null)\n }\n\n function save() {\n if (!draft || !draftRef || !draftValid) return\n saveUserProfile({ id: draft.id, label: draft.label.trim(), ...draftRef })\n setDraft(null)\n }\n\n function remove(id: string) {\n removeUserProfile(id)\n if (draft?.id === id) setDraft(null)\n }\n\n /** One short completion through the profile's endpoint (`ai.complete`). */\n async function runTest() {\n if (!draftRef || !draftValid) return\n const current: TestResult = { state: 'running' }\n latestTest.current = current\n setTest(current)\n try {\n if (!app.backend.ai) throw new Error(t('ai.sse.httpError', { status: 501 }))\n // i18n-ignore: model-facing test prompt sent to the completion endpoint\n await app.backend.ai.complete({ before: 'Hallo', model: draftRef }, new AbortController().signal)\n if (latestTest.current === current) setTest({ state: 'ok' })\n }\n catch (error) {\n if (latestTest.current === current) setTest({ state: 'error', message: isRateLimited(error) ? t('ai.rateLimited') : fetchErrorMessage(error) })\n }\n }\n\n const ids = { name: 'ai-profile-label', endpoint: 'ai-profile-endpoint', model: 'ai-profile-model', apiKey: 'ai-profile-key' }\n\n return (\n <div className=\"flex flex-col divide-y divide-border\">\n <div className=\"flex flex-col gap-2 py-3\">\n <p className=\"text-sm font-medium text-foreground\">{t('ai.profiles.server')}</p>\n {!presets.length\n ? <p className=\"text-sm text-muted-foreground\">{t('ai.profiles.noPresets')}</p>\n : (\n <ul className=\"flex flex-col divide-y divide-border rounded-md border border-border\">\n {presets.map(preset => (\n <li key={preset.id} className=\"flex items-center gap-3 px-3 py-2\">\n <Icon name=\"i-lucide-server\" className=\"text-muted-foreground\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\">{preset.label}</p>\n <p className=\"truncate text-xs text-muted-foreground\">{preset.model} · {preset.endpoint}</p>\n </div>\n <Badge variant=\"secondary\">{t('ai.profiles.serverBadge')}</Badge>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n <div className=\"flex flex-col gap-2 py-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-foreground\">{t('ai.profiles.own')}</p>\n {!draft && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => edit()}>\n <Icon name=\"i-lucide-plus\" />\n {t('ai.profiles.add')}\n </Button>\n )}\n </div>\n {userProfiles.length > 0 && (\n <ul className=\"flex flex-col divide-y divide-border rounded-md border border-border\">\n {userProfiles.map(profile => (\n <li key={profile.id} className=\"flex items-center gap-3 px-3 py-2\">\n <Icon name=\"i-lucide-key-round\" className=\"text-muted-foreground\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\">{profile.label}</p>\n <p className=\"truncate text-xs text-muted-foreground\">{profile.model} · {profile.endpoint}</p>\n </div>\n <Button size=\"icon-sm\" variant=\"ghost\" aria-label={t('ai.profiles.edit')} onClick={() => edit(profile)}>\n <Icon name=\"i-lucide-pencil\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"ghost\" className=\"text-destructive\" aria-label={t('ai.profiles.delete')} onClick={() => remove(profile.id)}>\n <Icon name=\"i-lucide-trash-2\" />\n </Button>\n </li>\n ))}\n </ul>\n )}\n\n {draft && (\n <form\n className=\"flex flex-col gap-3 rounded-md border border-border p-3\"\n onSubmit={(event) => {\n event.preventDefault()\n save()\n }}\n >\n <Field label={t('ai.profiles.name')} required htmlFor={ids.name}>\n <Input id={ids.name} value={draft.label} placeholder={t('ai.profiles.namePlaceholder')} onChange={event => setDraft({ ...draft, label: event.target.value })} />\n </Field>\n <Field label={t('ai.profiles.endpoint')} description={t('ai.profiles.endpointHelp')} required htmlFor={ids.endpoint}>\n <Input id={ids.endpoint} value={draft.endpoint} placeholder={t('ai.profiles.endpointPlaceholder')} onChange={event => setDraft({ ...draft, endpoint: event.target.value })} />\n </Field>\n <Field label={t('ai.profiles.model')} required htmlFor={ids.model}>\n <Input id={ids.model} value={draft.model} placeholder={t('ai.profiles.modelPlaceholder')} onChange={event => setDraft({ ...draft, model: event.target.value })} />\n </Field>\n <Field label={t('ai.profiles.apiKey')} required htmlFor={ids.apiKey}>\n <Input id={ids.apiKey} type=\"password\" autoComplete=\"off\" value={draft.apiKey} onChange={event => setDraft({ ...draft, apiKey: event.target.value })} />\n </Field>\n {test?.state === 'ok' && (\n <Alert>\n <Icon name=\"i-lucide-check\" />\n <AlertTitle>{t('ai.profiles.testOk')}</AlertTitle>\n </Alert>\n )}\n {test?.state === 'error' && (\n <Alert variant=\"destructive\">\n <Icon name=\"i-lucide-circle-alert\" />\n <AlertTitle>{test.message}</AlertTitle>\n </Alert>\n )}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" disabled={!draftValid || test?.state === 'running'} onClick={() => void runTest()}>\n <Icon name={test?.state === 'running' ? 'i-lucide-loader-circle' : 'i-lucide-plug-zap'} className={test?.state === 'running' ? 'animate-spin' : undefined} />\n {t('ai.profiles.test')}\n </Button>\n <Button type=\"button\" variant=\"ghost\" onClick={() => setDraft(null)}>{t('ai.profiles.cancel')}</Button>\n <Button type=\"submit\" disabled={!draftValid}>{t('ai.profiles.save')}</Button>\n </div>\n </form>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SettingsModelProfiles.js","names":[],"sources":["../../../../src/features/ai/react/SettingsModelProfiles.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react'\nimport { fetchErrorMessage } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useEditorContext } from '../../../react/context'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Alert, AlertTitle } from '../../../react/ui/alert'\nimport { Badge } from '../../../react/ui/badge'\nimport { Button } from '../../../react/ui/button'\nimport { Input } from '../../../react/ui/input'\nimport { Label } from '../../../react/ui/label'\nimport { modelRefSchema, type UserModelProfile } from '../protocol'\nimport { isRateLimited } from '../rate-limit'\nimport { useModelProfiles } from './use-model-profiles'\n\n/** Result of „Testen“; replaced (not mutated) per run, so a stale run recognizes it was superseded. */\ntype TestResult = { state: 'running' } | { state: 'ok' } | { state: 'error', message: string }\n\n/** A labelled form row (the counterpart of Nuxt UI's `UFormField`). */\nfunction Field({ label, description, required, htmlFor, children }: { label: string, description?: string, required?: boolean, htmlFor: string, children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor={htmlFor}>\n {label}\n {required && <span className=\"text-destructive\">*</span>}\n </Label>\n {description && <p className=\"text-xs text-muted-foreground\">{description}</p>}\n {children}\n </div>\n )\n}\n\n/**\n * Section of the „KI“ tab: the server's Model presets and this browser's own profiles. Profiles are assigned to Model roles in the\n * section above.\n */\nexport function SettingsModelProfiles() {\n const app = useEditorContext()\n const { t } = useEditorI18n()\n const { presets, userProfiles, saveUserProfile, removeUserProfile } = useModelProfiles()\n\n /** The profile being added or edited; null while the form is closed. */\n const [draft, setDraft] = useState<UserModelProfile | null>(null)\n const [test, setTest] = useState<TestResult | null>(null)\n // The result object of the latest run, so a stale run recognizes it was superseded.\n const latestTest = useRef<TestResult | null>(null)\n\n const draftRef = useMemo(() => draft && { endpoint: draft.endpoint.trim(), model: draft.model.trim(), apiKey: draft.apiKey.trim() }, [draft])\n const draftValid = !!draft?.label.trim() && modelRefSchema.safeParse(draftRef).success\n // A test result belongs to the values it ran with.\n const endpoint = draftRef?.endpoint\n const model = draftRef?.model\n const apiKey = draftRef?.apiKey\n useEffect(() => {\n latestTest.current = null\n setTest(null)\n }, [endpoint, model, apiKey])\n\n function edit(profile?: UserModelProfile) {\n setDraft(profile ? { ...profile } : { id: crypto.randomUUID(), label: '', endpoint: '', model: '', apiKey: '' })\n latestTest.current = null\n setTest(null)\n }\n\n function save() {\n if (!draft || !draftRef || !draftValid) return\n saveUserProfile({ id: draft.id, label: draft.label.trim(), ...draftRef })\n setDraft(null)\n }\n\n function remove(id: string) {\n removeUserProfile(id)\n if (draft?.id === id) setDraft(null)\n }\n\n /** One short completion through the profile's endpoint (`ai.complete`). */\n async function runTest() {\n if (!draftRef || !draftValid) return\n const current: TestResult = { state: 'running' }\n latestTest.current = current\n setTest(current)\n try {\n if (!app.backend.ai) throw new Error(t('ai.sse.httpError', { status: 501 }))\n // i18n-ignore: model-facing test prompt sent to the completion endpoint\n await app.backend.ai.complete({ before: 'Hallo', model: draftRef }, new AbortController().signal)\n if (latestTest.current === current) setTest({ state: 'ok' })\n }\n catch (error) {\n if (latestTest.current === current) setTest({ state: 'error', message: isRateLimited(error) ? t('ai.rateLimited') : fetchErrorMessage(error) })\n }\n }\n\n const ids = { name: 'ai-profile-label', endpoint: 'ai-profile-endpoint', model: 'ai-profile-model', apiKey: 'ai-profile-key' }\n\n return (\n <div className=\"flex flex-col divide-y divide-border\">\n <div className=\"flex flex-col gap-2 py-3\">\n <p className=\"text-sm font-medium text-foreground\">{t('ai.profiles.server')}</p>\n {!presets.length\n ? <p className=\"text-sm text-muted-foreground\">{t('ai.profiles.noPresets')}</p>\n : (\n <ul className=\"flex flex-col divide-y divide-border rounded-md border border-border\">\n {presets.map(preset => (\n <li key={preset.id} className=\"flex items-center gap-3 px-3 py-2\">\n <Icon name=\"i-lucide-server\" className=\"text-muted-foreground\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\">{preset.label}</p>\n <p className=\"truncate text-xs text-muted-foreground\">{preset.model} · {preset.endpoint}</p>\n </div>\n <Badge variant=\"secondary\">{t('ai.profiles.serverBadge')}</Badge>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n <div className=\"flex flex-col gap-2 py-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium text-foreground\">{t('ai.profiles.own')}</p>\n {!draft && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => edit()}>\n <Icon name=\"i-lucide-plus\" />\n {t('ai.profiles.add')}\n </Button>\n )}\n </div>\n {userProfiles.length > 0 && (\n <ul className=\"flex flex-col divide-y divide-border rounded-md border border-border\">\n {userProfiles.map(profile => (\n <li key={profile.id} className=\"flex items-center gap-3 px-3 py-2\">\n <Icon name=\"i-lucide-key-round\" className=\"text-muted-foreground\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium\">{profile.label}</p>\n <p className=\"truncate text-xs text-muted-foreground\">{profile.model} · {profile.endpoint}</p>\n </div>\n <Button size=\"icon-sm\" variant=\"ghost\" aria-label={t('ai.profiles.edit')} onClick={() => edit(profile)}>\n <Icon name=\"i-lucide-pencil\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"ghost\" className=\"text-destructive\" aria-label={t('ai.profiles.delete')} onClick={() => remove(profile.id)}>\n <Icon name=\"i-lucide-trash-2\" />\n </Button>\n </li>\n ))}\n </ul>\n )}\n\n {draft && (\n <form\n className=\"flex flex-col gap-3 rounded-md border border-border p-3\"\n onSubmit={(event) => {\n event.preventDefault()\n save()\n }}\n >\n <Field label={t('ai.profiles.name')} required htmlFor={ids.name}>\n <Input id={ids.name} value={draft.label} placeholder={t('ai.profiles.namePlaceholder')} onChange={event => setDraft({ ...draft, label: event.target.value })} />\n </Field>\n <Field label={t('ai.profiles.endpoint')} description={t('ai.profiles.endpointHelp')} required htmlFor={ids.endpoint}>\n <Input id={ids.endpoint} value={draft.endpoint} placeholder={t('ai.profiles.endpointPlaceholder')} onChange={event => setDraft({ ...draft, endpoint: event.target.value })} />\n </Field>\n <Field label={t('ai.profiles.model')} required htmlFor={ids.model}>\n <Input id={ids.model} value={draft.model} placeholder={t('ai.profiles.modelPlaceholder')} onChange={event => setDraft({ ...draft, model: event.target.value })} />\n </Field>\n <Field label={t('ai.profiles.apiKey')} required htmlFor={ids.apiKey}>\n <Input id={ids.apiKey} type=\"password\" autoComplete=\"off\" value={draft.apiKey} onChange={event => setDraft({ ...draft, apiKey: event.target.value })} />\n </Field>\n {test?.state === 'ok' && (\n <Alert>\n <Icon name=\"i-lucide-check\" />\n <AlertTitle>{t('ai.profiles.testOk')}</AlertTitle>\n </Alert>\n )}\n {test?.state === 'error' && (\n <Alert variant=\"destructive\">\n <Icon name=\"i-lucide-circle-alert\" />\n <AlertTitle>{test.message}</AlertTitle>\n </Alert>\n )}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" disabled={!draftValid || test?.state === 'running'} onClick={() => void runTest()}>\n <Icon name={test?.state === 'running' ? 'i-lucide-loader-circle' : 'i-lucide-plug-zap'} className={test?.state === 'running' ? 'animate-spin' : undefined} />\n {t('ai.profiles.test')}\n </Button>\n <Button type=\"button\" variant=\"ghost\" onClick={() => setDraft(null)}>{t('ai.profiles.cancel')}</Button>\n <Button type=\"submit\" disabled={!draftValid}>{t('ai.profiles.save')}</Button>\n </div>\n </form>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import "../index.js";
3
3
  import { Editor } from "@tiptap/core";
4
4
  //#region src/features/ai/react/use-ai-assistant.d.ts
5
5
  /**
6
- * Streams the backend's `generate` into component state, on the task's model slot (see `createAiAssistant`). A running
6
+ * Streams the backend's `generate` into component state, on the task's Model role (see `createAiAssistant`). A running
7
7
  * request is aborted when the component unmounts. The 429 notice goes to `editor`'s surface (else the app's notifier).
8
8
  */
9
9
  export declare function useAiAssistant(editor?: Editor): {
@@ -6,7 +6,7 @@ import { createAiAssistant } from "../assistant.js";
6
6
  import { useEffect, useMemo } from "react";
7
7
  //#region src/features/ai/react/use-ai-assistant.ts
8
8
  /**
9
- * Streams the backend's `generate` into component state, on the task's model slot (see `createAiAssistant`). A running
9
+ * Streams the backend's `generate` into component state, on the task's Model role (see `createAiAssistant`). A running
10
10
  * request is aborted when the component unmounts. The 429 notice goes to `editor`'s surface (else the app's notifier).
11
11
  */
12
12
  function useAiAssistant(editor) {
@@ -1 +1 @@
1
- {"version":3,"file":"use-ai-assistant.js","names":[],"sources":["../../../../src/features/ai/react/use-ai-assistant.ts"],"sourcesContent":["import { useEffect, useMemo } from 'react'\nimport { useEditorContext } from '../../../react/context'\nimport { useStore } from '../../../react/use-store'\nimport { type AiRunBody, type AiStatus, createAiAssistant } from '../assistant'\nimport { modelProfiles } from '../model-profiles'\nimport { useNotifier } from '../../../react/use-notifier'\nimport type { Editor } from '@tiptap/core'\n\nexport type { AiRunBody, AiStatus }\n\n/**\n * Streams the backend's `generate` into component state, on the task's model slot (see `createAiAssistant`). A running\n * request is aborted when the component unmounts. The 429 notice goes to `editor`'s surface (else the app's notifier).\n */\nexport function useAiAssistant(editor?: Editor) {\n const app = useEditorContext()\n const notify = useNotifier(editor)\n const assistant = useMemo(\n () => createAiAssistant({ ai: app.backend.ai, modelRef: modelProfiles(app).modelRef, notify, t: app.t }),\n [app, notify],\n )\n useEffect(() => () => assistant.abort(), [assistant])\n const state = useStore(assistant.state)\n /** `getState` reads the current state after an await (a render's `state` would be stale). */\n return { model: assistant, ...state, getState: assistant.state.get, run: assistant.run, abort: assistant.abort, reset: assistant.reset, clearError: assistant.clearError }\n}\n"],"mappings":";;;;;;;;;;;AAcA,SAAgB,eAAe,QAAiB;CAC9C,MAAM,MAAM,iBAAiB;CAC7B,MAAM,SAAS,YAAY,MAAM;CACjC,MAAM,YAAY,cACV,kBAAkB;EAAE,IAAI,IAAI,QAAQ;EAAI,UAAU,cAAc,GAAG,CAAC,CAAC;EAAU;EAAQ,GAAG,IAAI;CAAE,CAAC,GACvG,CAAC,KAAK,MAAM,CACd;CACA,sBAAsB,UAAU,MAAM,GAAG,CAAC,SAAS,CAAC;;CAGpD,OAAO;EAAE,OAAO;EAAW,GAFb,SAAS,UAAU,KAEC;EAAG,UAAU,UAAU,MAAM;EAAK,KAAK,UAAU;EAAK,OAAO,UAAU;EAAO,OAAO,UAAU;EAAO,YAAY,UAAU;CAAW;AAC3K"}
1
+ {"version":3,"file":"use-ai-assistant.js","names":[],"sources":["../../../../src/features/ai/react/use-ai-assistant.ts"],"sourcesContent":["import { useEffect, useMemo } from 'react'\nimport { useEditorContext } from '../../../react/context'\nimport { useStore } from '../../../react/use-store'\nimport { type AiRunBody, type AiStatus, createAiAssistant } from '../assistant'\nimport { modelProfiles } from '../model-profiles'\nimport { useNotifier } from '../../../react/use-notifier'\nimport type { Editor } from '@tiptap/core'\n\nexport type { AiRunBody, AiStatus }\n\n/**\n * Streams the backend's `generate` into component state, on the task's Model role (see `createAiAssistant`). A running\n * request is aborted when the component unmounts. The 429 notice goes to `editor`'s surface (else the app's notifier).\n */\nexport function useAiAssistant(editor?: Editor) {\n const app = useEditorContext()\n const notify = useNotifier(editor)\n const assistant = useMemo(\n () => createAiAssistant({ ai: app.backend.ai, modelRef: modelProfiles(app).modelRef, notify, t: app.t }),\n [app, notify],\n )\n useEffect(() => () => assistant.abort(), [assistant])\n const state = useStore(assistant.state)\n /** `getState` reads the current state after an await (a render's `state` would be stale). */\n return { model: assistant, ...state, getState: assistant.state.get, run: assistant.run, abort: assistant.abort, reset: assistant.reset, clearError: assistant.clearError }\n}\n"],"mappings":";;;;;;;;;;;AAcA,SAAgB,eAAe,QAAiB;CAC9C,MAAM,MAAM,iBAAiB;CAC7B,MAAM,SAAS,YAAY,MAAM;CACjC,MAAM,YAAY,cACV,kBAAkB;EAAE,IAAI,IAAI,QAAQ;EAAI,UAAU,cAAc,GAAG,CAAC,CAAC;EAAU;EAAQ,GAAG,IAAI;CAAE,CAAC,GACvG,CAAC,KAAK,MAAM,CACd;CACA,sBAAsB,UAAU,MAAM,GAAG,CAAC,SAAS,CAAC;;CAGpD,OAAO;EAAE,OAAO;EAAW,GAFb,SAAS,UAAU,KAEC;EAAG,UAAU,UAAU,MAAM;EAAK,KAAK,UAAU;EAAK,OAAO,UAAU;EAAO,OAAO,UAAU;EAAO,YAAY,UAAU;CAAW;AAC3K"}
@@ -1,19 +1,19 @@
1
- import { ModelSlot } from "../../../settings/values.js";
1
+ import { ModelRole } from "../../../settings/values.js";
2
2
  import "../../../settings/index.js";
3
3
  import { ModelProfile, ModelRef } from "../../../core/backend-types.js";
4
4
  import { UserModelProfile } from "../protocol/models.js";
5
5
  import "../protocol/index.js";
6
6
  //#region src/features/ai/react/use-model-profiles.d.ts
7
7
  /**
8
- * Model profiles the AI features run on, shared by all editors of the app (see `modelProfiles`): server presets plus
9
- * the user profiles of this browser. Re-renders when either or the settings' slot assignment changes.
8
+ * Model profiles the AI features run on, shared by all editors of the app (see `modelProfiles`): Model presets plus
9
+ * the user profiles of this browser. Re-renders when either or the settings' Model role assignment changes.
10
10
  */
11
11
  export declare function useModelProfiles(): {
12
12
  presets: ModelProfile[];
13
13
  userProfiles: UserModelProfile[];
14
14
  profiles: ModelProfile[];
15
- slotProfile: (slot: ModelSlot) => ModelProfile | undefined;
16
- modelRef: (slot: ModelSlot) => ModelRef;
15
+ slotProfile: (slot: ModelRole) => ModelProfile | undefined;
16
+ modelRef: (slot: ModelRole) => ModelRef;
17
17
  saveUserProfile: (profile: UserModelProfile) => void;
18
18
  removeUserProfile: (id: string) => void;
19
19
  };
@@ -4,8 +4,8 @@ import { modelProfiles } from "../model-profiles.js";
4
4
  import { useMemo } from "react";
5
5
  //#region src/features/ai/react/use-model-profiles.ts
6
6
  /**
7
- * Model profiles the AI features run on, shared by all editors of the app (see `modelProfiles`): server presets plus
8
- * the user profiles of this browser. Re-renders when either or the settings' slot assignment changes.
7
+ * Model profiles the AI features run on, shared by all editors of the app (see `modelProfiles`): Model presets plus
8
+ * the user profiles of this browser. Re-renders when either or the settings' Model role assignment changes.
9
9
  */
10
10
  function useModelProfiles() {
11
11
  const app = useEditorContext();