ompchamber 1.2.1 → 2.0.1

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 (237) hide show
  1. package/README.md +143 -147
  2. package/dist/client/index.html +4 -2
  3. package/dist/client/static/css/index.5f630421b7.css +2 -0
  4. package/dist/client/static/js/77.0d81c74cf0.js +139 -0
  5. package/dist/client/static/js/async/1k.973e8e21fc.js +2 -0
  6. package/dist/client/static/js/async/{3y.3186ecdd63.js → 3y.3530b824ad.js} +6 -6
  7. package/dist/client/static/js/async/{q.b453193538.js → 4m.928a1dde85.js} +10 -10
  8. package/dist/client/static/js/async/66.5dfa29f957.js +93 -0
  9. package/dist/client/static/js/async/7g.a7814350c3.js +1 -0
  10. package/dist/client/static/js/async/ar.9303e5f2e8.js +2 -0
  11. package/dist/client/static/js/async/{cx.9c8268d542.js → cx.3cb0242a3a.js} +1 -1
  12. package/dist/client/static/js/async/e.93214d622b.js +79 -0
  13. package/dist/client/static/js/async/g.cb81980498.js +2 -0
  14. package/dist/client/static/js/async/h1.393f066d09.js +1 -0
  15. package/dist/client/static/js/async/jl.874d352278.js +1 -0
  16. package/dist/client/static/js/async/{k.d16cd09ae7.js → k.3210286401.js} +1 -1
  17. package/dist/client/static/js/async/{m.2e1ea3db9e.js → m.eb2e6752e2.js} +3 -3
  18. package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +11 -0
  19. package/dist/client/static/js/async/mt.0352e976d8.js +1 -0
  20. package/dist/client/static/js/async/{oc.eb22093f05.js → oc.b73042e8bd.js} +1 -1
  21. package/dist/client/static/js/async/{og.53f068804c.js → og.8eb577893a.js} +1 -1
  22. package/dist/client/static/js/async/px.fc6877924b.js +1 -0
  23. package/dist/client/static/js/async/q.22588845a9.js +21 -0
  24. package/dist/client/static/js/async/{q5.88b50ffa95.js → q5.97dc1481b6.js} +1 -1
  25. package/dist/client/static/js/async/s.0f395cdda5.js +94 -0
  26. package/dist/client/static/js/async/{tr.2a0d2b7600.js → tr.e878658140.js} +1 -1
  27. package/dist/client/static/js/async/vt.42416b707d.js +1 -0
  28. package/dist/client/static/js/async/x1.da8687159b.js +90 -0
  29. package/dist/client/static/js/async/xe.da8dcc2015.js +2 -0
  30. package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +11 -0
  31. package/dist/client/static/js/async/{xn.6360379f71.js → xn.6510c0abe2.js} +2 -2
  32. package/dist/client/static/js/async/{yx.af2936c368.js → xn1.83a7784fe1.js} +3 -3
  33. package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +11 -0
  34. package/dist/client/static/js/async/yg.35bebd6d0c.js +149 -0
  35. package/dist/client/static/js/async/yv.d99d829136.js +1 -0
  36. package/dist/client/static/js/index.1fd9e1c543.js +38 -0
  37. package/package.json +3 -3
  38. package/src/client/components/common/ComposerTextarea.tsx +9 -1
  39. package/src/client/components/common/MarkdownRenderer.tsx +50 -12
  40. package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +9 -3
  41. package/src/client/components/settings/Modal.tsx +11 -2
  42. package/src/client/components/settings/categories/AppearanceSettings.tsx +7 -30
  43. package/src/client/components/settings/categories/ProviderSettings.tsx +2 -0
  44. package/src/client/components/settings/categories/appearance-settings/ThemeCard.tsx +61 -0
  45. package/src/client/components/settings/categories/appearance-settings/ThemeGrid.tsx +80 -0
  46. package/src/client/components/settings/categories/chat-settings/TitleSection.tsx +1 -1
  47. package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +43 -6
  48. package/src/client/components/settings/categories/provider-settings/ModelConfigModal.tsx +54 -54
  49. package/src/client/components/workspace/SearchPanel.tsx +16 -3
  50. package/src/client/components/workspace/btw-panel/AskCard.tsx +11 -6
  51. package/src/client/components/workspace/btw-panel/Messages.tsx +105 -51
  52. package/src/client/components/workspace/btw-panel/index.tsx +124 -45
  53. package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +30 -14
  54. package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +10 -1
  55. package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +8 -2
  56. package/src/client/components/workspace/chat-timeline/MessageItem.tsx +27 -16
  57. package/src/client/components/workspace/chat-timeline/MessageList.tsx +11 -0
  58. package/src/client/components/workspace/chat-timeline/RunFooter.tsx +8 -0
  59. package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +12 -1
  60. package/src/client/components/workspace/chat-timeline/index.tsx +7 -2
  61. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +42 -15
  62. package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +13 -1
  63. package/src/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index.tsx +13 -4
  64. package/src/client/components/workspace/diff-panel/SplitView.tsx +4 -1
  65. package/src/client/components/workspace/diff-panel/UnifiedView.tsx +70 -65
  66. package/src/client/components/workspace/diff-panel/index.tsx +8 -1
  67. package/src/client/components/workspace/editor/index.tsx +18 -2
  68. package/src/client/components/workspace/file-explorer/GitRepoDropdown.tsx +15 -41
  69. package/src/client/components/workspace/file-explorer/index.tsx +86 -16
  70. package/src/client/components/workspace/git-panel/index.tsx +60 -78
  71. package/src/client/components/workspace/terminal-panel/Header.tsx +16 -1
  72. package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +5 -5
  73. package/src/client/components/workspace/terminal-panel/index.tsx +6 -2
  74. package/src/client/components/workspace/terminal-panel/mount.ts +2 -2
  75. package/src/client/data/theme/terminal.ts +50 -52
  76. package/src/client/hooks/chat/btw/index.ts +139 -18
  77. package/src/client/hooks/chat/btw/intercept.ts +8 -8
  78. package/src/client/hooks/chat/btw/mode.ts +111 -16
  79. package/src/client/hooks/chat/composer/index.ts +10 -4
  80. package/src/client/hooks/chat/timeline/access-mode.ts +41 -0
  81. package/src/client/hooks/chat/timeline/ask-frames.test.ts +87 -0
  82. package/src/client/hooks/chat/timeline/ask-frames.ts +20 -1
  83. package/src/client/hooks/chat/timeline/auto-scroll.ts +13 -4
  84. package/src/client/hooks/chat/timeline/index.ts +8 -20
  85. package/src/client/hooks/chat/timeline/queue.ts +66 -9
  86. package/src/client/hooks/chat/timeline/scroll.test.ts +260 -0
  87. package/src/client/hooks/chat/timeline/scroll.ts +93 -20
  88. package/src/client/hooks/chat/timeline/session-load.ts +7 -1
  89. package/src/client/hooks/chat/timeline/session-pagination.test.ts +167 -0
  90. package/src/client/hooks/chat/timeline/session-pagination.ts +37 -11
  91. package/src/client/hooks/editor/save-file.ts +62 -0
  92. package/src/client/hooks/editor/use-file-editor.test.ts +61 -5
  93. package/src/client/hooks/editor/use-file-editor.ts +38 -29
  94. package/src/client/hooks/editor/use-file-transfer.ts +5 -2
  95. package/src/client/hooks/settings/providers.ts +34 -3
  96. package/src/client/hooks/ui/open-file.ts +7 -1
  97. package/src/client/hooks/ui/theme.ts +82 -57
  98. package/src/client/hooks/workspace/file-tabs.ts +5 -1
  99. package/src/client/hooks/workspace/repo-scope/index.ts +119 -0
  100. package/src/client/hooks/workspace/repo-scope/store.test.ts +161 -0
  101. package/src/client/hooks/workspace/repo-scope/store.ts +201 -0
  102. package/src/client/hooks/workspace/search-stream.ts +24 -5
  103. package/src/client/main.tsx +6 -0
  104. package/src/client/tailwind.css +19 -56
  105. package/src/server/lib/btw/child.server.ts +41 -0
  106. package/src/server/lib/btw/lifecycle.server.ts +254 -0
  107. package/src/server/lib/btw/prompt.ts +13 -5
  108. package/src/server/lib/btw/registry.server.ts +13 -4
  109. package/src/server/lib/btw/runtime.server.ts +149 -156
  110. package/src/server/lib/btw/schema.server.ts +27 -0
  111. package/src/server/lib/btw/service.server.ts +106 -5
  112. package/src/server/lib/btw/store.server.ts +57 -6
  113. package/src/server/lib/db/schema.ts +4 -0
  114. package/src/server/lib/db/settings-store.test.ts +65 -0
  115. package/src/server/lib/db/settings-store.ts +25 -0
  116. package/src/server/lib/fs/atomic-write.ts +42 -4
  117. package/src/server/lib/fs/config-lock.ts +74 -0
  118. package/src/server/lib/fs/git-ignore.ts +55 -5
  119. package/src/server/lib/fs/ripgrep.ts +119 -0
  120. package/src/server/lib/lifecycle/identity.ts +10 -64
  121. package/src/server/lib/lifecycle/proc/darwin.ts +238 -0
  122. package/src/server/lib/lifecycle/proc/index.ts +38 -0
  123. package/src/server/lib/lifecycle/proc/linux.ts +80 -0
  124. package/src/server/lib/lifecycle/proc/ps.ts +86 -0
  125. package/src/server/lib/lifecycle/proc/types.ts +33 -0
  126. package/src/server/lib/lifecycle/proc/win32.ts +53 -0
  127. package/src/server/lib/models/provider-registry.server.ts +2 -2
  128. package/src/server/lib/models/registry.server.ts +1 -1
  129. package/src/server/lib/omp/config/behavior.ts +17 -17
  130. package/src/server/lib/omp/config/disabled-providers.ts +84 -35
  131. package/src/server/lib/omp/config/document.ts +128 -0
  132. package/src/server/lib/omp/config/extensions.ts +11 -10
  133. package/src/server/lib/omp/config/mcp.ts +4 -57
  134. package/src/server/lib/omp/config/model-overrides.ts +123 -0
  135. package/src/server/lib/omp/config/models-config.ts +92 -0
  136. package/src/server/lib/omp/config/models-validation.ts +159 -0
  137. package/src/server/lib/omp/config/path-scoped.ts +74 -0
  138. package/src/server/lib/omp/config/providers.ts +241 -211
  139. package/src/server/lib/omp/config/roles.ts +6 -14
  140. package/src/server/lib/omp/core/auth-credentials.ts +1 -1
  141. package/src/server/lib/omp/rpc/frame-fold.test.ts +124 -0
  142. package/src/server/lib/omp/rpc/frame-fold.ts +43 -24
  143. package/src/server/lib/omp/rpc/lifecycle.test.ts +41 -0
  144. package/src/server/lib/omp/rpc/manager.ts +21 -21
  145. package/src/server/lib/omp/rpc/pending-ui-dialogs.ts +11 -7
  146. package/src/server/lib/omp/rpc/session-commands.ts +17 -29
  147. package/src/server/lib/omp/rpc/session-registry.ts +5 -66
  148. package/src/server/lib/omp/rpc/web-state.ts +0 -6
  149. package/src/server/lib/omp/session/auto-title.server.test.ts +109 -11
  150. package/src/server/lib/omp/session/auto-title.server.ts +95 -20
  151. package/src/server/lib/omp/session/sidebar-data.server.ts +11 -6
  152. package/src/server/lib/queue/delivery-dispatch.server.test.ts +162 -0
  153. package/src/server/lib/queue/delivery-harness.ts +88 -0
  154. package/src/server/lib/queue/delivery.server.test.ts +78 -149
  155. package/src/server/lib/queue/delivery.server.ts +105 -13
  156. package/src/server/lib/queue/store.server.ts +30 -5
  157. package/src/server/lib/terminal/foreground.test.ts +164 -0
  158. package/src/server/lib/terminal/foreground.ts +34 -6
  159. package/src/server/lib/terminal/history.ts +8 -7
  160. package/src/server/plugins/compress.ts +33 -5
  161. package/src/server/plugins/ssr.ts +14 -7
  162. package/src/server/routes/btw/topics.ts +89 -2
  163. package/src/server/routes/fs/action.test.ts +64 -0
  164. package/src/server/routes/fs/action.ts +10 -1
  165. package/src/server/routes/fs/git.ts +5 -6
  166. package/src/server/routes/fs/search.ts +17 -26
  167. package/src/server/routes/sessions/queue.ts +17 -4
  168. package/src/server/routes/settings/index.ts +2 -0
  169. package/src/server/routes/settings/model-override.ts +69 -0
  170. package/src/server/routes/settings/provider-models.ts +35 -12
  171. package/src/server/routes/settings/providers.ts +31 -1
  172. package/src/server/routes/settings/root.ts +8 -3
  173. package/src/shared/lib/chat/btw/client.ts +34 -0
  174. package/src/shared/lib/chat/timeline/tool-phrases.ts +7 -0
  175. package/src/shared/lib/code/line-endings.test.ts +38 -0
  176. package/src/shared/lib/code/line-endings.ts +56 -0
  177. package/src/shared/lib/code/shiki-themes.ts +9 -5
  178. package/src/shared/lib/fs/file-tree.ts +17 -12
  179. package/src/shared/lib/markdown/autolink.test.ts +32 -0
  180. package/src/shared/lib/markdown/autolink.ts +50 -0
  181. package/src/shared/lib/markdown/document-urls.test.ts +68 -0
  182. package/src/shared/lib/markdown/document-urls.ts +118 -0
  183. package/src/shared/lib/markdown/marked.ts +27 -15
  184. package/src/shared/lib/markdown/mermaid.ts +2 -2
  185. package/src/shared/lib/models/catalog.ts +1 -1
  186. package/src/shared/lib/omp/session/schema-migrations.ts +30 -0
  187. package/src/shared/lib/omp/session/stream-state.server.test.ts +54 -0
  188. package/src/shared/lib/omp/session/stream-state.server.ts +70 -21
  189. package/src/shared/lib/settings/client.ts +10 -4
  190. package/src/shared/lib/settings/diff.test.ts +31 -0
  191. package/src/shared/lib/settings/diff.ts +37 -0
  192. package/src/shared/lib/theme/catalog.test.ts +168 -0
  193. package/src/shared/lib/theme/catalog.ts +98 -0
  194. package/src/shared/lib/theme/color.ts +88 -0
  195. package/src/shared/lib/theme/css.ts +70 -0
  196. package/src/shared/lib/theme/palettes/chamber.ts +61 -0
  197. package/src/shared/lib/theme/palettes/dark.ts +313 -0
  198. package/src/shared/lib/theme/palettes/light.ts +313 -0
  199. package/src/shared/types/btw.ts +83 -4
  200. package/src/shared/types/index.ts +1 -0
  201. package/src/shared/types/settings/provider.ts +8 -1
  202. package/src/shared/types/settings/state.ts +10 -4
  203. package/src/shared/types/theme.ts +48 -0
  204. package/dist/client/static/css/index.b938f4a693.css +0 -2
  205. package/dist/client/static/js/77.8022a800b5.js +0 -139
  206. package/dist/client/static/js/async/1k.a411fe5b43.js +0 -2
  207. package/dist/client/static/js/async/66.86544ad741.js +0 -93
  208. package/dist/client/static/js/async/7g.db82aca7ba.js +0 -1
  209. package/dist/client/static/js/async/ar.ad5c8822a8.js +0 -2
  210. package/dist/client/static/js/async/e.6443e739a2.js +0 -79
  211. package/dist/client/static/js/async/g.ee41bfed7f.js +0 -2
  212. package/dist/client/static/js/async/h1.d7091cef78.js +0 -1
  213. package/dist/client/static/js/async/jl.4911fa3383.js +0 -1
  214. package/dist/client/static/js/async/m.2e1ea3db9e.js.LICENSE.txt +0 -6
  215. package/dist/client/static/js/async/mt.853be2b5aa.js +0 -1
  216. package/dist/client/static/js/async/ot.84c71b308f.js +0 -2
  217. package/dist/client/static/js/async/px.783b6f91f2.js +0 -1
  218. package/dist/client/static/js/async/q8.7f1b48e9cd.js +0 -21
  219. package/dist/client/static/js/async/q8.7f1b48e9cd.js.LICENSE.txt +0 -6
  220. package/dist/client/static/js/async/s.aad2ecebc6.js +0 -94
  221. package/dist/client/static/js/async/vt.20406a5604.js +0 -1
  222. package/dist/client/static/js/async/x1.fe8af17016.js +0 -90
  223. package/dist/client/static/js/async/xn.6360379f71.js.LICENSE.txt +0 -6
  224. package/dist/client/static/js/async/yg.5a94e1fc8a.js +0 -149
  225. package/dist/client/static/js/async/yv.2fa22f009a.js +0 -1
  226. package/dist/client/static/js/index.5fe7a2cb64.js +0 -34
  227. package/src/client/components/workspace/btw-panel/Composer.tsx +0 -183
  228. package/src/client/components/workspace/btw-panel/attachments.ts +0 -80
  229. /package/dist/client/static/js/{77.8022a800b5.js.LICENSE.txt → 77.0d81c74cf0.js.LICENSE.txt} +0 -0
  230. /package/dist/client/static/js/async/{g.ee41bfed7f.js.LICENSE.txt → 1k.973e8e21fc.js.LICENSE.txt} +0 -0
  231. /package/dist/client/static/js/async/{3y.3186ecdd63.js.LICENSE.txt → 3y.3530b824ad.js.LICENSE.txt} +0 -0
  232. /package/dist/client/static/js/async/{ot.84c71b308f.js.LICENSE.txt → 4m.928a1dde85.js.LICENSE.txt} +0 -0
  233. /package/dist/client/static/js/async/{q.b453193538.js.LICENSE.txt → ar.9303e5f2e8.js.LICENSE.txt} +0 -0
  234. /package/dist/client/static/js/async/{yx.af2936c368.js.LICENSE.txt → g.cb81980498.js.LICENSE.txt} +0 -0
  235. /package/dist/client/static/js/async/{1k.a411fe5b43.js.LICENSE.txt → q.22588845a9.js.LICENSE.txt} +0 -0
  236. /package/dist/client/static/js/async/{ar.ad5c8822a8.js.LICENSE.txt → xn.6510c0abe2.js.LICENSE.txt} +0 -0
  237. /package/dist/client/static/js/{index.5fe7a2cb64.js.LICENSE.txt → index.1fd9e1c543.js.LICENSE.txt} +0 -0
