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.
- package/README.md +143 -147
- package/dist/client/index.html +4 -2
- package/dist/client/static/css/index.5f630421b7.css +2 -0
- package/dist/client/static/js/77.0d81c74cf0.js +139 -0
- package/dist/client/static/js/async/1k.973e8e21fc.js +2 -0
- package/dist/client/static/js/async/{3y.3186ecdd63.js → 3y.3530b824ad.js} +6 -6
- package/dist/client/static/js/async/{q.b453193538.js → 4m.928a1dde85.js} +10 -10
- package/dist/client/static/js/async/66.5dfa29f957.js +93 -0
- package/dist/client/static/js/async/7g.a7814350c3.js +1 -0
- package/dist/client/static/js/async/ar.9303e5f2e8.js +2 -0
- package/dist/client/static/js/async/{cx.9c8268d542.js → cx.3cb0242a3a.js} +1 -1
- package/dist/client/static/js/async/e.93214d622b.js +79 -0
- package/dist/client/static/js/async/g.cb81980498.js +2 -0
- package/dist/client/static/js/async/h1.393f066d09.js +1 -0
- package/dist/client/static/js/async/jl.874d352278.js +1 -0
- package/dist/client/static/js/async/{k.d16cd09ae7.js → k.3210286401.js} +1 -1
- package/dist/client/static/js/async/{m.2e1ea3db9e.js → m.eb2e6752e2.js} +3 -3
- package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/mt.0352e976d8.js +1 -0
- package/dist/client/static/js/async/{oc.eb22093f05.js → oc.b73042e8bd.js} +1 -1
- package/dist/client/static/js/async/{og.53f068804c.js → og.8eb577893a.js} +1 -1
- package/dist/client/static/js/async/px.fc6877924b.js +1 -0
- package/dist/client/static/js/async/q.22588845a9.js +21 -0
- package/dist/client/static/js/async/{q5.88b50ffa95.js → q5.97dc1481b6.js} +1 -1
- package/dist/client/static/js/async/s.0f395cdda5.js +94 -0
- package/dist/client/static/js/async/{tr.2a0d2b7600.js → tr.e878658140.js} +1 -1
- package/dist/client/static/js/async/vt.42416b707d.js +1 -0
- package/dist/client/static/js/async/x1.da8687159b.js +90 -0
- package/dist/client/static/js/async/xe.da8dcc2015.js +2 -0
- package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{xn.6360379f71.js → xn.6510c0abe2.js} +2 -2
- package/dist/client/static/js/async/{yx.af2936c368.js → xn1.83a7784fe1.js} +3 -3
- package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/yg.35bebd6d0c.js +149 -0
- package/dist/client/static/js/async/yv.d99d829136.js +1 -0
- package/dist/client/static/js/index.1fd9e1c543.js +38 -0
- package/package.json +3 -3
- package/src/client/components/common/ComposerTextarea.tsx +9 -1
- package/src/client/components/common/MarkdownRenderer.tsx +50 -12
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +9 -3
- package/src/client/components/settings/Modal.tsx +11 -2
- package/src/client/components/settings/categories/AppearanceSettings.tsx +7 -30
- package/src/client/components/settings/categories/ProviderSettings.tsx +2 -0
- package/src/client/components/settings/categories/appearance-settings/ThemeCard.tsx +61 -0
- package/src/client/components/settings/categories/appearance-settings/ThemeGrid.tsx +80 -0
- package/src/client/components/settings/categories/chat-settings/TitleSection.tsx +1 -1
- package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +43 -6
- package/src/client/components/settings/categories/provider-settings/ModelConfigModal.tsx +54 -54
- package/src/client/components/workspace/SearchPanel.tsx +16 -3
- package/src/client/components/workspace/btw-panel/AskCard.tsx +11 -6
- package/src/client/components/workspace/btw-panel/Messages.tsx +105 -51
- package/src/client/components/workspace/btw-panel/index.tsx +124 -45
- package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +30 -14
- package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +10 -1
- package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +8 -2
- package/src/client/components/workspace/chat-timeline/MessageItem.tsx +27 -16
- package/src/client/components/workspace/chat-timeline/MessageList.tsx +11 -0
- package/src/client/components/workspace/chat-timeline/RunFooter.tsx +8 -0
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +12 -1
- package/src/client/components/workspace/chat-timeline/index.tsx +7 -2
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +42 -15
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +13 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index.tsx +13 -4
- package/src/client/components/workspace/diff-panel/SplitView.tsx +4 -1
- package/src/client/components/workspace/diff-panel/UnifiedView.tsx +70 -65
- package/src/client/components/workspace/diff-panel/index.tsx +8 -1
- package/src/client/components/workspace/editor/index.tsx +18 -2
- package/src/client/components/workspace/file-explorer/GitRepoDropdown.tsx +15 -41
- package/src/client/components/workspace/file-explorer/index.tsx +86 -16
- package/src/client/components/workspace/git-panel/index.tsx +60 -78
- package/src/client/components/workspace/terminal-panel/Header.tsx +16 -1
- package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +5 -5
- package/src/client/components/workspace/terminal-panel/index.tsx +6 -2
- package/src/client/components/workspace/terminal-panel/mount.ts +2 -2
- package/src/client/data/theme/terminal.ts +50 -52
- package/src/client/hooks/chat/btw/index.ts +139 -18
- package/src/client/hooks/chat/btw/intercept.ts +8 -8
- package/src/client/hooks/chat/btw/mode.ts +111 -16
- package/src/client/hooks/chat/composer/index.ts +10 -4
- package/src/client/hooks/chat/timeline/access-mode.ts +41 -0
- package/src/client/hooks/chat/timeline/ask-frames.test.ts +87 -0
- package/src/client/hooks/chat/timeline/ask-frames.ts +20 -1
- package/src/client/hooks/chat/timeline/auto-scroll.ts +13 -4
- package/src/client/hooks/chat/timeline/index.ts +8 -20
- package/src/client/hooks/chat/timeline/queue.ts +66 -9
- package/src/client/hooks/chat/timeline/scroll.test.ts +260 -0
- package/src/client/hooks/chat/timeline/scroll.ts +93 -20
- package/src/client/hooks/chat/timeline/session-load.ts +7 -1
- package/src/client/hooks/chat/timeline/session-pagination.test.ts +167 -0
- package/src/client/hooks/chat/timeline/session-pagination.ts +37 -11
- package/src/client/hooks/editor/save-file.ts +62 -0
- package/src/client/hooks/editor/use-file-editor.test.ts +61 -5
- package/src/client/hooks/editor/use-file-editor.ts +38 -29
- package/src/client/hooks/editor/use-file-transfer.ts +5 -2
- package/src/client/hooks/settings/providers.ts +34 -3
- package/src/client/hooks/ui/open-file.ts +7 -1
- package/src/client/hooks/ui/theme.ts +82 -57
- package/src/client/hooks/workspace/file-tabs.ts +5 -1
- package/src/client/hooks/workspace/repo-scope/index.ts +119 -0
- package/src/client/hooks/workspace/repo-scope/store.test.ts +161 -0
- package/src/client/hooks/workspace/repo-scope/store.ts +201 -0
- package/src/client/hooks/workspace/search-stream.ts +24 -5
- package/src/client/main.tsx +6 -0
- package/src/client/tailwind.css +19 -56
- package/src/server/lib/btw/child.server.ts +41 -0
- package/src/server/lib/btw/lifecycle.server.ts +254 -0
- package/src/server/lib/btw/prompt.ts +13 -5
- package/src/server/lib/btw/registry.server.ts +13 -4
- package/src/server/lib/btw/runtime.server.ts +149 -156
- package/src/server/lib/btw/schema.server.ts +27 -0
- package/src/server/lib/btw/service.server.ts +106 -5
- package/src/server/lib/btw/store.server.ts +57 -6
- package/src/server/lib/db/schema.ts +4 -0
- package/src/server/lib/db/settings-store.test.ts +65 -0
- package/src/server/lib/db/settings-store.ts +25 -0
- package/src/server/lib/fs/atomic-write.ts +42 -4
- package/src/server/lib/fs/config-lock.ts +74 -0
- package/src/server/lib/fs/git-ignore.ts +55 -5
- package/src/server/lib/fs/ripgrep.ts +119 -0
- package/src/server/lib/lifecycle/identity.ts +10 -64
- package/src/server/lib/lifecycle/proc/darwin.ts +238 -0
- package/src/server/lib/lifecycle/proc/index.ts +38 -0
- package/src/server/lib/lifecycle/proc/linux.ts +80 -0
- package/src/server/lib/lifecycle/proc/ps.ts +86 -0
- package/src/server/lib/lifecycle/proc/types.ts +33 -0
- package/src/server/lib/lifecycle/proc/win32.ts +53 -0
- package/src/server/lib/models/provider-registry.server.ts +2 -2
- package/src/server/lib/models/registry.server.ts +1 -1
- package/src/server/lib/omp/config/behavior.ts +17 -17
- package/src/server/lib/omp/config/disabled-providers.ts +84 -35
- package/src/server/lib/omp/config/document.ts +128 -0
- package/src/server/lib/omp/config/extensions.ts +11 -10
- package/src/server/lib/omp/config/mcp.ts +4 -57
- package/src/server/lib/omp/config/model-overrides.ts +123 -0
- package/src/server/lib/omp/config/models-config.ts +92 -0
- package/src/server/lib/omp/config/models-validation.ts +159 -0
- package/src/server/lib/omp/config/path-scoped.ts +74 -0
- package/src/server/lib/omp/config/providers.ts +241 -211
- package/src/server/lib/omp/config/roles.ts +6 -14
- package/src/server/lib/omp/core/auth-credentials.ts +1 -1
- package/src/server/lib/omp/rpc/frame-fold.test.ts +124 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +43 -24
- package/src/server/lib/omp/rpc/lifecycle.test.ts +41 -0
- package/src/server/lib/omp/rpc/manager.ts +21 -21
- package/src/server/lib/omp/rpc/pending-ui-dialogs.ts +11 -7
- package/src/server/lib/omp/rpc/session-commands.ts +17 -29
- package/src/server/lib/omp/rpc/session-registry.ts +5 -66
- package/src/server/lib/omp/rpc/web-state.ts +0 -6
- package/src/server/lib/omp/session/auto-title.server.test.ts +109 -11
- package/src/server/lib/omp/session/auto-title.server.ts +95 -20
- package/src/server/lib/omp/session/sidebar-data.server.ts +11 -6
- package/src/server/lib/queue/delivery-dispatch.server.test.ts +162 -0
- package/src/server/lib/queue/delivery-harness.ts +88 -0
- package/src/server/lib/queue/delivery.server.test.ts +78 -149
- package/src/server/lib/queue/delivery.server.ts +105 -13
- package/src/server/lib/queue/store.server.ts +30 -5
- package/src/server/lib/terminal/foreground.test.ts +164 -0
- package/src/server/lib/terminal/foreground.ts +34 -6
- package/src/server/lib/terminal/history.ts +8 -7
- package/src/server/plugins/compress.ts +33 -5
- package/src/server/plugins/ssr.ts +14 -7
- package/src/server/routes/btw/topics.ts +89 -2
- package/src/server/routes/fs/action.test.ts +64 -0
- package/src/server/routes/fs/action.ts +10 -1
- package/src/server/routes/fs/git.ts +5 -6
- package/src/server/routes/fs/search.ts +17 -26
- package/src/server/routes/sessions/queue.ts +17 -4
- package/src/server/routes/settings/index.ts +2 -0
- package/src/server/routes/settings/model-override.ts +69 -0
- package/src/server/routes/settings/provider-models.ts +35 -12
- package/src/server/routes/settings/providers.ts +31 -1
- package/src/server/routes/settings/root.ts +8 -3
- package/src/shared/lib/chat/btw/client.ts +34 -0
- package/src/shared/lib/chat/timeline/tool-phrases.ts +7 -0
- package/src/shared/lib/code/line-endings.test.ts +38 -0
- package/src/shared/lib/code/line-endings.ts +56 -0
- package/src/shared/lib/code/shiki-themes.ts +9 -5
- package/src/shared/lib/fs/file-tree.ts +17 -12
- package/src/shared/lib/markdown/autolink.test.ts +32 -0
- package/src/shared/lib/markdown/autolink.ts +50 -0
- package/src/shared/lib/markdown/document-urls.test.ts +68 -0
- package/src/shared/lib/markdown/document-urls.ts +118 -0
- package/src/shared/lib/markdown/marked.ts +27 -15
- package/src/shared/lib/markdown/mermaid.ts +2 -2
- package/src/shared/lib/models/catalog.ts +1 -1
- package/src/shared/lib/omp/session/schema-migrations.ts +30 -0
- package/src/shared/lib/omp/session/stream-state.server.test.ts +54 -0
- package/src/shared/lib/omp/session/stream-state.server.ts +70 -21
- package/src/shared/lib/settings/client.ts +10 -4
- package/src/shared/lib/settings/diff.test.ts +31 -0
- package/src/shared/lib/settings/diff.ts +37 -0
- package/src/shared/lib/theme/catalog.test.ts +168 -0
- package/src/shared/lib/theme/catalog.ts +98 -0
- package/src/shared/lib/theme/color.ts +88 -0
- package/src/shared/lib/theme/css.ts +70 -0
- package/src/shared/lib/theme/palettes/chamber.ts +61 -0
- package/src/shared/lib/theme/palettes/dark.ts +313 -0
- package/src/shared/lib/theme/palettes/light.ts +313 -0
- package/src/shared/types/btw.ts +83 -4
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/settings/provider.ts +8 -1
- package/src/shared/types/settings/state.ts +10 -4
- package/src/shared/types/theme.ts +48 -0
- package/dist/client/static/css/index.b938f4a693.css +0 -2
- package/dist/client/static/js/77.8022a800b5.js +0 -139
- package/dist/client/static/js/async/1k.a411fe5b43.js +0 -2
- package/dist/client/static/js/async/66.86544ad741.js +0 -93
- package/dist/client/static/js/async/7g.db82aca7ba.js +0 -1
- package/dist/client/static/js/async/ar.ad5c8822a8.js +0 -2
- package/dist/client/static/js/async/e.6443e739a2.js +0 -79
- package/dist/client/static/js/async/g.ee41bfed7f.js +0 -2
- package/dist/client/static/js/async/h1.d7091cef78.js +0 -1
- package/dist/client/static/js/async/jl.4911fa3383.js +0 -1
- package/dist/client/static/js/async/m.2e1ea3db9e.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/mt.853be2b5aa.js +0 -1
- package/dist/client/static/js/async/ot.84c71b308f.js +0 -2
- package/dist/client/static/js/async/px.783b6f91f2.js +0 -1
- package/dist/client/static/js/async/q8.7f1b48e9cd.js +0 -21
- package/dist/client/static/js/async/q8.7f1b48e9cd.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/s.aad2ecebc6.js +0 -94
- package/dist/client/static/js/async/vt.20406a5604.js +0 -1
- package/dist/client/static/js/async/x1.fe8af17016.js +0 -90
- package/dist/client/static/js/async/xn.6360379f71.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/yg.5a94e1fc8a.js +0 -149
- package/dist/client/static/js/async/yv.2fa22f009a.js +0 -1
- package/dist/client/static/js/index.5fe7a2cb64.js +0 -34
- package/src/client/components/workspace/btw-panel/Composer.tsx +0 -183
- package/src/client/components/workspace/btw-panel/attachments.ts +0 -80
- /package/dist/client/static/js/{77.8022a800b5.js.LICENSE.txt → 77.0d81c74cf0.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.ee41bfed7f.js.LICENSE.txt → 1k.973e8e21fc.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3y.3186ecdd63.js.LICENSE.txt → 3y.3530b824ad.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ot.84c71b308f.js.LICENSE.txt → 4m.928a1dde85.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.b453193538.js.LICENSE.txt → ar.9303e5f2e8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{yx.af2936c368.js.LICENSE.txt → g.cb81980498.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{1k.a411fe5b43.js.LICENSE.txt → q.22588845a9.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ar.ad5c8822a8.js.LICENSE.txt → xn.6510c0abe2.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.5fe7a2cb64.js.LICENSE.txt → index.1fd9e1c543.js.LICENSE.txt} +0 -0
|
@@ -11,28 +11,41 @@ interface ModelConfigModalProps {
|
|
|
11
11
|
onSaveModelConfig: (modelId: string, updates: Partial<ProviderModel>) => void;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** The reasoning levels omp's `thinking.efforts` accepts, in its own order. */
|
|
15
|
+
const EFFORT_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;
|
|
16
|
+
type EffortLevel = (typeof EFFORT_LEVELS)[number];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Per-model configuration for one entry of `models.yml`.
|
|
20
|
+
*
|
|
21
|
+
* Only the knobs omp resolves per MODEL live here: `maxTokens` and the
|
|
22
|
+
* reasoning `thinking` block. omp has no per-model temperature or top-P — those
|
|
23
|
+
* are global `config.yml` settings, editable in Settings → OMP Engine — so
|
|
24
|
+
* offering sliders for them here would be a control that changes nothing. The
|
|
25
|
+
* dialog points at that panel instead.
|
|
26
|
+
*/
|
|
14
27
|
export function ModelConfigModal({
|
|
15
28
|
isOpen,
|
|
16
29
|
model,
|
|
17
30
|
onClose,
|
|
18
31
|
onSaveModelConfig,
|
|
19
32
|
}: ModelConfigModalProps) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const [temperature, setTemperature] = useState<number>(model.temperature ?? 0.7);
|
|
23
|
-
const [maxTokens, setMaxTokens] = useState<number>(model.maxTokens ?? 384000);
|
|
24
|
-
const [topP, setTopP] = useState<number>(model.topP ?? 0.95);
|
|
25
|
-
const [reasoningEffort, setReasoningEffort] = useState<'low' | 'medium' | 'high'>(
|
|
26
|
-
model.reasoningEffort ?? 'medium'
|
|
33
|
+
const [maxTokens, setMaxTokens] = useState<string>(
|
|
34
|
+
model?.maxTokens ? String(model.maxTokens) : '',
|
|
27
35
|
);
|
|
36
|
+
const [reasoningEffort, setReasoningEffort] = useState<EffortLevel | ''>(
|
|
37
|
+
(model?.reasoningEffort as EffortLevel | undefined) ?? '',
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (!isOpen || !model) return null;
|
|
28
41
|
|
|
29
42
|
const handleSubmit = (e: FormEvent) => {
|
|
30
43
|
e.preventDefault();
|
|
44
|
+
const parsed = Number.parseInt(maxTokens, 10);
|
|
31
45
|
onSaveModelConfig(model.id, {
|
|
32
|
-
|
|
33
|
-
maxTokens,
|
|
34
|
-
|
|
35
|
-
reasoningEffort,
|
|
46
|
+
// Empty clears the override, so the model returns to the catalog value.
|
|
47
|
+
maxTokens: Number.isFinite(parsed) && parsed > 0 ? parsed : undefined,
|
|
48
|
+
reasoningEffort: reasoningEffort || undefined,
|
|
36
49
|
});
|
|
37
50
|
onClose();
|
|
38
51
|
};
|
|
@@ -73,43 +86,6 @@ export function ModelConfigModal({
|
|
|
73
86
|
</div>
|
|
74
87
|
}
|
|
75
88
|
>
|
|
76
|
-
{/* Temperature Slider */}
|
|
77
|
-
<div>
|
|
78
|
-
<div className="flex items-center justify-between mb-1 text-xs">
|
|
79
|
-
<span className="font-semibold text-ink">Sampling Temperature</span>
|
|
80
|
-
<span className="font-mono text-ink/70 font-semibold">{temperature.toFixed(2)}</span>
|
|
81
|
-
</div>
|
|
82
|
-
<input
|
|
83
|
-
type="range"
|
|
84
|
-
min="0.0"
|
|
85
|
-
max="2.0"
|
|
86
|
-
step="0.05"
|
|
87
|
-
value={temperature}
|
|
88
|
-
onChange={(e) => setTemperature(parseFloat(e.currentTarget.value))}
|
|
89
|
-
className="w-full accent-ink cursor-pointer"
|
|
90
|
-
/>
|
|
91
|
-
<p className="text-[10px] text-ink/40 mt-0.5">
|
|
92
|
-
Controls randomness: lower is more deterministic, higher is more creative.
|
|
93
|
-
</p>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
{/* Top P Slider */}
|
|
97
|
-
<div>
|
|
98
|
-
<div className="flex items-center justify-between mb-1 text-xs">
|
|
99
|
-
<span className="font-semibold text-ink">Top P (Nucleus Sampling)</span>
|
|
100
|
-
<span className="font-mono text-ink/70 font-semibold">{topP.toFixed(2)}</span>
|
|
101
|
-
</div>
|
|
102
|
-
<input
|
|
103
|
-
type="range"
|
|
104
|
-
min="0.1"
|
|
105
|
-
max="1.0"
|
|
106
|
-
step="0.05"
|
|
107
|
-
value={topP}
|
|
108
|
-
onChange={(e) => setTopP(parseFloat(e.currentTarget.value))}
|
|
109
|
-
className="w-full accent-ink cursor-pointer"
|
|
110
|
-
/>
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
89
|
{/* Max Output Tokens */}
|
|
114
90
|
<div>
|
|
115
91
|
<label className="block text-xs font-semibold text-ink mb-1">
|
|
@@ -117,23 +93,37 @@ export function ModelConfigModal({
|
|
|
117
93
|
</label>
|
|
118
94
|
<input
|
|
119
95
|
type="number"
|
|
120
|
-
min={
|
|
121
|
-
max={1000000}
|
|
96
|
+
min={1}
|
|
122
97
|
step={1024}
|
|
123
98
|
value={maxTokens}
|
|
124
|
-
|
|
99
|
+
placeholder="Catalog default"
|
|
100
|
+
onChange={(e) => setMaxTokens(e.currentTarget.value)}
|
|
125
101
|
className="w-full bg-paper border border-ink/20 rounded-md px-3 py-1.5 text-xs text-ink outline-none focus:border-ink/60 font-mono"
|
|
126
102
|
/>
|
|
103
|
+
<p className="text-[10px] text-ink/40 mt-0.5">
|
|
104
|
+
Written to models.yml as this model's output cap. Empty uses the catalog value.
|
|
105
|
+
</p>
|
|
127
106
|
</div>
|
|
128
107
|
|
|
129
|
-
{/* Reasoning Effort (
|
|
108
|
+
{/* Reasoning Effort (only for models that support reasoning) */}
|
|
130
109
|
{model.hasReasoning && (
|
|
131
110
|
<div>
|
|
132
111
|
<label className="block text-xs font-semibold text-ink mb-1.5">
|
|
133
|
-
Reasoning Effort
|
|
112
|
+
Default Reasoning Effort
|
|
134
113
|
</label>
|
|
135
114
|
<div className="grid grid-cols-3 gap-2">
|
|
136
|
-
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
onClick={() => setReasoningEffort('')}
|
|
118
|
+
className={`py-1.5 rounded-md text-xs font-medium border text-center transition-colors cursor-pointer ${
|
|
119
|
+
reasoningEffort === ''
|
|
120
|
+
? 'border-ink/50 bg-ink/10 text-ink'
|
|
121
|
+
: 'border-ink/15 text-ink/60 hover:border-ink/30'
|
|
122
|
+
}`}
|
|
123
|
+
>
|
|
124
|
+
Auto
|
|
125
|
+
</button>
|
|
126
|
+
{EFFORT_LEVELS.map((lvl) => (
|
|
137
127
|
<button
|
|
138
128
|
key={lvl}
|
|
139
129
|
type="button"
|
|
@@ -148,8 +138,18 @@ export function ModelConfigModal({
|
|
|
148
138
|
</button>
|
|
149
139
|
))}
|
|
150
140
|
</div>
|
|
141
|
+
<p className="text-[10px] text-ink/40 mt-0.5">
|
|
142
|
+
The level omp starts this model at. Auto leaves the choice to the chat composer.
|
|
143
|
+
</p>
|
|
151
144
|
</div>
|
|
152
145
|
)}
|
|
146
|
+
|
|
147
|
+
<div className="flex items-start gap-2 px-3 py-2 rounded-md bg-ink/5 border border-ink/10 text-[11px] text-ink/60">
|
|
148
|
+
<span>
|
|
149
|
+
Sampling temperature and Top P are global omp settings, not per-model —
|
|
150
|
+
change them in <span className="font-medium text-ink">Settings → OMP Engine</span>.
|
|
151
|
+
</span>
|
|
152
|
+
</div>
|
|
153
153
|
</Modal>
|
|
154
154
|
);
|
|
155
155
|
}
|
|
@@ -6,6 +6,7 @@ import { useFetcher } from '@/client/lib/router/fetcher';
|
|
|
6
6
|
import { GitRepoDropdown } from '@/client/components/workspace/file-explorer/GitRepoDropdown';
|
|
7
7
|
import { useScrollbarFade, scrollbarFadeClass } from '@/client/hooks/ui/scrollbar-fade';
|
|
8
8
|
import { useSessionState } from '@/client/hooks/workspace/session-state';
|
|
9
|
+
import { useRepoList, useRepoScope } from '@/client/hooks/workspace/repo-scope';
|
|
9
10
|
import { useSearchStream } from '@/client/hooks/workspace/search-stream';
|
|
10
11
|
import { useOnClickOutside } from '@/client/hooks/ui/on-click-outside';
|
|
11
12
|
import { getLanguageFromPath, highlightCode } from '@/shared/lib/code/syntax-highlight';
|
|
@@ -23,13 +24,18 @@ export function SearchPanel({ className = '', enabled = true, rootPath }: { clas
|
|
|
23
24
|
const [includeFiles, setIncludeFiles] = useSessionState<string>('search.includeFiles', '');
|
|
24
25
|
const [showMenu, setShowMenu] = useState(false);
|
|
25
26
|
const [showIncludeField, setShowIncludeField] = useSessionState<boolean>('search.showIncludeField', false);
|
|
26
|
-
const
|
|
27
|
+
const { activeRepo, setActiveRepo } = useRepoScope(rootPath, 'search.activeRepo');
|
|
28
|
+
const { repos, scanning: reposScanning, rescan: rescanRepos } = useRepoList(rootPath, enabled);
|
|
27
29
|
|
|
28
30
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
29
31
|
const { isScrolling, handleScroll } = useScrollbarFade();
|
|
30
32
|
|
|
31
33
|
const replaceFetcher = useFetcher<{ success: boolean, results: any[] }>();
|
|
32
|
-
|
|
34
|
+
|
|
35
|
+
// The tree being searched: the workspace root plus the selected repo. Every
|
|
36
|
+
// result is relative to it, so a switch must not keep the previous tree's
|
|
37
|
+
// hits on screen — `useSearchStream` tags them with this scope.
|
|
38
|
+
const { results, isSearching, start: startSearch } = useSearchStream(`${rootPath ?? ''}\u0000${activeRepo}`);
|
|
33
39
|
|
|
34
40
|
useOnClickOutside(menuRef, () => setShowMenu(false));
|
|
35
41
|
|
|
@@ -109,7 +115,14 @@ export function SearchPanel({ className = '', enabled = true, rootPath }: { clas
|
|
|
109
115
|
<div className="p-3 border-b border-ink/10 flex items-center justify-between">
|
|
110
116
|
<div className="flex items-center space-x-2 min-w-0">
|
|
111
117
|
<h2 className="text-xs font-semibold text-ink uppercase tracking-wider">Search</h2>
|
|
112
|
-
<GitRepoDropdown
|
|
118
|
+
<GitRepoDropdown
|
|
119
|
+
rootPath={rootPath}
|
|
120
|
+
activeRepo={activeRepo}
|
|
121
|
+
onSelectRepo={setActiveRepo}
|
|
122
|
+
repos={repos}
|
|
123
|
+
scanning={reposScanning}
|
|
124
|
+
onRefreshRepos={rescanRepos}
|
|
125
|
+
/>
|
|
113
126
|
</div>
|
|
114
127
|
<div className="relative" ref={menuRef}>
|
|
115
128
|
<button
|
|
@@ -4,12 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* The ask card — the
|
|
7
|
+
* The ask card — the HEADER of the side-question panel.
|
|
8
8
|
*
|
|
9
|
-
* It is the
|
|
10
|
-
* a question of its own (a new topic), which is why it carries
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* It is the panel's own entry, not the chat's: the question typed here is
|
|
10
|
+
* always a question of its own (a new topic), which is why it carries the
|
|
11
|
+
* panel's actions — `⟳` start over, `⌄` topic history, `⧉` promote, `✕` leave.
|
|
12
|
+
*
|
|
13
|
+
* It is a header rather than another card in the stack on purpose. The panel
|
|
14
|
+
* also holds a composer, and a second free-standing input beside it read as
|
|
15
|
+
* "the same thing again": users typed a follow-up into the new-question field.
|
|
16
|
+
* As a header it belongs to the answers card it sits on, and the composer below
|
|
17
|
+
* is the only input that continues the topic on screen.
|
|
13
18
|
*/
|
|
14
19
|
|
|
15
20
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
@@ -77,7 +82,7 @@ export function BtwAskCard({
|
|
|
77
82
|
};
|
|
78
83
|
|
|
79
84
|
return (
|
|
80
|
-
<div className="
|
|
85
|
+
<div className="flex items-center gap-1 pl-1.5 pr-2 py-1.5 border-b border-ink/10 bg-canvas/40">
|
|
81
86
|
<button
|
|
82
87
|
type="button"
|
|
83
88
|
onClick={() => {
|
|
@@ -1,34 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* BTW panel message body — the scrollable conversation of the active topic.
|
|
3
8
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
9
|
+
* It renders through the CHAT's own `MessageList`, not a btw-specific renderer:
|
|
10
|
+
* a turn's `messages` are `ChatMessageData` (thinking, tool calls, usage,
|
|
11
|
+
* errors), so a tool the side child runs draws the same card it would in the
|
|
12
|
+
* chat, and the two can never drift. This file only assembles the row list —
|
|
13
|
+
* each turn's question as a user row, then that turn's conversation — and adds
|
|
14
|
+
* the btw-only status line for a turn that did not complete cleanly.
|
|
15
|
+
*
|
|
16
|
+
* Pure presentation: no fetch, no session state.
|
|
6
17
|
*/
|
|
7
18
|
|
|
8
19
|
import { useEffect, useMemo, useRef } from 'preact/hooks';
|
|
9
|
-
import type { BtwTopic, BtwTurnStatus } from '@/shared/types
|
|
10
|
-
import {
|
|
20
|
+
import type { BtwTopic, BtwTurn, BtwTurnStatus, ChatMessageData } from '@/shared/types';
|
|
21
|
+
import { MessageList } from '@/client/components/workspace/chat-timeline/MessageList';
|
|
11
22
|
|
|
12
23
|
export interface BtwMessagesProps {
|
|
13
24
|
topic: BtwTopic | null;
|
|
14
|
-
/**
|
|
15
|
-
|
|
25
|
+
/** The running turn's conversation, chat-shaped (empty when none). */
|
|
26
|
+
liveMessages: ChatMessageData[];
|
|
16
27
|
/** Panel-level error line, surfaced above the turns. */
|
|
17
28
|
error: string | null;
|
|
29
|
+
provider?: string;
|
|
30
|
+
providerNames?: Record<string, string>;
|
|
31
|
+
modelName?: string;
|
|
32
|
+
/** Thinking level the side child runs at, for the run footer. */
|
|
33
|
+
thinkingLevel?: string;
|
|
34
|
+
isMobile?: boolean;
|
|
18
35
|
}
|
|
19
36
|
|
|
20
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Status line for a turn that did not complete cleanly.
|
|
39
|
+
*
|
|
40
|
+
* `running` has no line here: the panel reports a turn in flight with the
|
|
41
|
+
* chat's own `GeneratingIndicator` at the bottom of the card, and a second
|
|
42
|
+
* "Thinking…" under the answer said the same thing twice.
|
|
43
|
+
*/
|
|
21
44
|
function AnswerStatus({ status }: { status: BtwTurnStatus }) {
|
|
22
|
-
if (status === 'complete') return null;
|
|
23
|
-
|
|
24
|
-
if (status === 'running') {
|
|
25
|
-
return (
|
|
26
|
-
<div className="flex items-center gap-1.5 text-[11px] text-ink/50">
|
|
27
|
-
<span className="w-1.5 h-1.5 rounded-full bg-ink/40 animate-pulse" />
|
|
28
|
-
<span>Thinking…</span>
|
|
29
|
-
</div>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
45
|
+
if (status === 'complete' || status === 'running') return null;
|
|
32
46
|
|
|
33
47
|
if (status === 'cancelled') {
|
|
34
48
|
return <div className="text-[11px] text-ink/50">Cancelled</div>;
|
|
@@ -37,15 +51,63 @@ function AnswerStatus({ status }: { status: BtwTurnStatus }) {
|
|
|
37
51
|
return <div className="text-[11px] text-error">{status === 'failed' ? 'Failed' : 'Interrupted'}</div>;
|
|
38
52
|
}
|
|
39
53
|
|
|
40
|
-
|
|
54
|
+
/** The question row for a turn, in the chat's own user-bubble shape. */
|
|
55
|
+
function questionRow(topic: BtwTopic, turn: BtwTurn): ChatMessageData {
|
|
56
|
+
return {
|
|
57
|
+
id: `btw-${topic.id}-${turn.index}-user`,
|
|
58
|
+
role: 'user',
|
|
59
|
+
content: turn.question,
|
|
60
|
+
timestamp: new Date(turn.createdAt).toISOString(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A turn's rows: its stored conversation, or — for a turn written before the
|
|
66
|
+
* conversation was stored — its plain `answer` as a single assistant row.
|
|
67
|
+
* Without this fallback every pre-existing turn would render its question and
|
|
68
|
+
* no answer at all.
|
|
69
|
+
*
|
|
70
|
+
* The child ECHOES the prompt it received, and that echo is the `<btw>` wrapper
|
|
71
|
+
* this panel itself built — the caller already renders the user's own question
|
|
72
|
+
* as a row, so the echo is dropped rather than shown as a second, uglier copy
|
|
73
|
+
* of the same turn.
|
|
74
|
+
*/
|
|
75
|
+
function turnRows(topic: BtwTopic, turn: BtwTurn): ChatMessageData[] {
|
|
76
|
+
const conversation = turn.messages.filter((message) => message.role !== 'user');
|
|
77
|
+
if (conversation.length > 0) return conversation;
|
|
78
|
+
if (!turn.answer) return [];
|
|
79
|
+
return [
|
|
80
|
+
{
|
|
81
|
+
id: `btw-${topic.id}-${turn.index}-answer`,
|
|
82
|
+
role: 'ai',
|
|
83
|
+
content: turn.answer,
|
|
84
|
+
timestamp: new Date(turn.updatedAt).toISOString(),
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function BtwMessages({ topic, liveMessages, error, provider, providerNames, modelName, thinkingLevel, isMobile = false }: BtwMessagesProps) {
|
|
41
90
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
42
91
|
const turns = topic?.turns ?? [];
|
|
43
92
|
|
|
93
|
+
// One row list for the whole topic: each question, then its conversation. The
|
|
94
|
+
// running turn's live rows replace its (still empty) stored ones.
|
|
95
|
+
const rows = useMemo<ChatMessageData[]>(() => {
|
|
96
|
+
if (!topic) return [];
|
|
97
|
+
return turns.flatMap((turn) => {
|
|
98
|
+
const running = turn.status === 'running' && liveMessages.length > 0;
|
|
99
|
+
// The live buffer carries the same echoed prompt the stored conversation
|
|
100
|
+
// does, so it is filtered the same way (see `turnRows`).
|
|
101
|
+
const conversation = running ? liveMessages.filter((message) => message.role !== 'user') : turnRows(topic, turn);
|
|
102
|
+
return [questionRow(topic, turn), ...conversation];
|
|
103
|
+
});
|
|
104
|
+
}, [topic, turns, liveMessages]);
|
|
105
|
+
|
|
44
106
|
// Cheap scroll signal: any visible text growth (stream delta, new question,
|
|
45
107
|
// topic switch) changes this number.
|
|
46
108
|
const renderedLength = useMemo(
|
|
47
|
-
() =>
|
|
48
|
-
[
|
|
109
|
+
() => rows.reduce((total, row) => total + row.content.length + (row.toolCalls?.length ?? 0) * 64, 0),
|
|
110
|
+
[rows],
|
|
49
111
|
);
|
|
50
112
|
|
|
51
113
|
useEffect(() => {
|
|
@@ -53,47 +115,39 @@ export function BtwMessages({ topic, liveAnswer, error }: BtwMessagesProps) {
|
|
|
53
115
|
if (body) body.scrollTop = body.scrollHeight;
|
|
54
116
|
}, [renderedLength]);
|
|
55
117
|
|
|
56
|
-
const lastIndex = turns.length - 1;
|
|
57
|
-
|
|
58
118
|
return (
|
|
59
119
|
<div
|
|
60
120
|
ref={scrollRef}
|
|
61
|
-
className="flex-1 min-h-0 overflow-y-auto scrollbar-overlay-container scrollbar-overlay-static px-4 py-3
|
|
121
|
+
className="flex-1 min-h-0 overflow-y-auto scrollbar-overlay-container scrollbar-overlay-static px-4 py-3"
|
|
62
122
|
>
|
|
63
123
|
{error && (
|
|
64
124
|
<div className="bg-error/10 border border-error/30 text-error rounded-lg px-3 py-2 text-[12px]">{error}</div>
|
|
65
125
|
)}
|
|
66
126
|
|
|
67
127
|
{topic === null ? (
|
|
68
|
-
<div className="flex h-
|
|
69
|
-
|
|
128
|
+
<div className="flex min-h-[72px] items-center justify-center px-4 text-center text-[12px] text-ink/50">
|
|
129
|
+
Ask a question above — the answer stays out of this chat's transcript.
|
|
70
130
|
</div>
|
|
71
131
|
) : (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)}
|
|
92
|
-
|
|
93
|
-
<AnswerStatus status={turn.status} />
|
|
94
|
-
</div>
|
|
95
|
-
);
|
|
96
|
-
})
|
|
132
|
+
<>
|
|
133
|
+
<MessageList
|
|
134
|
+
messages={rows}
|
|
135
|
+
isGenerating={topic.turns.some((turn) => turn.status === 'running')}
|
|
136
|
+
provider={provider}
|
|
137
|
+
providerNames={providerNames}
|
|
138
|
+
modelName={modelName}
|
|
139
|
+
thinkingLevel={thinkingLevel}
|
|
140
|
+
isMobile={isMobile}
|
|
141
|
+
userActions={false}
|
|
142
|
+
/>
|
|
143
|
+
{/* A turn that stopped without completing is the only thing the chat's
|
|
144
|
+
own rows cannot express, so it is the only btw-specific line. */}
|
|
145
|
+
{turns
|
|
146
|
+
.filter((turn) => turn.status !== 'complete' && turn.status !== 'running')
|
|
147
|
+
.map((turn) => (
|
|
148
|
+
<AnswerStatus key={turn.index} status={turn.status} />
|
|
149
|
+
))}
|
|
150
|
+
</>
|
|
97
151
|
)}
|
|
98
152
|
</div>
|
|
99
153
|
);
|
|
@@ -4,33 +4,67 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* The side-question
|
|
7
|
+
* The side-question panel — what the chat composer becomes while BTW mode is on.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* TWO cards, and the split is what keeps it unambiguous:
|
|
10
|
+
*
|
|
11
|
+
* 1. the panel card — the ask card as its HEADER (the field that starts a new
|
|
12
|
+
* topic, plus `⟳` start over / `⌄` topic history / `⧉` promote / `✕` leave),
|
|
13
|
+
* the topic's turns as its body, and the running turn's indicator pinned at
|
|
14
|
+
* the BOTTOM, where it reports the answer being streamed above it;
|
|
15
|
+
* 2. the composer — the chat's own `ChatInput`, for follow-ups to the topic on
|
|
16
|
+
* screen.
|
|
17
|
+
*
|
|
18
|
+
* The ask field used to be a free-standing card directly above that composer,
|
|
19
|
+
* and users typed a follow-up into it. As a header it belongs to the answers
|
|
20
|
+
* card it sits on, and only one input continues the topic in view.
|
|
21
|
+
*
|
|
22
|
+
* Nothing here enters the chat transcript — that is the point of a side
|
|
23
|
+
* question — and promotion is the one explicit action that moves an answer into
|
|
24
|
+
* the chat, as a session branched from it.
|
|
25
|
+
*
|
|
26
|
+
* The composer card is the CHAT's own `ChatInput`, not a look-alike: a side
|
|
27
|
+
* question runs a child with tools and an access mode, so it offers the same
|
|
28
|
+
* model / thinking / access controls, the same attachments, drop targets and
|
|
29
|
+
* keybindings. Only the autocomplete is off (`enablePicker`) — a side question
|
|
30
|
+
* has no file tree, commands or skills to complete against.
|
|
15
31
|
*/
|
|
16
32
|
|
|
17
33
|
import { useState } from 'preact/hooks';
|
|
34
|
+
import type { Attachment } from '@/shared/types';
|
|
18
35
|
import type { BtwMode } from '@/client/hooks/chat/btw/mode';
|
|
19
36
|
import { BtwAskCard } from '@/client/components/workspace/btw-panel/AskCard';
|
|
20
37
|
import { BtwMessages } from '@/client/components/workspace/btw-panel/Messages';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
38
|
+
import { ChatInput } from '@/client/components/workspace/chat-timeline/chat-input/index';
|
|
39
|
+
import { GeneratingIndicator } from '@/client/components/workspace/chat-timeline/GeneratingIndicator';
|
|
40
|
+
import { ExtensionDialog } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/Lazy';
|
|
41
|
+
|
|
42
|
+
/** Tallest the answer card grows before its own scrollbar takes over. */
|
|
43
|
+
const ANSWERS_MAX_HEIGHT_PX = 520;
|
|
23
44
|
|
|
24
45
|
export interface BtwFormProps {
|
|
25
46
|
mode: BtwMode;
|
|
26
|
-
|
|
47
|
+
appSettings: Record<string, unknown>;
|
|
48
|
+
composerModelRef: { current: { provider: string; modelId: string; thinkingLevel: string } | null };
|
|
49
|
+
rootPath: string | null;
|
|
50
|
+
variant: 'desktop' | 'mobile';
|
|
51
|
+
/** Chat model/provider, shown until the topic reports the one it ran on. */
|
|
27
52
|
modelName?: string;
|
|
28
53
|
provider?: string;
|
|
29
54
|
providerNames?: Record<string, string>;
|
|
30
55
|
}
|
|
31
56
|
|
|
32
|
-
export function BtwForm({
|
|
33
|
-
|
|
57
|
+
export function BtwForm({
|
|
58
|
+
mode,
|
|
59
|
+
appSettings,
|
|
60
|
+
composerModelRef,
|
|
61
|
+
rootPath,
|
|
62
|
+
variant,
|
|
63
|
+
modelName,
|
|
64
|
+
provider,
|
|
65
|
+
providerNames,
|
|
66
|
+
}: BtwFormProps) {
|
|
67
|
+
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
|
34
68
|
const lastTurn = mode.activeTopic?.turns[mode.activeTopic.turns.length - 1];
|
|
35
69
|
// `busy` is the command in flight (a promote POST, say): promoting again from
|
|
36
70
|
// the same click burst would be a second request for work already running.
|
|
@@ -38,51 +72,96 @@ export function BtwForm({ mode, modelName, provider, providerNames }: BtwFormPro
|
|
|
38
72
|
// session exists, and the server hands that id back on a repeat promote.
|
|
39
73
|
const canPromote =
|
|
40
74
|
Boolean(mode.activeTopic) && !mode.runningTopicId && !mode.busy && !mode.activeTopic?.promotedSessionId && lastTurn?.status === 'complete';
|
|
41
|
-
const
|
|
75
|
+
const dialog = mode.dialogs[0];
|
|
76
|
+
const running = Boolean(mode.runningTopicId);
|
|
42
77
|
|
|
43
78
|
return (
|
|
44
79
|
<div className="flex flex-col gap-2">
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
80
|
+
{/* ONE card, so the header's field and the body's answers read as one
|
|
81
|
+
panel — and the composer below it as the other. The ask card is that
|
|
82
|
+
header: a second free-standing input beside the composer is what made
|
|
83
|
+
users type a follow-up into the new-question field. */}
|
|
84
|
+
<div
|
|
85
|
+
className="bg-paper border border-ink/15 rounded-2xl shadow-xl flex flex-col overflow-hidden"
|
|
86
|
+
style={{ maxHeight: ANSWERS_MAX_HEIGHT_PX }}
|
|
87
|
+
>
|
|
88
|
+
<BtwAskCard
|
|
89
|
+
value={mode.askDraft}
|
|
90
|
+
onChange={mode.setAskDraft}
|
|
91
|
+
onSubmit={mode.submitAsk}
|
|
92
|
+
onNewQuestion={mode.resetQuestion}
|
|
93
|
+
onClose={mode.exit}
|
|
94
|
+
running={running}
|
|
95
|
+
topics={mode.topics}
|
|
96
|
+
activeTopicId={mode.activeTopic?.id ?? null}
|
|
97
|
+
onSelectTopic={mode.selectTopic}
|
|
98
|
+
onDeleteTopic={mode.removeTopic}
|
|
99
|
+
canPromote={canPromote}
|
|
100
|
+
onPromote={() => void mode.promote()}
|
|
101
|
+
/>
|
|
50
102
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
103
|
+
<BtwMessages
|
|
104
|
+
topic={mode.activeTopic}
|
|
105
|
+
liveMessages={mode.liveMessages}
|
|
106
|
+
error={mode.error}
|
|
107
|
+
provider={mode.activeTopic?.model?.provider ?? provider}
|
|
108
|
+
providerNames={providerNames}
|
|
109
|
+
modelName={mode.activeTopic?.model?.name ?? modelName}
|
|
110
|
+
thinkingLevel={mode.thinkingLevel}
|
|
111
|
+
isMobile={variant === 'mobile'}
|
|
112
|
+
/>
|
|
113
|
+
|
|
114
|
+
{/* The turn in flight reports itself HERE, at the bottom of the card and
|
|
115
|
+
below the answer it is streaming — not above it, and not as the
|
|
116
|
+
"Thinking…" line the transcript used to draw under the answer. */}
|
|
117
|
+
{running && (
|
|
118
|
+
<GeneratingIndicator
|
|
119
|
+
id="btw-generating-indicator"
|
|
120
|
+
generatingVerb={mode.liveVerb}
|
|
121
|
+
modelName={mode.activeTopic?.model?.name ?? modelName}
|
|
122
|
+
provider={mode.activeTopic?.model?.provider ?? provider}
|
|
123
|
+
providerNames={providerNames}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
65
127
|
|
|
66
|
-
<
|
|
128
|
+
<ChatInput
|
|
67
129
|
value={mode.followUpDraft}
|
|
68
130
|
onChange={mode.setFollowUpDraft}
|
|
69
|
-
|
|
70
|
-
// Hand the
|
|
71
|
-
// re-send the same
|
|
72
|
-
mode.submitFollowUp(mode.followUpDraft,
|
|
131
|
+
onSend={(outgoing) => {
|
|
132
|
+
// Hand the attachments over and clear them: leaving them staged would
|
|
133
|
+
// re-send the same file with every later follow-up.
|
|
134
|
+
mode.submitFollowUp(mode.followUpDraft, outgoing);
|
|
73
135
|
setAttachments([]);
|
|
74
136
|
}}
|
|
137
|
+
isGenerating={running}
|
|
75
138
|
onStop={mode.abort}
|
|
76
|
-
|
|
77
|
-
modelLabel={mode.activeTopic?.model?.name ?? modelName}
|
|
78
|
-
provider={mode.activeTopic?.model?.provider ?? provider}
|
|
79
|
-
providerNames={providerNames}
|
|
139
|
+
appSettings={appSettings}
|
|
80
140
|
attachments={attachments}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
141
|
+
onAttachmentsChange={setAttachments}
|
|
142
|
+
rootPath={rootPath}
|
|
143
|
+
variant={variant}
|
|
144
|
+
enablePicker={false}
|
|
145
|
+
placeholder="Ask in this btw session..."
|
|
146
|
+
accessMode={mode.accessMode}
|
|
147
|
+
onAccessModeChange={mode.setAccessMode}
|
|
148
|
+
onThinkingLevelChange={mode.setThinkingLevel}
|
|
149
|
+
onModelChange={mode.setModel}
|
|
150
|
+
sessionModel={mode.activeTopic?.model ? { provider: mode.activeTopic.model.provider, modelId: mode.activeTopic.model.id } : null}
|
|
151
|
+
sessionThinkingLevel={mode.thinkingLevel}
|
|
152
|
+
composerModelRef={composerModelRef}
|
|
85
153
|
/>
|
|
154
|
+
|
|
155
|
+
{/* A side child runs with tools, so a gated call parks its turn on an
|
|
156
|
+
approval dialog. There is no tool card here to host one inline (unlike
|
|
157
|
+
the chat's `ask`), so every answerable request is a modal — and the
|
|
158
|
+
dialog brings its own backdrop. */}
|
|
159
|
+
{dialog && (
|
|
160
|
+
<ExtensionDialog
|
|
161
|
+
request={dialog.request}
|
|
162
|
+
onRespond={(_, response) => mode.respondToDialog(dialog.request.id, response)}
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
86
165
|
</div>
|
|
87
166
|
);
|
|
88
167
|
}
|