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
|
@@ -26,6 +26,13 @@ interface AiMessageFooterProps {
|
|
|
26
26
|
onRetry?: (msgId: string) => void;
|
|
27
27
|
onNewChat?: (content: string) => void;
|
|
28
28
|
isMobile?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the row offers Retry / New-chat. Both act on the CHAT (re-run a chat
|
|
31
|
+
* turn, branch a chat session from an answer), so a footer that describes a
|
|
32
|
+
* row the chat does not own — the BTW panel's side answer — turns them off
|
|
33
|
+
* instead of rendering buttons that do nothing.
|
|
34
|
+
*/
|
|
35
|
+
showActions?: boolean;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
export function AiMessageFooter({
|
|
@@ -41,6 +48,7 @@ export function AiMessageFooter({
|
|
|
41
48
|
onRetry,
|
|
42
49
|
onNewChat,
|
|
43
50
|
isMobile = false,
|
|
51
|
+
showActions = true,
|
|
44
52
|
}: AiMessageFooterProps) {
|
|
45
53
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
46
54
|
|
|
@@ -198,14 +206,18 @@ export function AiMessageFooter({
|
|
|
198
206
|
wrapLabel
|
|
199
207
|
onClick={() => setIsMenuOpen(false)}
|
|
200
208
|
/>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
{showActions && (
|
|
210
|
+
<>
|
|
211
|
+
<button type="button" onClick={handleRetry} className="flex min-h-12 w-full items-center gap-3 rounded-lg px-3 text-left text-sm text-ink transition-colors hover:bg-ink/5 cursor-pointer">
|
|
212
|
+
<RotateCcw size={20} className="shrink-0 text-ink/55" />
|
|
213
|
+
<span>Retry response</span>
|
|
214
|
+
</button>
|
|
215
|
+
<button type="button" onClick={handleNewChat} className="flex min-h-12 w-full items-center gap-3 rounded-lg px-3 text-left text-sm text-ink transition-colors hover:bg-ink/5 cursor-pointer">
|
|
216
|
+
<MessageSquarePlus size={20} className="shrink-0 text-ink/55" />
|
|
217
|
+
<span>Start new chat from this answer</span>
|
|
218
|
+
</button>
|
|
219
|
+
</>
|
|
220
|
+
)}
|
|
209
221
|
</div>
|
|
210
222
|
</div>
|
|
211
223
|
</div>
|
|
@@ -232,18 +244,22 @@ export function AiMessageFooter({
|
|
|
232
244
|
</div>
|
|
233
245
|
|
|
234
246
|
<div className="flex items-center space-x-1 shrink-0">
|
|
235
|
-
|
|
236
|
-
<
|
|
237
|
-
|
|
247
|
+
{showActions && (
|
|
248
|
+
<button type="button" className="flex items-center hover:text-ink transition-colors p-1 rounded hover:bg-ink/5 cursor-pointer" title="Re-run / Retry generation" onClick={handleRetry}>
|
|
249
|
+
<RotateCcw size={12} />
|
|
250
|
+
</button>
|
|
251
|
+
)}
|
|
238
252
|
<CopyButton
|
|
239
253
|
text={content}
|
|
240
254
|
className="flex items-center hover:text-ink transition-colors p-1 rounded hover:bg-ink/5 cursor-pointer"
|
|
241
255
|
iconSize={12}
|
|
242
256
|
title="Copy response"
|
|
243
257
|
/>
|
|
244
|
-
|
|
245
|
-
<
|
|
246
|
-
|
|
258
|
+
{showActions && (
|
|
259
|
+
<button type="button" className="flex items-center hover:text-ink transition-colors p-1 rounded hover:bg-ink/5 cursor-pointer" title="New Chat from here" onClick={handleNewChat}>
|
|
260
|
+
<MessageSquarePlus size={12} />
|
|
261
|
+
</button>
|
|
262
|
+
)}
|
|
247
263
|
</div>
|
|
248
264
|
</div>
|
|
249
265
|
);
|
|
@@ -118,7 +118,16 @@ export function ComposerDock({
|
|
|
118
118
|
isSteering
|
|
119
119
|
/>
|
|
120
120
|
{btw.open ? (
|
|
121
|
-
<BtwForm
|
|
121
|
+
<BtwForm
|
|
122
|
+
mode={btw}
|
|
123
|
+
appSettings={appSettings}
|
|
124
|
+
composerModelRef={composerModelRef}
|
|
125
|
+
rootPath={rootPath}
|
|
126
|
+
variant={variant}
|
|
127
|
+
modelName={modelName}
|
|
128
|
+
provider={provider}
|
|
129
|
+
providerNames={providerNames}
|
|
130
|
+
/>
|
|
122
131
|
) : (
|
|
123
132
|
<ChatInput
|
|
124
133
|
value={inputValue}
|
|
@@ -7,6 +7,12 @@ interface GeneratingIndicatorProps {
|
|
|
7
7
|
generatingVerb?: string;
|
|
8
8
|
provider?: string;
|
|
9
9
|
providerNames?: Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* DOM id for the docked row. Two indicators can be mounted at once — the chat
|
|
12
|
+
* run and a side question both streaming — so a second caller must pass its
|
|
13
|
+
* own id rather than duplicating the default one.
|
|
14
|
+
*/
|
|
15
|
+
id?: string;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
const COOL_VERBS = [
|
|
@@ -18,7 +24,7 @@ const COOL_VERBS = [
|
|
|
18
24
|
'Compiling edge routes'
|
|
19
25
|
];
|
|
20
26
|
|
|
21
|
-
export function GeneratingIndicator({ modelName, generatingVerb, provider, providerNames }: GeneratingIndicatorProps) {
|
|
27
|
+
export function GeneratingIndicator({ modelName, generatingVerb, provider, providerNames, id = 'generating-docked-indicator' }: GeneratingIndicatorProps) {
|
|
22
28
|
const [activeVerbIndex, setActiveVerbIndex] = useState(0);
|
|
23
29
|
|
|
24
30
|
// Rotate action verb every 2.8s
|
|
@@ -38,7 +44,7 @@ export function GeneratingIndicator({ modelName, generatingVerb, provider, provi
|
|
|
38
44
|
|
|
39
45
|
return (
|
|
40
46
|
<div
|
|
41
|
-
id=
|
|
47
|
+
id={id}
|
|
42
48
|
className="w-full flex items-center px-1 py-1 bg-transparent border-0 border-none text-[11px] font-mono select-none animate-in fade-in duration-200"
|
|
43
49
|
>
|
|
44
50
|
<style>{`
|
|
@@ -23,6 +23,12 @@ interface ChatMessageItemProps {
|
|
|
23
23
|
className?: string;
|
|
24
24
|
/** Whether the message immediately preceding this one was also an assistant message. */
|
|
25
25
|
isPrevAssistant?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether a user row offers Undo / New-chat. Both act on the CHAT, so a row
|
|
28
|
+
* that is not a chat turn (the BTW panel's question row) turns them off
|
|
29
|
+
* instead of rendering buttons that do nothing.
|
|
30
|
+
*/
|
|
31
|
+
userActions?: boolean;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
export const ChatMessageItem = memo(function ChatMessageItem({
|
|
@@ -32,6 +38,7 @@ export const ChatMessageItem = memo(function ChatMessageItem({
|
|
|
32
38
|
onNewChat,
|
|
33
39
|
className = '',
|
|
34
40
|
isPrevAssistant = false,
|
|
41
|
+
userActions = true,
|
|
35
42
|
}: ChatMessageItemProps) {
|
|
36
43
|
const isUser = msg.role === 'user';
|
|
37
44
|
|
|
@@ -107,14 +114,16 @@ export const ChatMessageItem = memo(function ChatMessageItem({
|
|
|
107
114
|
|
|
108
115
|
<div className="flex items-center space-x-1">
|
|
109
116
|
{/* Undo Button (to the left of copy button) */}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
{userActions && (
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
className="flex items-center hover:text-ink transition-colors p-1 rounded hover:bg-ink/5 cursor-pointer"
|
|
121
|
+
title="Undo / Edit message"
|
|
122
|
+
onClick={handleUndo}
|
|
123
|
+
>
|
|
124
|
+
<Undo2 size={12} />
|
|
125
|
+
</button>
|
|
126
|
+
)}
|
|
118
127
|
|
|
119
128
|
{/* Copy Button */}
|
|
120
129
|
<CopyButton
|
|
@@ -125,14 +134,16 @@ export const ChatMessageItem = memo(function ChatMessageItem({
|
|
|
125
134
|
/>
|
|
126
135
|
|
|
127
136
|
{/* New Chat Button */}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
{userActions && (
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
className="flex items-center hover:text-ink transition-colors p-1 rounded hover:bg-ink/5 cursor-pointer"
|
|
141
|
+
title="New Chat from here"
|
|
142
|
+
onClick={handleNewChat}
|
|
143
|
+
>
|
|
144
|
+
<MessageSquarePlus size={12} />
|
|
145
|
+
</button>
|
|
146
|
+
)}
|
|
136
147
|
</div>
|
|
137
148
|
</div>
|
|
138
149
|
</div>
|
|
@@ -21,6 +21,14 @@ export interface MessageListProps {
|
|
|
21
21
|
onRetry?: (id: string) => void;
|
|
22
22
|
onNewChat?: (content: string) => void;
|
|
23
23
|
isMobile?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether chat-owning actions render: a USER row's Undo / New-chat, and the
|
|
26
|
+
* run footer's Retry / New-chat. All of them act on the CHAT (rewind a chat
|
|
27
|
+
* turn, re-run it, branch a session from it), so a renderer that draws rows
|
|
28
|
+
* the chat does not own — the BTW panel's side question and its answer — turns
|
|
29
|
+
* them off instead of showing buttons that do nothing.
|
|
30
|
+
*/
|
|
31
|
+
userActions?: boolean;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
/**
|
|
@@ -49,6 +57,7 @@ export const MessageList = memo(function MessageList({
|
|
|
49
57
|
onRetry,
|
|
50
58
|
onNewChat,
|
|
51
59
|
isMobile = false,
|
|
60
|
+
userActions = true,
|
|
52
61
|
}: MessageListProps) {
|
|
53
62
|
const { streamingIdx, footers, prevNonNoticeIdx } = useMemo(() => {
|
|
54
63
|
return {
|
|
@@ -79,6 +88,7 @@ export const MessageList = memo(function MessageList({
|
|
|
79
88
|
onUndo={onUndo}
|
|
80
89
|
onNewChat={onNewChat}
|
|
81
90
|
isPrevAssistant={isPrevAssistant}
|
|
91
|
+
userActions={userActions}
|
|
82
92
|
className={isNoticeRow(msg) ? 'mt-3 mb-1' : isAiFragment ? 'mt-1' : 'mt-3'}
|
|
83
93
|
/>
|
|
84
94
|
{footer && (
|
|
@@ -93,6 +103,7 @@ export const MessageList = memo(function MessageList({
|
|
|
93
103
|
onRetry={onRetry}
|
|
94
104
|
onNewChat={onNewChat}
|
|
95
105
|
isMobile={isMobile}
|
|
106
|
+
showActions={userActions}
|
|
96
107
|
/>
|
|
97
108
|
)}
|
|
98
109
|
</Fragment>
|
|
@@ -19,6 +19,12 @@ interface RunFooterProps {
|
|
|
19
19
|
onNewChat?: (content: string) => void;
|
|
20
20
|
/** Mobile uses a compact provider/model footer with a bottom-sheet menu. */
|
|
21
21
|
isMobile?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the footer offers Retry / New-chat. Both act on the CHAT, so a
|
|
24
|
+
* footer describing a row the chat does not own (the BTW panel's side answer)
|
|
25
|
+
* turns them off rather than rendering dead buttons.
|
|
26
|
+
*/
|
|
27
|
+
showActions?: boolean;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/**
|
|
@@ -38,6 +44,7 @@ export function RunFooter({
|
|
|
38
44
|
onRetry,
|
|
39
45
|
onNewChat,
|
|
40
46
|
isMobile = false,
|
|
47
|
+
showActions = true,
|
|
41
48
|
}: RunFooterProps) {
|
|
42
49
|
return (
|
|
43
50
|
<div className="mt-2">
|
|
@@ -54,6 +61,7 @@ export function RunFooter({
|
|
|
54
61
|
onRetry={onRetry}
|
|
55
62
|
onNewChat={onNewChat}
|
|
56
63
|
isMobile={isMobile}
|
|
64
|
+
showActions={showActions}
|
|
57
65
|
/>
|
|
58
66
|
</div>
|
|
59
67
|
);
|
|
@@ -33,6 +33,8 @@ export function ChatInput({
|
|
|
33
33
|
variant = 'desktop',
|
|
34
34
|
accessMode,
|
|
35
35
|
onAccessModeChange,
|
|
36
|
+
enablePicker = true,
|
|
37
|
+
placeholder,
|
|
36
38
|
/** Written on every model/thinking pick so the send path can snapshot the
|
|
37
39
|
* selection into queued items without lifting ChatInput state. */
|
|
38
40
|
composerModelRef,
|
|
@@ -62,6 +64,14 @@ export function ChatInput({
|
|
|
62
64
|
rootPath?: string | null;
|
|
63
65
|
accessMode: ApprovalMode;
|
|
64
66
|
onAccessModeChange: (mode: ApprovalMode) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Whether the `@` / `/` / `!` / `#` autocomplete runs. Off for the
|
|
69
|
+
* side-question form, which has no file tree, commands or skills to offer —
|
|
70
|
+
* and whose text is never run through the mention translator.
|
|
71
|
+
*/
|
|
72
|
+
enablePicker?: boolean;
|
|
73
|
+
/** Overrides the default hint text (the side-question form names its own). */
|
|
74
|
+
placeholder?: string;
|
|
65
75
|
/**
|
|
66
76
|
* `mobile` sizes the composer for a phone: 16px text (iOS Safari zooms the
|
|
67
77
|
* viewport when focusing an input below that), thumb-sized send/stop
|
|
@@ -292,7 +302,8 @@ export function ChatInput({
|
|
|
292
302
|
disabled={disabled}
|
|
293
303
|
appSettings={appSettings}
|
|
294
304
|
rootPath={rootPath}
|
|
295
|
-
|
|
305
|
+
enablePicker={enablePicker}
|
|
306
|
+
placeholder={placeholder ?? (disabled ? "Please select a workspace above to start prompting..." : "@ for files/agents; / for commands and skills; ! for shell; # for snippets (Paste images/files here)")}
|
|
296
307
|
className={`w-full bg-transparent border-none focus:outline-none resize-none text-ink placeholder-ink/40 disabled:opacity-50 disabled:cursor-not-allowed ${
|
|
297
308
|
isMobile ? 'px-3 py-3 text-base min-h-[68px] max-h-40' : 'px-3 py-3 text-sm min-h-[80px]'
|
|
298
309
|
}`}
|
|
@@ -88,9 +88,14 @@ export function ChatTimeline({ className = '', appSettings = {}, onSessionTitle,
|
|
|
88
88
|
|
|
89
89
|
// Ask dialogs render on their own tool card; anything else omp is blocked on
|
|
90
90
|
// (an approval gate, an extension picker) has no card and keeps the modal.
|
|
91
|
+
// The modal also waits for the session's committed history: a dialog replayed
|
|
92
|
+
// on open (the agent probe answers from local flags, long before the JSONL
|
|
93
|
+
// fetch lands) has no card to be matched against yet, so deciding "no card
|
|
94
|
+
// owns this" during that window painted the ask modal and then took it back —
|
|
95
|
+
// the flash on switching to a session parked on a question.
|
|
91
96
|
const { framesByTool, modalRequest } = useMemo(
|
|
92
|
-
() => splitAskFrames(localMessages, extensionDialogs),
|
|
93
|
-
[localMessages, extensionDialogs],
|
|
97
|
+
() => splitAskFrames(localMessages, extensionDialogs, !sessionLoading),
|
|
98
|
+
[localMessages, extensionDialogs, sessionLoading],
|
|
94
99
|
);
|
|
95
100
|
const respondToFrame = useCallback((request: ExtensionUiDialogRequest, response: ExtensionDialogResponse) => {
|
|
96
101
|
void respondToExtensionUi(request, response);
|
package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* One question of an `ask` call, in whichever of its
|
|
8
|
-
* answered (omp already recorded it), asking (its dialog is live)
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* One question of an `ask` call, in whichever of its four states it is in:
|
|
8
|
+
* answered (omp already recorded it), asking (its dialog is live), still queued
|
|
9
|
+
* behind an earlier question (omp asks them strictly in order, so the later
|
|
10
|
+
* ones can only be previewed until their turn comes) or abandoned — the ask
|
|
11
|
+
* ended without reaching it, so no dialog can arrive again.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
import { CheckCircle2, Clock } from 'lucide-preact';
|
|
14
|
+
import { Ban, CheckCircle2, Clock } from 'lucide-preact';
|
|
14
15
|
import { MarkdownRenderer } from '@/client/components/common/MarkdownRenderer';
|
|
15
16
|
import type { ExtensionUiDialogRequest } from '@/shared/types/omp/agent';
|
|
16
17
|
import type { AskAnswer, AskQuestion } from '@/shared/lib/chat/ask-questions';
|
|
@@ -28,6 +29,12 @@ interface QuestionBlockProps {
|
|
|
28
29
|
draft?: string;
|
|
29
30
|
/** Unanswered, but omp has already moved past it — its dialog is gone. */
|
|
30
31
|
passed: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The ask's tool call already failed or was aborted: omp withdrew its dialog
|
|
34
|
+
* and cannot raise another, so an unanswered question is not queued behind
|
|
35
|
+
* anything.
|
|
36
|
+
*/
|
|
37
|
+
settled: boolean;
|
|
31
38
|
/** Labels toggled locally on a multi-select question. */
|
|
32
39
|
multiSelected: string[];
|
|
33
40
|
onAnswer: (value: string) => void;
|
|
@@ -49,14 +56,24 @@ export function QuestionBlock({
|
|
|
49
56
|
recorded,
|
|
50
57
|
draft,
|
|
51
58
|
passed,
|
|
59
|
+
settled,
|
|
52
60
|
multiSelected,
|
|
53
61
|
onAnswer,
|
|
54
62
|
onToggle,
|
|
55
63
|
onRespond,
|
|
56
64
|
}: QuestionBlockProps) {
|
|
57
65
|
const live = frames.length > 0 ? frames[frames.length - 1] : null;
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
// A recorded answer with no labels is still an answer — omp accepts an empty
|
|
67
|
+
// multi-select as "select none" — so presence, never the rendered part count,
|
|
68
|
+
// decides whether a question is answered. Reading the parts made such a
|
|
69
|
+
// question fall through to the queued state and claim a dialog was live.
|
|
70
|
+
const hasAnswer = recorded !== null || draft !== undefined || multiSelected.length > 0;
|
|
71
|
+
const parts = recorded ? answerParts(recorded) : draft ? [draft] : multiSelected;
|
|
72
|
+
const chips = parts.length > 0 ? parts : ['none selected'];
|
|
73
|
+
// A settled ask has no live dialog by construction, even if a frame this
|
|
74
|
+
// client never saw withdrawn is still in its queue: answering it would post
|
|
75
|
+
// a response for a tool call omp has already finished.
|
|
76
|
+
const asking = !settled && Boolean(live) && !recorded && (question.multi || !draft);
|
|
60
77
|
|
|
61
78
|
return (
|
|
62
79
|
<div className="rounded-lg border border-ink/10 bg-paper p-2.5 shadow-xs">
|
|
@@ -94,10 +111,10 @@ export function QuestionBlock({
|
|
|
94
111
|
/>
|
|
95
112
|
)}
|
|
96
113
|
|
|
97
|
-
{
|
|
114
|
+
{hasAnswer && (
|
|
98
115
|
<div className="mt-2 flex flex-wrap items-center gap-1.5">
|
|
99
116
|
<CheckCircle2 size={11} className="shrink-0 text-success" />
|
|
100
|
-
{
|
|
117
|
+
{chips.map((part) => (
|
|
101
118
|
<span
|
|
102
119
|
key={part}
|
|
103
120
|
className="rounded-md border border-ink/12 bg-canvas/40 px-2 py-0.5 text-[11.5px] font-medium text-ink select-text"
|
|
@@ -113,14 +130,14 @@ export function QuestionBlock({
|
|
|
113
130
|
</div>
|
|
114
131
|
)}
|
|
115
132
|
|
|
116
|
-
{!asking &&
|
|
133
|
+
{!asking && !hasAnswer && passed && (
|
|
117
134
|
<div className="mt-2 flex items-center gap-1.5 text-[10.5px] text-ink/45">
|
|
118
135
|
<CheckCircle2 size={11} className="shrink-0 text-ink/35" />
|
|
119
136
|
<span>Answered earlier in this run.</span>
|
|
120
137
|
</div>
|
|
121
138
|
)}
|
|
122
139
|
|
|
123
|
-
{!asking &&
|
|
140
|
+
{!asking && !hasAnswer && !passed && (
|
|
124
141
|
<div className="mt-2 space-y-1 border-t border-ink/6 pt-2">
|
|
125
142
|
{question.options.map((option, optionIndex) => (
|
|
126
143
|
<div key={option.label} className="flex items-start gap-2 px-0.5 text-[11.5px] text-ink/45">
|
|
@@ -142,10 +159,20 @@ export function QuestionBlock({
|
|
|
142
159
|
</span>
|
|
143
160
|
</div>
|
|
144
161
|
))}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
162
|
+
{/* "Waiting for the previous answer" is only true while the ask
|
|
163
|
+
is still running: once it settles nothing is being asked, and
|
|
164
|
+
the question was abandoned unanswered. */}
|
|
165
|
+
{settled ? (
|
|
166
|
+
<div className="flex items-center gap-1.5 pt-0.5 text-[10.5px] text-ink/40">
|
|
167
|
+
<Ban size={10} className="shrink-0" />
|
|
168
|
+
<span>Not answered — the ask was cut short.</span>
|
|
169
|
+
</div>
|
|
170
|
+
) : (
|
|
171
|
+
<div className="flex items-center gap-1.5 pt-0.5 text-[10.5px] text-ink/40">
|
|
172
|
+
<Clock size={10} className="shrink-0" />
|
|
173
|
+
<span>Waiting for the previous answer…</span>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
149
176
|
</div>
|
|
150
177
|
)}
|
|
151
178
|
</div>
|
|
@@ -46,7 +46,18 @@ export function AskPanel({ tool }: { tool: ToolCallData }) {
|
|
|
46
46
|
return tool.output ? <FallbackOutput text={tool.output} /> : null;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
// An ask whose tool call failed or was aborted is OVER: omp cancels the live
|
|
50
|
+
// dialog on abort (the client drops it on that `cancel` frame) and the result
|
|
51
|
+
// records no answer, so after the queue empties every unanswered question
|
|
52
|
+
// falls out of the "passed" branch with nothing left to say it was
|
|
53
|
+
// abandoned — the card read "Waiting for the previous answer…" over a run the
|
|
54
|
+
// user had already stopped. Only a terminal status counts: a session reloaded
|
|
55
|
+
// mid-ask reads its still-blocking tool call back from the JSONL as
|
|
56
|
+
// `success` (omp writes no result until the question is answered), and that
|
|
57
|
+
// question must stay answerable.
|
|
58
|
+
const settled =
|
|
59
|
+
tool.status === 'error' || tool.status === 'aborted' || tool.status === 'skipped';
|
|
60
|
+
const waiting = !settled && groups.some((frames, index) => frames.length > 0 && !recorded[index]);
|
|
50
61
|
// omp asks in order and blocks on each, so the earliest question holding a
|
|
51
62
|
// dialog is the only open one: everything before it is answered, whether or
|
|
52
63
|
// not this client still has the text.
|
|
@@ -71,6 +82,7 @@ export function AskPanel({ tool }: { tool: ToolCallData }) {
|
|
|
71
82
|
recorded={recorded[index] ?? null}
|
|
72
83
|
draft={drafts[index]}
|
|
73
84
|
passed={askingIndex !== -1 && index < askingIndex}
|
|
85
|
+
settled={settled}
|
|
74
86
|
multiSelected={multiSelected[index] ?? NO_LABELS}
|
|
75
87
|
onAnswer={(value) => commit(index, value)}
|
|
76
88
|
onToggle={(label) => toggleMulti(index, label)}
|
package/src/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index.tsx
CHANGED
|
@@ -35,12 +35,21 @@ export function ExtensionDialog({ request, onRespond }: ExtensionDialogProps) {
|
|
|
35
35
|
const options = request.options ?? [];
|
|
36
36
|
const optionDetails = request.optionDetails ?? [];
|
|
37
37
|
|
|
38
|
+
// Reset for a NEW dialog only. `request` is the identity to depend on —
|
|
39
|
+
// `options`/`optionDetails` above are derived arrays, re-created on every
|
|
40
|
+
// render, so listing one as a dependency re-ran this effect after every
|
|
41
|
+
// commit and threw away whatever had just been typed: an editor/input frame
|
|
42
|
+
// carries no `options` at all, so the reset fired on every keystroke and the
|
|
43
|
+
// dialog could not be typed into.
|
|
38
44
|
useEffect(() => {
|
|
45
|
+
const items = request.options ?? [];
|
|
39
46
|
setValue(request.method === 'editor' ? request.prefill ?? '' : '');
|
|
40
|
-
setSelectedOption(
|
|
47
|
+
setSelectedOption(items.length > 0 ? items[0] : null);
|
|
41
48
|
setCustomValue('');
|
|
42
|
-
}, [request
|
|
49
|
+
}, [request]);
|
|
43
50
|
|
|
51
|
+
// Focus once per dialog, for the same reason: a fresh dependency on every
|
|
52
|
+
// render also pulls the caret back into the field the user just left.
|
|
44
53
|
useEffect(() => {
|
|
45
54
|
panelRef.current?.focus();
|
|
46
55
|
if (request.method === 'input') {
|
|
@@ -50,12 +59,12 @@ export function ExtensionDialog({ request, onRespond }: ExtensionDialogProps) {
|
|
|
50
59
|
} else if (request.method === 'select') {
|
|
51
60
|
// When the AI offers an "Other"-style option, the intended interaction
|
|
52
61
|
// is typing a custom answer — focus the input right away.
|
|
53
|
-
const hasOther = options.some((option) => /^other\b/i.test(option.trim()));
|
|
62
|
+
const hasOther = (request.options ?? []).some((option) => /^other\b/i.test(option.trim()));
|
|
54
63
|
if (hasOther) {
|
|
55
64
|
customInputRef.current?.focus();
|
|
56
65
|
}
|
|
57
66
|
}
|
|
58
|
-
}, [request
|
|
67
|
+
}, [request]);
|
|
59
68
|
|
|
60
69
|
const cancel = useCallback(() => {
|
|
61
70
|
onRespond(request, { cancelled: true });
|
|
@@ -44,7 +44,10 @@ export function SplitView({ rows, language }: SplitViewProps) {
|
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<div className="w-full h-full overflow-auto font-mono text-xs select-text bg-paper text-ink code-surface">
|
|
47
|
-
|
|
47
|
+
{/* The room under the last row belongs to the content, not the scroller:
|
|
48
|
+
a scroll container's own bottom padding is not part of its scrollable
|
|
49
|
+
overflow, so the padding has to sit inside it to have any effect. */}
|
|
50
|
+
<div className="w-max min-w-full min-w-[700px] pb-4">
|
|
48
51
|
<div className="grid grid-cols-2 sticky top-0 z-10 bg-canvas border-b border-ink/10 text-[11px] text-ink/70 font-sans select-none shadow-xs">
|
|
49
52
|
<div className="px-3 py-1 font-medium border-r border-ink/10 flex items-center justify-between">
|
|
50
53
|
<span>Original (HEAD / Base)</span>
|