@@ -1,183 +0,0 @@
1
- /**
2
- * BTW panel composer card — the side question's own input.
3
- *
4
- * Pure presentation: text, attachments and the running turn are owned by the
5
- * parent; this file only autosizes the textarea, reads keys and renders.
6
- */
7
-
8
- import { useEffect, useRef } from 'preact/hooks';
9
- import type { TargetedEvent, TargetedKeyboardEvent } from 'preact';
10
- import { File as FileIcon, Paperclip, Send, Shield, Square, X } from 'lucide-preact';
11
- import { formatBytes } from '@/shared/lib/format/number';
12
- import { providerLabel } from '@/shared/lib/models/provider-label';
13
-
14
- export interface BtwComposerProps {
15
- value: string;
16
- onChange: (value: string) => void;
17
- onSubmit: () => void;
18
- onStop: () => void;
19
- running: boolean;
20
- modelLabel?: string;
21
- provider?: string;
22
- providerNames?: Record<string, string>;
23
- attachments: { id: string; name: string; size: number }[];
24
- onFilesSelected: (files: File[]) => void;
25
- onRemoveAttachment: (id: string) => void;
26
- }
27
-
28
- const ICON_BUTTON = 'p-1.5 rounded text-ink/60 hover:text-ink hover:bg-ink/5 transition-colors cursor-pointer';
29
-
30
- export function BtwComposer({
31
- value,
32
- onChange,
33
- onSubmit,
34
- onStop,
35
- running,
36
- modelLabel,
37
- provider,
38
- providerNames,
39
- attachments,
40
- onFilesSelected,
41
- onRemoveAttachment,
42
- }: BtwComposerProps) {
43
- const fileInputRef = useRef<HTMLInputElement>(null);
44
- const textareaRef = useRef<HTMLTextAreaElement>(null);
45
-
46
- const canSend = value.trim().length > 0 || attachments.length > 0;
47
- const providerText = provider ? providerLabel(provider, providerNames) : '';
48
-
49
- // The form replaces the chat composer that had focus, so the caret is put
50
- // here — the field the form is for, and the one a restored draft is waiting
51
- // in — instead of being left on the document body.
52
- useEffect(() => {
53
- const textarea = textareaRef.current;
54
- if (!textarea) return;
55
- textarea.focus();
56
- textarea.setSelectionRange(textarea.value.length, textarea.value.length);
57
- }, []);
58
-
59
- // Grow with the content and collapse again when the parent clears `value`
60
- // after a submit — `height: auto` first, otherwise the box can only expand.
61
- useEffect(() => {
62
- const textarea = textareaRef.current;
63
- if (!textarea) return;
64
- textarea.style.height = 'auto';
65
- textarea.style.height = `${textarea.scrollHeight}px`;
66
- }, [value]);
67
-
68
- const handleKeyDown = (event: TargetedKeyboardEvent<HTMLTextAreaElement>) => {
69
- if (event.key === 'Escape') {
70
- event.currentTarget.blur();
71
- return;
72
- }
73
- if (event.key !== 'Enter' || event.shiftKey) return;
74
- // IME candidate selection must not submit the question.
75
- if (event.isComposing || event.keyCode === 229) return;
76
- event.preventDefault();
77
- if (canSend) onSubmit();
78
- };
79
-
80
- const handleFileSelect = (event: TargetedEvent<HTMLInputElement>) => {
81
- const files = event.currentTarget.files;
82
- if (files && files.length > 0) onFilesSelected(Array.from(files));
83
- if (fileInputRef.current) fileInputRef.current.value = '';
84
- };
85
-
86
- return (
87
- <div className="bg-paper border border-ink/20 rounded-2xl shadow-lg flex flex-col overflow-hidden">
88
- <textarea
89
- ref={textareaRef}
90
- rows={1}
91
- value={value}
92
- onInput={(event) => onChange(event.currentTarget.value)}
93
- onKeyDown={handleKeyDown}
94
- placeholder="Ask in this btw session..."
95
- className="w-full bg-transparent text-[13px] text-ink placeholder-ink/40 resize-none outline-none px-3.5 pt-3 pb-1 max-h-32"
96
- />
97
-
98
- {attachments.length > 0 && (
99
- <div className="flex items-center flex-wrap gap-1.5 px-3 pt-2">
100
- {attachments.map((attachment) => (
101
- <div
102
- key={attachment.id}
103
- className="relative flex items-center bg-canvas border border-ink/10 rounded-md p-0.5 pr-6 text-xs"
104
- >
105
- <div className="w-6 h-6 flex items-center justify-center bg-ink/5 rounded-sm mr-1.5 text-ink/60">
106
- <FileIcon size={12} strokeWidth={2} />
107
- </div>
108
- <span className="truncate max-w-[120px] font-mono text-[10px] text-ink/80">{attachment.name}</span>
109
- {formatBytes(attachment.size) && (
110
- <span className="ml-1.5 font-mono text-[10px] text-ink/40">{formatBytes(attachment.size)}</span>
111
- )}
112
- <button
113
- type="button"
114
- onClick={() => onRemoveAttachment(attachment.id)}
115
- className="absolute right-0.5 top-1/2 -translate-y-1/2 p-0.5 text-ink/40 hover:text-error hover:bg-error/10 rounded transition-colors cursor-pointer"
116
- aria-label={`Remove ${attachment.name}`}
117
- title="Remove attachment"
118
- >
119
- <X size={10} strokeWidth={2} />
120
- </button>
121
- </div>
122
- ))}
123
- </div>
124
- )}
125
-
126
- <div className="flex items-center justify-between px-2 pb-2">
127
- <div className="flex items-center gap-0.5">
128
- <button
129
- type="button"
130
- onClick={() => fileInputRef.current?.click()}
131
- className={ICON_BUTTON}
132
- aria-label="Attach files"
133
- title="Attach files"
134
- >
135
- <Paperclip size={14} strokeWidth={2} />
136
- </button>
137
-
138
- <span
139
- className="p-1.5 text-ink/40"
140
- aria-label="Side answers never use tools"
141
- title="Side answers never use tools"
142
- >
143
- <Shield size={14} strokeWidth={2} />
144
- </span>
145
- </div>
146
-
147
- <div className="flex items-center gap-2 min-w-0">
148
- {(providerText || modelLabel) && (
149
- <div className="flex items-baseline gap-1.5 min-w-0">
150
- {providerText && <span className="text-[10px] text-ink/40 shrink-0">{providerText}</span>}
151
- {modelLabel && <span className="text-[11px] text-ink/70 truncate">{modelLabel}</span>}
152
- </div>
153
- )}
154
-
155
- {running ? (
156
- <button
157
- type="button"
158
- onClick={onStop}
159
- className="bg-ink text-canvas rounded p-1.5 cursor-pointer"
160
- aria-label="Stop side answer"
161
- title="Stop side answer"
162
- >
163
- <Square size={14} strokeWidth={2} />
164
- </button>
165
- ) : (
166
- <button
167
- type="button"
168
- onClick={onSubmit}
169
- disabled={!canSend}
170
- className="bg-ink text-canvas hover:bg-ink/80 rounded p-1.5 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
171
- aria-label="Send side question"
172
- title="Send side question"
173
- >
174
- <Send size={14} strokeWidth={2} />
175
- </button>
176
- )}
177
- </div>
178
- </div>
179
-
180
- <input type="file" accept="image/*" multiple className="hidden" ref={fileInputRef} onChange={handleFileSelect} />
181
- </div>
182
- );
183
- }
@@ -1,80 +0,0 @@
1
- /**
2
- * @license
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- /**
7
- * Attachment handling for the side composer.
8
- *
9
- * A side question reaches the model with images only — no text files and no
10
- * tools (the prompt says so, and the child runs `--no-tools`), so an
11
- * attachment that cannot be an image payload is not offered in the first
12
- * place. The file picker is `accept="image/*"`; this reads what it returns.
13
- */
14
-
15
- import type { AgentImage } from '@/shared/types';
16
-
17
- /** An attachment held by the side composer until it is sent. */
18
- export interface BtwAttachment {
19
- id: string;
20
- name: string;
21
- size: number;
22
- dataBase64?: string;
23
- }
24
-
25
- function readImage(file: File): Promise<string | undefined> {
26
- const { promise, resolve } = Promise.withResolvers<string | undefined>();
27
- const reader = new FileReader();
28
- reader.onload = () => {
29
- const result = typeof reader.result === 'string' ? reader.result : '';
30
- const comma = result.indexOf(',');
31
- resolve(comma >= 0 ? result.slice(comma + 1) : undefined);
32
- };
33
- reader.onerror = () => resolve(undefined);
34
- reader.readAsDataURL(file);
35
- return promise;
36
- }
37
-
38
- /** Read picked files into attachments, keeping only images. */
39
- export async function readBtwAttachments(files: File[]): Promise<BtwAttachment[]> {
40
- const images = files.filter((file) => file.type.startsWith('image/'));
41
- return Promise.all(
42
- images.map(async (file) => {
43
- const dataBase64 = await readImage(file);
44
- return {
45
- id: `btw-${file.name}-${file.size}-${file.lastModified}`,
46
- name: file.name,
47
- size: file.size,
48
- ...(dataBase64 ? { dataBase64 } : {}),
49
- };
50
- }),
51
- );
52
- }
53
-
54
- /** The model payload for an ask — images only, base64 already read. */
55
- export function btwImagesFrom(attachments: BtwAttachment[]): AgentImage[] {
56
- return attachments
57
- .filter((attachment): attachment is BtwAttachment & { dataBase64: string } => typeof attachment.dataBase64 === 'string')
58
- .map((attachment) => ({
59
- type: 'image' as const,
60
- data: attachment.dataBase64,
61
- mimeType: mimeTypeOf(attachment.name),
62
- }));
63
- }
64
-
65
- const MIME_BY_EXTENSION: Record<string, string> = {
66
- png: 'image/png',
67
- jpg: 'image/jpeg',
68
- jpeg: 'image/jpeg',
69
- gif: 'image/gif',
70
- webp: 'image/webp',
71
- bmp: 'image/bmp',
72
- svg: 'image/svg+xml',
73
- avif: 'image/avif',
74
- };
75
-
76
- function mimeTypeOf(name: string): string {
77
- const dot = name.lastIndexOf('.');
78
- const extension = dot >= 0 ? name.slice(dot + 1).toLowerCase() : '';
79
- return MIME_BY_EXTENSION[extension] ?? 'image/png';
80
- }