ompchamber 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.html +1 -1
- package/dist/client/static/css/index.5c0447cefe.css +2 -0
- package/dist/client/static/js/async/{3y.4c2596f64c.js → 3y.71f2ccf3b6.js} +2 -2
- package/dist/client/static/js/async/{xw.ae67ba06fd.js → 5b.0d6ae50fe8.js} +10 -10
- package/dist/client/static/js/async/5bh.8b2363424f.js +1 -0
- package/dist/client/static/js/async/{vy.bbe114c4e0.js → bi.09688ba75c.js} +1 -2
- package/dist/client/static/js/async/c.797d933882.js +1 -0
- package/dist/client/static/js/async/{dc.6dc4b0a04b.js → dc.856fda6560.js} +2 -2
- package/dist/client/static/js/async/{g.86e2256a70.js → g.d1c2fe09aa.js} +2 -2
- package/dist/client/static/js/async/g.d1c2fe09aa.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{m.4b29d34f93.js → m.0896dab823.js} +2 -2
- package/dist/client/static/js/async/ma.2c261b04dd.js +2 -0
- package/dist/client/static/js/async/nam.b5663e919f.js +10 -0
- package/dist/client/static/js/async/nam.b5663e919f.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{q.542c184fec.js → q.23af2a358f.js} +3 -3
- package/dist/client/static/js/async/{s.413c2a4683.js → s.222ee974f6.js} +2 -2
- package/dist/client/static/js/async/t.4dca0e2ba8.js +1 -0
- package/dist/client/static/js/async/uf.46ad6209fa.js +2 -0
- package/dist/client/static/js/async/x4.69ec69c61b.js +6 -0
- package/dist/client/static/js/async/xn.6360379f71.js +2 -0
- package/dist/client/static/js/async/xnl.feb0021e50.js +2 -0
- package/dist/client/static/js/index.dc5f5fabe8.js +33 -0
- package/dist/client/static/js/{uy.2e8929ed0c.js → jp.50817896f3.js} +6 -6
- package/package.json +1 -1
- package/src/client/components/common/MarkdownRenderer.tsx +69 -27
- package/src/client/components/common/code-editor/history.test.ts +118 -0
- package/src/client/components/common/code-editor/history.ts +101 -0
- package/src/client/components/common/code-editor/index.tsx +56 -66
- package/src/client/components/common/code-surface/index.tsx +84 -11
- package/src/client/components/common/code-surface/measure.ts +66 -17
- package/src/client/components/common/diagram-viewer/index.tsx +258 -0
- package/src/client/components/common/diagram-viewer/read-block.ts +53 -0
- package/src/client/components/layout/session-sidebar/CategoryItem.tsx +1 -0
- package/src/client/components/layout/session-sidebar/SessionItem.tsx +10 -3
- package/src/client/components/settings/LazyModal.tsx +1 -1
- package/src/client/components/settings/Modal.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +8 -1
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +11 -52
- package/src/client/components/workspace/chat-timeline/chat-input/selection.ts +57 -0
- package/src/client/components/workspace/chat-timeline/index.tsx +121 -107
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/FrameControl.tsx +172 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +155 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/drafts.ts +113 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +92 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Footer.tsx +3 -3
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Header.tsx +3 -3
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/Lazy.tsx +8 -8
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/SelectBody.tsx +3 -3
- package/src/client/components/workspace/chat-timeline/tool-renderers/{ask-dialog → extension-dialog}/index.tsx +16 -10
- package/src/client/components/workspace/chat-timeline/tool-renderers/index.tsx +2 -2
- package/src/client/components/workspace/file-explorer/index.tsx +18 -5
- package/src/client/hooks/chat/omp/session-list.tsx +4 -0
- package/src/client/hooks/chat/timeline/actions.ts +0 -9
- package/src/client/hooks/chat/timeline/ask-frames.ts +102 -0
- package/src/client/hooks/chat/timeline/extension-dialog.ts +12 -9
- package/src/client/hooks/chat/timeline/index.ts +6 -8
- package/src/client/hooks/chat/timeline/prompt-text.ts +35 -0
- package/src/client/hooks/chat/timeline/send.ts +13 -27
- package/src/client/hooks/chat/timeline/session-load.ts +48 -106
- package/src/client/hooks/chat/timeline/session-pagination.ts +144 -0
- package/src/client/hooks/editor/use-code-window.ts +118 -0
- package/src/client/hooks/ui/input-required-alert.ts +43 -0
- package/src/client/hooks/ui/notification-sound.ts +65 -0
- package/src/client/hooks/ui/theme.ts +22 -6
- package/src/client/tailwind.css +38 -12
- package/src/server/lib/omp/rpc/manager.ts +14 -3
- package/src/server/lib/omp/rpc/session-registry.ts +18 -0
- package/src/server/lib/omp/session/sidebar-data.server.ts +7 -1
- package/src/shared/lib/chat/ask-questions.test.ts +169 -0
- package/src/shared/lib/chat/ask-questions.ts +264 -0
- package/src/shared/lib/code/lazy-window.test.ts +161 -0
- package/src/shared/lib/code/lazy-window.ts +156 -0
- package/src/shared/lib/code/syntax-highlight.test.ts +50 -1
- package/src/shared/lib/code/syntax-highlight.ts +14 -1
- package/src/shared/lib/code/windowed-highlight.ts +63 -0
- package/src/shared/lib/markdown/mermaid.ts +8 -6
- package/src/shared/types/omp/agent.ts +2 -2
- package/src/shared/types/workspace.ts +4 -0
- package/dist/client/static/css/index.51f68210a5.css +0 -2
- package/dist/client/static/js/async/5b.d88304bc13.js +0 -1
- package/dist/client/static/js/async/c.cd45b0ac45.js +0 -1
- package/dist/client/static/js/async/fw.6a51998a8e.js +0 -6
- package/dist/client/static/js/async/kc.bbdb35b771.js +0 -1
- package/dist/client/static/js/async/mw.ca1512563c.js +0 -10
- package/dist/client/static/js/async/rf.d626afc367.js +0 -2
- package/dist/client/static/js/async/t.cda4891526.js +0 -1
- package/dist/client/static/js/async/uf.24ca3d49c3.js +0 -2
- package/dist/client/static/js/async/v0.394ed37c6b.js +0 -2
- package/dist/client/static/js/async/v0.394ed37c6b.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/vy.bbe114c4e0.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/xn.afecebbbd9.js +0 -2
- package/dist/client/static/js/index.eccad0b70a.js +0 -33
- package/dist/client/static/js/index.eccad0b70a.js.LICENSE.txt +0 -4
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Ask.tsx +0 -205
- /package/dist/client/static/js/async/{3y.4c2596f64c.js.LICENSE.txt → 3y.71f2ccf3b6.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.86e2256a70.js.LICENSE.txt → 5b.0d6ae50fe8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{dc.6dc4b0a04b.js.LICENSE.txt → dc.856fda6560.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{fw.6a51998a8e.js.LICENSE.txt → m.0896dab823.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{m.4b29d34f93.js.LICENSE.txt → ma.2c261b04dd.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{mw.ca1512563c.js.LICENSE.txt → q.23af2a358f.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.542c184fec.js.LICENSE.txt → s.222ee974f6.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{rf.d626afc367.js.LICENSE.txt → uf.46ad6209fa.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{s.413c2a4683.js.LICENSE.txt → x4.69ec69c61b.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{uf.24ca3d49c3.js.LICENSE.txt → xn.6360379f71.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{xn.afecebbbd9.js.LICENSE.txt → xnl.feb0021e50.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{async/xw.ae67ba06fd.js.LICENSE.txt → index.dc5f5fabe8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{uy.2e8929ed0c.js.LICENSE.txt → jp.50817896f3.js.LICENSE.txt} +0 -0
package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One question of an `ask` call, in whichever of its three states it is in:
|
|
8
|
+
* answered (omp already recorded it), asking (its dialog is live) or still
|
|
9
|
+
* queued behind an earlier question — omp asks them strictly in order, so the
|
|
10
|
+
* later ones can only be previewed until their turn comes.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { CheckCircle2, Clock } from 'lucide-preact';
|
|
14
|
+
import { MarkdownRenderer } from '@/client/components/common/MarkdownRenderer';
|
|
15
|
+
import type { ExtensionUiDialogRequest } from '@/shared/types/omp/agent';
|
|
16
|
+
import type { AskAnswer, AskQuestion } from '@/shared/lib/chat/ask-questions';
|
|
17
|
+
import { FrameControl } from '@/client/components/workspace/chat-timeline/tool-renderers/ask-panel/FrameControl';
|
|
18
|
+
import type { ExtensionDialogResponse } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/Lazy';
|
|
19
|
+
|
|
20
|
+
interface QuestionBlockProps {
|
|
21
|
+
index: number;
|
|
22
|
+
total: number;
|
|
23
|
+
question: AskQuestion;
|
|
24
|
+
/** Dialogs this question raised, oldest first; the last one is live. */
|
|
25
|
+
frames: ExtensionUiDialogRequest[];
|
|
26
|
+
recorded: AskAnswer | null;
|
|
27
|
+
/** Answer picked locally for a question omp has not reached yet. */
|
|
28
|
+
draft?: string;
|
|
29
|
+
/** Unanswered, but omp has already moved past it — its dialog is gone. */
|
|
30
|
+
passed: boolean;
|
|
31
|
+
/** Labels toggled locally on a multi-select question. */
|
|
32
|
+
multiSelected: string[];
|
|
33
|
+
onAnswer: (value: string) => void;
|
|
34
|
+
onToggle: (label: string) => void;
|
|
35
|
+
onRespond: (response: ExtensionDialogResponse) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function answerParts(answer: AskAnswer): string[] {
|
|
39
|
+
const parts = [...answer.selectedOptions];
|
|
40
|
+
if (answer.customInput) parts.push(answer.customInput);
|
|
41
|
+
return parts;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function QuestionBlock({
|
|
45
|
+
index,
|
|
46
|
+
total,
|
|
47
|
+
question,
|
|
48
|
+
frames,
|
|
49
|
+
recorded,
|
|
50
|
+
draft,
|
|
51
|
+
passed,
|
|
52
|
+
multiSelected,
|
|
53
|
+
onAnswer,
|
|
54
|
+
onToggle,
|
|
55
|
+
onRespond,
|
|
56
|
+
}: QuestionBlockProps) {
|
|
57
|
+
const live = frames.length > 0 ? frames[frames.length - 1] : null;
|
|
58
|
+
const answered = recorded ? answerParts(recorded) : draft ? [draft] : multiSelected;
|
|
59
|
+
const asking = Boolean(live) && !recorded && (question.multi || !draft);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="rounded-lg border border-ink/10 bg-paper p-2.5 shadow-xs">
|
|
63
|
+
<div className="flex items-start gap-2">
|
|
64
|
+
<span className="mt-px flex h-4 w-4 shrink-0 items-center justify-center rounded bg-ink/5 font-mono text-[9px] font-semibold text-ink/60">
|
|
65
|
+
{index + 1}
|
|
66
|
+
</span>
|
|
67
|
+
<div className="min-w-0 flex-1">
|
|
68
|
+
<div className="flex items-center gap-1.5">
|
|
69
|
+
<span className="text-[9px] font-semibold uppercase tracking-wider text-ink/40">
|
|
70
|
+
{question.header ?? `Question ${index + 1}`}
|
|
71
|
+
</span>
|
|
72
|
+
{question.multi && (
|
|
73
|
+
<span className="rounded-full bg-ink/5 px-1.5 py-px font-mono text-[8.5px] text-ink/45">multi</span>
|
|
74
|
+
)}
|
|
75
|
+
{total > 1 && (
|
|
76
|
+
<span className="ml-auto font-mono text-[9px] text-ink/35">
|
|
77
|
+
{index + 1}/{total}
|
|
78
|
+
</span>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div className="mt-1 text-[12.5px] leading-relaxed text-ink/90 select-text">
|
|
83
|
+
<MarkdownRenderer content={question.question} />
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{asking && live && (
|
|
87
|
+
<FrameControl
|
|
88
|
+
frame={live}
|
|
89
|
+
multi={question.multi}
|
|
90
|
+
selected={multiSelected}
|
|
91
|
+
onAnswer={onAnswer}
|
|
92
|
+
onToggle={onToggle}
|
|
93
|
+
onRespond={onRespond}
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{answered.length > 0 && (
|
|
98
|
+
<div className="mt-2 flex flex-wrap items-center gap-1.5">
|
|
99
|
+
<CheckCircle2 size={11} className="shrink-0 text-success" />
|
|
100
|
+
{answered.map((part) => (
|
|
101
|
+
<span
|
|
102
|
+
key={part}
|
|
103
|
+
className="rounded-md border border-ink/12 bg-canvas/40 px-2 py-0.5 text-[11.5px] font-medium text-ink select-text"
|
|
104
|
+
>
|
|
105
|
+
{part}
|
|
106
|
+
</span>
|
|
107
|
+
))}
|
|
108
|
+
{recorded?.timedOut && (
|
|
109
|
+
<span className="rounded-full bg-ink/5 px-1.5 py-px font-mono text-[9px] text-ink/45">
|
|
110
|
+
auto-selected on timeout
|
|
111
|
+
</span>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{!asking && answered.length === 0 && passed && (
|
|
117
|
+
<div className="mt-2 flex items-center gap-1.5 text-[10.5px] text-ink/45">
|
|
118
|
+
<CheckCircle2 size={11} className="shrink-0 text-ink/35" />
|
|
119
|
+
<span>Answered earlier in this run.</span>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
{!asking && answered.length === 0 && !passed && (
|
|
124
|
+
<div className="mt-2 space-y-1 border-t border-ink/6 pt-2">
|
|
125
|
+
{question.options.map((option, optionIndex) => (
|
|
126
|
+
<div key={option.label} className="flex items-start gap-2 px-0.5 text-[11.5px] text-ink/45">
|
|
127
|
+
<span className="mt-1 h-3 w-3 shrink-0 rounded-full border border-ink/20" />
|
|
128
|
+
<span className="min-w-0 flex-1">
|
|
129
|
+
<span className="block truncate">
|
|
130
|
+
{option.label}
|
|
131
|
+
{question.recommended === optionIndex && (
|
|
132
|
+
<span className="ml-1.5 font-mono text-[9px] uppercase tracking-wider text-ink/35">
|
|
133
|
+
suggested
|
|
134
|
+
</span>
|
|
135
|
+
)}
|
|
136
|
+
</span>
|
|
137
|
+
{option.description && (
|
|
138
|
+
<span className="mt-0.5 block text-[10.5px] leading-relaxed text-ink/35">
|
|
139
|
+
{option.description}
|
|
140
|
+
</span>
|
|
141
|
+
)}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
))}
|
|
145
|
+
<div className="flex items-center gap-1.5 pt-0.5 text-[10.5px] text-ink/40">
|
|
146
|
+
<Clock size={10} className="shrink-0" />
|
|
147
|
+
<span>Waiting for the previous answer…</span>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Answer bookkeeping for the inline ask card.
|
|
8
|
+
*
|
|
9
|
+
* omp asks a multi-question `ask` one question at a time and blocks on each, so
|
|
10
|
+
* only the question currently in flight owns a dialog. The card still lets the
|
|
11
|
+
* user answer any of them, in any order: a choice made for a question omp has
|
|
12
|
+
* not reached yet is held here and released the moment its dialog arrives. Every
|
|
13
|
+
* sent dialog is remembered by id, so a re-render (or a replayed request after a
|
|
14
|
+
* page reload) can never answer the same frame twice.
|
|
15
|
+
*
|
|
16
|
+
* Multi-select questions are excluded from that queue: omp re-asks them once per
|
|
17
|
+
* toggle and only the LAST one settles the question, so each click is sent
|
|
18
|
+
* immediately and only the display state accumulates here.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
22
|
+
import type { ExtensionUiDialogRequest } from '@/shared/types/omp/agent';
|
|
23
|
+
import type { AskAnswer, AskQuestion } from '@/shared/lib/chat/ask-questions';
|
|
24
|
+
import type { ExtensionDialogResponse } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/Lazy';
|
|
25
|
+
|
|
26
|
+
interface UseAskDraftsOptions {
|
|
27
|
+
toolId: string;
|
|
28
|
+
questions: AskQuestion[];
|
|
29
|
+
/** Dialogs per question, oldest first — the last one is the live dialog. */
|
|
30
|
+
groups: ExtensionUiDialogRequest[][];
|
|
31
|
+
/** Answers omp already recorded in the tool result, per question. */
|
|
32
|
+
recorded: (AskAnswer | null)[];
|
|
33
|
+
respond: (request: ExtensionUiDialogRequest, response: ExtensionDialogResponse) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AskDrafts {
|
|
37
|
+
/** Committed single-choice answers, by question index. */
|
|
38
|
+
drafts: Record<number, string>;
|
|
39
|
+
/** Labels the user has toggled on a multi-select question, for display only. */
|
|
40
|
+
multiSelected: Record<number, string[]>;
|
|
41
|
+
commit: (index: number, value: string) => void;
|
|
42
|
+
toggleMulti: (index: number, label: string) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The live dialog for a question: the most recent frame it raised. */
|
|
46
|
+
export function liveFrame(groups: ExtensionUiDialogRequest[][], index: number): ExtensionUiDialogRequest | null {
|
|
47
|
+
const frames = groups[index];
|
|
48
|
+
return frames && frames.length > 0 ? frames[frames.length - 1] : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// omp records answers only in the FINAL tool result, so a reload mid-ask would
|
|
52
|
+
// otherwise repaint every question already answered as still waiting. The draft
|
|
53
|
+
// cache keeps the text across a reload; it is per-tab and dies with the session.
|
|
54
|
+
function draftKey(toolId: string): string {
|
|
55
|
+
return `ompchamber.ask-draft:${toolId}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function loadDrafts(toolId: string): Record<number, string> {
|
|
59
|
+
try {
|
|
60
|
+
const raw = sessionStorage.getItem(draftKey(toolId));
|
|
61
|
+
return raw ? (JSON.parse(raw) as Record<number, string>) : {};
|
|
62
|
+
} catch {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function useAskDrafts({ toolId, questions, groups, recorded, respond }: UseAskDraftsOptions): AskDrafts {
|
|
68
|
+
const [drafts, setDrafts] = useState<Record<number, string>>(() => loadDrafts(toolId));
|
|
69
|
+
const [multiSelected, setMultiSelected] = useState<Record<number, string[]>>({});
|
|
70
|
+
const sentRef = useRef<Set<string>>(new Set());
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
for (let index = 0; index < questions.length; index += 1) {
|
|
74
|
+
if (recorded[index] || questions[index].multi) continue;
|
|
75
|
+
const value = drafts[index];
|
|
76
|
+
if (!value) continue;
|
|
77
|
+
const frame = liveFrame(groups, index);
|
|
78
|
+
if (!frame || frame.method !== 'select' || sentRef.current.has(frame.id)) continue;
|
|
79
|
+
sentRef.current.add(frame.id);
|
|
80
|
+
respond(frame, { value });
|
|
81
|
+
}
|
|
82
|
+
}, [questions, groups, drafts, recorded, respond]);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
try {
|
|
86
|
+
sessionStorage.setItem(draftKey(toolId), JSON.stringify(drafts));
|
|
87
|
+
} catch {
|
|
88
|
+
// Storage disabled (private mode): the card still works for this mount.
|
|
89
|
+
}
|
|
90
|
+
}, [toolId, drafts]);
|
|
91
|
+
|
|
92
|
+
const commit = useCallback((index: number, value: string) => {
|
|
93
|
+
setDrafts((current) => (current[index] === value ? current : { ...current, [index]: value }));
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const toggleMulti = useCallback((index: number, label: string) => {
|
|
97
|
+
const frame = liveFrame(groups, index);
|
|
98
|
+
if (!frame || sentRef.current.has(frame.id)) return;
|
|
99
|
+
// One omp dialog per toggle: answering it re-asks the question with the
|
|
100
|
+
// updated set, so the next click lands on the dialog that just arrived.
|
|
101
|
+
sentRef.current.add(frame.id);
|
|
102
|
+
respond(frame, { value: label });
|
|
103
|
+
setMultiSelected((current) => {
|
|
104
|
+
const selected = current[index] ?? [];
|
|
105
|
+
return {
|
|
106
|
+
...current,
|
|
107
|
+
[index]: selected.includes(label) ? selected.filter((item) => item !== label) : [...selected, label],
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}, [groups, respond]);
|
|
111
|
+
|
|
112
|
+
return { drafts, multiSelected, commit, toggleMulti };
|
|
113
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Inline card for omp's `ask` tool — the whole conversation with the user, in
|
|
8
|
+
* the timeline instead of a modal.
|
|
9
|
+
*
|
|
10
|
+
* omp asks one question per dialog and blocks on it, so a modal could only ever
|
|
11
|
+
* show the question in flight and lost every earlier one on the way; the card
|
|
12
|
+
* keeps all of them in view, marks the ones already answered, and ends as the
|
|
13
|
+
* summary of what the agent asked and what it was told.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { HelpCircle, XCircle } from 'lucide-preact';
|
|
17
|
+
import type { ToolCallData } from '@/shared/types';
|
|
18
|
+
import type { ExtensionUiDialogRequest } from '@/shared/types/omp/agent';
|
|
19
|
+
import { parseAskQuestions, parseAskResult } from '@/shared/lib/chat/ask-questions';
|
|
20
|
+
import { useAskFrames } from '@/client/hooks/chat/timeline/ask-frames';
|
|
21
|
+
import { liveFrame, useAskDrafts } from '@/client/components/workspace/chat-timeline/tool-renderers/ask-panel/drafts';
|
|
22
|
+
import { QuestionBlock } from '@/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock';
|
|
23
|
+
import { FallbackOutput } from '@/client/components/workspace/chat-timeline/tool-renderers/shared/FallbackOutput';
|
|
24
|
+
|
|
25
|
+
// Stable empties: fresh `[]` literals would defeat the question block's props.
|
|
26
|
+
const NO_FRAMES: ExtensionUiDialogRequest[] = [];
|
|
27
|
+
const NO_GROUPS: ExtensionUiDialogRequest[][] = [];
|
|
28
|
+
const NO_LABELS: string[] = [];
|
|
29
|
+
|
|
30
|
+
export function AskPanel({ tool }: { tool: ToolCallData }) {
|
|
31
|
+
const questions = parseAskQuestions(tool);
|
|
32
|
+
const recorded = parseAskResult(tool, questions);
|
|
33
|
+
const { framesByTool, respond } = useAskFrames();
|
|
34
|
+
const groups = framesByTool.get(tool.id) ?? NO_GROUPS;
|
|
35
|
+
const { drafts, multiSelected, commit, toggleMulti } = useAskDrafts({
|
|
36
|
+
toolId: tool.id,
|
|
37
|
+
questions,
|
|
38
|
+
groups,
|
|
39
|
+
recorded,
|
|
40
|
+
respond,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// An `ask` whose arguments the timeline never saw (or a non-questions shape)
|
|
44
|
+
// still has a result worth showing.
|
|
45
|
+
if (questions.length === 0) {
|
|
46
|
+
return tool.output ? <FallbackOutput text={tool.output} /> : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const waiting = groups.some((frames, index) => frames.length > 0 && !recorded[index]);
|
|
50
|
+
// omp asks in order and blocks on each, so the earliest question holding a
|
|
51
|
+
// dialog is the only open one: everything before it is answered, whether or
|
|
52
|
+
// not this client still has the text.
|
|
53
|
+
const askingIndex = groups.findIndex((frames) => frames.length > 0);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="space-y-2">
|
|
57
|
+
{waiting && (
|
|
58
|
+
<div className="flex items-center gap-1.5 text-[10.5px] text-ink/50">
|
|
59
|
+
<HelpCircle size={11} className="shrink-0" />
|
|
60
|
+
<span>Input needed — the agent is blocked until you answer.</span>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{questions.map((question, index) => (
|
|
65
|
+
<QuestionBlock
|
|
66
|
+
key={question.id ?? index}
|
|
67
|
+
index={index}
|
|
68
|
+
total={questions.length}
|
|
69
|
+
question={question}
|
|
70
|
+
frames={groups[index] ?? NO_FRAMES}
|
|
71
|
+
recorded={recorded[index] ?? null}
|
|
72
|
+
draft={drafts[index]}
|
|
73
|
+
passed={askingIndex !== -1 && index < askingIndex}
|
|
74
|
+
multiSelected={multiSelected[index] ?? NO_LABELS}
|
|
75
|
+
onAnswer={(value) => commit(index, value)}
|
|
76
|
+
onToggle={(label) => toggleMulti(index, label)}
|
|
77
|
+
onRespond={(response) => {
|
|
78
|
+
const frame = liveFrame(groups, index);
|
|
79
|
+
if (frame) respond(frame, response);
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
|
|
84
|
+
{(tool.status === 'error' || tool.status === 'aborted') && tool.output && (
|
|
85
|
+
<div className="flex items-start gap-2 rounded-md border border-ink/8 bg-ink/[0.02] px-2.5 py-1.5 text-[10.5px] text-ink/60">
|
|
86
|
+
<XCircle size={11} className="mt-0.5 shrink-0 text-ink/40" />
|
|
87
|
+
<span className="font-mono select-text">{tool.output}</span>
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CornerDownLeft, X } from 'lucide-preact';
|
|
2
2
|
|
|
3
|
-
interface
|
|
3
|
+
interface ExtensionDialogFooterProps {
|
|
4
4
|
method: string;
|
|
5
5
|
optionsLength: number;
|
|
6
6
|
selectedOption: string | null;
|
|
@@ -9,14 +9,14 @@ interface AskDialogFooterProps {
|
|
|
9
9
|
onSubmit: () => void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function
|
|
12
|
+
export function ExtensionDialogFooter({
|
|
13
13
|
method,
|
|
14
14
|
optionsLength,
|
|
15
15
|
selectedOption,
|
|
16
16
|
hasCustomAnswer = false,
|
|
17
17
|
onCancel,
|
|
18
18
|
onSubmit,
|
|
19
|
-
}:
|
|
19
|
+
}: ExtensionDialogFooterProps) {
|
|
20
20
|
const isSelectDisabled = method === 'select' && !selectedOption && !hasCustomAnswer;
|
|
21
21
|
|
|
22
22
|
return (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'preact/compat';
|
|
2
2
|
import { Sparkles, X } from 'lucide-preact';
|
|
3
3
|
|
|
4
|
-
interface
|
|
4
|
+
interface ExtensionDialogHeaderProps {
|
|
5
5
|
title?: string;
|
|
6
6
|
methodLabel: string;
|
|
7
7
|
badge: string;
|
|
@@ -9,13 +9,13 @@ interface AskDialogHeaderProps {
|
|
|
9
9
|
onCancel: () => void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function
|
|
12
|
+
export function ExtensionDialogHeader({
|
|
13
13
|
title,
|
|
14
14
|
methodLabel,
|
|
15
15
|
badge,
|
|
16
16
|
icon,
|
|
17
17
|
onCancel,
|
|
18
|
-
}:
|
|
18
|
+
}: ExtensionDialogHeaderProps) {
|
|
19
19
|
return (
|
|
20
20
|
<div className="flex items-center justify-between border-b border-ink/8 bg-canvas/40 px-5 py-3.5">
|
|
21
21
|
<div className="flex items-center gap-3 min-w-0">
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { Suspense, lazy } from 'preact/compat';
|
|
2
2
|
import type { ExtensionUiDialogRequest } from '@/client/hooks/chat/omp';
|
|
3
|
-
import type { ExtensionDialogResponse } from '@/client/components/workspace/chat-timeline/tool-renderers/
|
|
3
|
+
import type { ExtensionDialogResponse } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
import('@/client/components/workspace/chat-timeline/tool-renderers/
|
|
7
|
-
default: m.
|
|
5
|
+
const ExtensionDialogImpl = lazy(() =>
|
|
6
|
+
import('@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index').then((m) => ({
|
|
7
|
+
default: m.ExtensionDialog,
|
|
8
8
|
}))
|
|
9
9
|
);
|
|
10
10
|
|
|
11
|
-
interface
|
|
11
|
+
interface ExtensionDialogProps {
|
|
12
12
|
request: ExtensionUiDialogRequest;
|
|
13
13
|
onRespond: (request: ExtensionUiDialogRequest, response: ExtensionDialogResponse) => void;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function
|
|
16
|
+
export function ExtensionDialog(props: ExtensionDialogProps) {
|
|
17
17
|
return (
|
|
18
18
|
<Suspense fallback={null}>
|
|
19
|
-
<
|
|
19
|
+
<ExtensionDialogImpl {...props} />
|
|
20
20
|
</Suspense>
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export type { ExtensionDialogResponse } from '@/client/components/workspace/chat-timeline/tool-renderers/
|
|
24
|
+
export type { ExtensionDialogResponse } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RefObject } from 'preact/compat';
|
|
2
2
|
import { CheckCircle2, Circle } from 'lucide-preact';
|
|
3
3
|
|
|
4
|
-
interface
|
|
4
|
+
interface ExtensionDialogSelectBodyProps {
|
|
5
5
|
options: string[];
|
|
6
6
|
optionDetails: { description?: string }[];
|
|
7
7
|
selectedOption: string | null;
|
|
@@ -12,7 +12,7 @@ interface AskDialogSelectBodyProps {
|
|
|
12
12
|
onConfirmOption: (option: string) => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function
|
|
15
|
+
export function ExtensionDialogSelectBody({
|
|
16
16
|
options,
|
|
17
17
|
optionDetails,
|
|
18
18
|
selectedOption,
|
|
@@ -21,7 +21,7 @@ export function AskDialogSelectBody({
|
|
|
21
21
|
customInputRef,
|
|
22
22
|
onSelect,
|
|
23
23
|
onConfirmOption,
|
|
24
|
-
}:
|
|
24
|
+
}: ExtensionDialogSelectBodyProps) {
|
|
25
25
|
return (
|
|
26
26
|
<div className="space-y-2">
|
|
27
27
|
<div className="flex items-center justify-between text-[11px] font-medium tracking-wider text-ink/50 uppercase">
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
3
3
|
import { AlertCircle, CheckCircle2, FileText, HelpCircle, ListFilter, Terminal } from 'lucide-preact';
|
|
4
4
|
import type { ExtensionUiDialogRequest } from '@/client/hooks/chat/omp';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { ExtensionDialogHeader } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/Header';
|
|
6
|
+
import { ExtensionDialogSelectBody } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/SelectBody';
|
|
7
|
+
import { ExtensionDialogFooter } from '@/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/Footer';
|
|
8
8
|
import { MarkdownRenderer } from '@/client/components/common/MarkdownRenderer';
|
|
9
9
|
|
|
10
10
|
export type ExtensionDialogResponse =
|
|
@@ -12,12 +12,18 @@ export type ExtensionDialogResponse =
|
|
|
12
12
|
| { confirmed: boolean }
|
|
13
13
|
| { cancelled: true };
|
|
14
14
|
|
|
15
|
-
interface
|
|
15
|
+
interface ExtensionDialogProps {
|
|
16
16
|
request: ExtensionUiDialogRequest;
|
|
17
17
|
onRespond: (request: ExtensionUiDialogRequest, response: ExtensionDialogResponse) => void;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Blocking extension dialog that has no card of its own to render in: an
|
|
22
|
+
* approval gate under `always-ask`, or a picker an extension raised. The `ask`
|
|
23
|
+
* tool's own dialogs render inline on its tool card instead (see ask-panel) —
|
|
24
|
+
* a modal would only ever show the question currently in flight.
|
|
25
|
+
*/
|
|
26
|
+
export function ExtensionDialog({ request, onRespond }: ExtensionDialogProps) {
|
|
21
27
|
const [value, setValue] = useState(request.method === 'editor' ? request.prefill ?? '' : '');
|
|
22
28
|
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
|
23
29
|
const [customValue, setCustomValue] = useState('');
|
|
@@ -202,7 +208,7 @@ export function AskDialog({ request, onRespond }: AskDialogProps) {
|
|
|
202
208
|
|
|
203
209
|
return (
|
|
204
210
|
<div
|
|
205
|
-
className="
|
|
211
|
+
className="extension-dialog-backdrop fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6"
|
|
206
212
|
style={{
|
|
207
213
|
background: 'color-mix(in srgb, var(--theme-ink) 55%, transparent)',
|
|
208
214
|
backdropFilter: 'blur(8px)',
|
|
@@ -217,12 +223,12 @@ export function AskDialog({ request, onRespond }: AskDialogProps) {
|
|
|
217
223
|
aria-modal="true"
|
|
218
224
|
aria-label={request.title || methodMeta.label}
|
|
219
225
|
tabIndex={-1}
|
|
220
|
-
className="
|
|
226
|
+
className="extension-dialog-panel w-full max-w-[560px] overflow-hidden rounded-2xl border border-ink/15 bg-paper shadow-2xl outline-none"
|
|
221
227
|
style={{
|
|
222
228
|
boxShadow: '0 24px 64px -8px color-mix(in srgb, var(--theme-ink) 28%, transparent)',
|
|
223
229
|
}}
|
|
224
230
|
>
|
|
225
|
-
<
|
|
231
|
+
<ExtensionDialogHeader
|
|
226
232
|
title={request.title}
|
|
227
233
|
methodLabel={methodMeta.label}
|
|
228
234
|
badge={methodMeta.badge}
|
|
@@ -240,7 +246,7 @@ export function AskDialog({ request, onRespond }: AskDialogProps) {
|
|
|
240
246
|
|
|
241
247
|
{/* Select Options */}
|
|
242
248
|
{request.method === 'select' && (
|
|
243
|
-
<
|
|
249
|
+
<ExtensionDialogSelectBody
|
|
244
250
|
options={options}
|
|
245
251
|
optionDetails={optionDetails}
|
|
246
252
|
selectedOption={selectedOption}
|
|
@@ -307,7 +313,7 @@ export function AskDialog({ request, onRespond }: AskDialogProps) {
|
|
|
307
313
|
)}
|
|
308
314
|
</div>
|
|
309
315
|
|
|
310
|
-
<
|
|
316
|
+
<ExtensionDialogFooter
|
|
311
317
|
method={request.method}
|
|
312
318
|
optionsLength={options.length}
|
|
313
319
|
selectedOption={selectedOption}
|
|
@@ -19,7 +19,7 @@ import { AstEdit } from '@/client/components/workspace/chat-timeline/tool-render
|
|
|
19
19
|
import { Resolve } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/Resolve';
|
|
20
20
|
import { ManageSkill } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/ManageSkill';
|
|
21
21
|
import { SearchFs } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/SearchFs';
|
|
22
|
-
import {
|
|
22
|
+
import { AskPanel } from '@/client/components/workspace/chat-timeline/tool-renderers/ask-panel';
|
|
23
23
|
import { Think } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/Think';
|
|
24
24
|
import { Read } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/Read';
|
|
25
25
|
import { Edit } from '@/client/components/workspace/chat-timeline/tool-renderers/panels/Edit';
|
|
@@ -142,7 +142,7 @@ export function ToolDetailsPanel({ tool }: { tool: ToolCallData }): ReactNode {
|
|
|
142
142
|
return <Memory tool={tool} />;
|
|
143
143
|
}
|
|
144
144
|
if (key === 'goal' || key === 'yield') return <Goal tool={tool} />;
|
|
145
|
-
if (key === 'ask') return <
|
|
145
|
+
if (key === 'ask') return <AskPanel tool={tool} />;
|
|
146
146
|
if (key === 'think') return <Think tool={tool} />;
|
|
147
147
|
|
|
148
148
|
// Dynamic MCP / custom tools — `mcp__<tool>` names and xd:// device writes
|
|
@@ -63,15 +63,28 @@ export function FileExplorer({ className = '', enabled = true, rootPath, onOpenF
|
|
|
63
63
|
loadFiles();
|
|
64
64
|
}, [refreshKey, rootPath, enabled, activeRepo, expandedPathsReady]);
|
|
65
65
|
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
66
|
+
// The listing and its git decorations describe the same working tree, so one
|
|
67
|
+
// refresh drives both. Re-reading only the tree left every folder dot stale:
|
|
68
|
+
// `useGitStatus` has no poll of its own in this panel, so a new/changed file
|
|
69
|
+
// appeared under a folder that still claimed a clean status until the user
|
|
70
|
+
// hit refresh or refocused the window.
|
|
71
|
+
const refreshListing = () => {
|
|
72
|
+
loadFiles({ silent: true });
|
|
73
|
+
refreshGitStatus();
|
|
74
|
+
};
|
|
75
|
+
// Auto refresh: re-read on a cadence so external changes (terminal output,
|
|
76
|
+
// agent edits) surface without a manual refresh. Silent — the loading
|
|
77
|
+
// spinner stays reserved for the user's own refresh button.
|
|
78
|
+
usePanelRefresh(refreshListing, enabled && expandedPathsReady);
|
|
70
79
|
// AI edits land between poll ticks: re-read right after a file-mutating tool
|
|
71
80
|
// (edit / write / ast_edit / bash) finishes, not up to 2s later.
|
|
72
|
-
useFileMutationRefresh(
|
|
81
|
+
useFileMutationRefresh(refreshListing, enabled && expandedPathsReady);
|
|
73
82
|
|
|
74
83
|
const loadChildren = (path: string) => {
|
|
84
|
+
// Expanding IS a directory load: fetch the children AND re-read git status,
|
|
85
|
+
// so a freshly listed row never renders bare next to a status map that
|
|
86
|
+
// still predates its change (the panel poll alone can lag a fresh edit).
|
|
87
|
+
refreshGitStatus();
|
|
75
88
|
return fetch(listUrl(path))
|
|
76
89
|
.then(r => r.json())
|
|
77
90
|
.then(data => {
|
|
@@ -21,6 +21,7 @@ import type { ReactNode } from 'preact/compat';
|
|
|
21
21
|
import { useFetcher } from '@/client/lib/router/fetcher';
|
|
22
22
|
import { usePanelRefresh } from '@/client/hooks/workspace/panel-refresh';
|
|
23
23
|
import { SIDEBAR_IDLE_REFRESH_MS } from '@/shared/lib/workspace/refresh-cadence';
|
|
24
|
+
import { useInputRequiredAlert } from '@/client/hooks/ui/input-required-alert';
|
|
24
25
|
import type { WorkspaceFolderData } from '@/shared/types';
|
|
25
26
|
import type { SessionListPayload } from '@/server/lib/omp/session/sidebar-data.server';
|
|
26
27
|
|
|
@@ -87,6 +88,9 @@ export function SidebarDataProvider({ children, initialFolders = [] }: { childre
|
|
|
87
88
|
folder.sessions?.some((session) => session.streamStatus === 'stream'),
|
|
88
89
|
),
|
|
89
90
|
);
|
|
91
|
+
// Fires for ANY session, including one this tab never opened — the cue that an
|
|
92
|
+
// agent is blocked on an answer must not depend on the open timeline.
|
|
93
|
+
useInputRequiredAlert(fetcher.data?.folders ?? []);
|
|
90
94
|
usePanelRefresh(refresh, !hasStreaming, SIDEBAR_IDLE_REFRESH_MS);
|
|
91
95
|
|
|
92
96
|
const markSeen = useCallback((sessionId: number | string) => {
|
|
@@ -49,8 +49,6 @@ export interface ChatTimelineActionsDeps {
|
|
|
49
49
|
/** Global access-control mode, snapshotted onto queued items. */
|
|
50
50
|
accessModeRef: { current: ApprovalMode };
|
|
51
51
|
setSearchParams: (fn: (prev: URLSearchParams) => URLSearchParams, opts?: { replace?: boolean }) => void;
|
|
52
|
-
/** Advance the pending-dialog queue after the head was answered. */
|
|
53
|
-
dismissExtensionDialog: () => void;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
export interface ChatTimelineActionsResult {
|
|
@@ -64,7 +62,6 @@ export interface ChatTimelineActionsResult {
|
|
|
64
62
|
stopGenerating: () => number;
|
|
65
63
|
handleThinkingLevelChange: (level: string) => void;
|
|
66
64
|
handleModelChange: (provider: string, modelId: string) => void;
|
|
67
|
-
closeExtensionDialog: () => void;
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
export function useChatTimelineActions(deps: ChatTimelineActionsDeps): ChatTimelineActionsResult {
|
|
@@ -91,7 +88,6 @@ export function useChatTimelineActions(deps: ChatTimelineActionsDeps): ChatTimel
|
|
|
91
88
|
composerModelRef,
|
|
92
89
|
accessModeRef,
|
|
93
90
|
setSearchParams,
|
|
94
|
-
dismissExtensionDialog,
|
|
95
91
|
} = deps;
|
|
96
92
|
|
|
97
93
|
const handleSend = useCallback(async (attachments: Attachment[], options?: { steering?: boolean }) => {
|
|
@@ -302,10 +298,6 @@ export function useChatTimelineActions(deps: ChatTimelineActionsDeps): ChatTimel
|
|
|
302
298
|
void ompAgent.setModel(provider, modelId);
|
|
303
299
|
}, [isOmpSession, ompAgent, pendingComposerModelRef]);
|
|
304
300
|
|
|
305
|
-
const closeExtensionDialog = useCallback(() => {
|
|
306
|
-
dismissExtensionDialog();
|
|
307
|
-
}, [dismissExtensionDialog]);
|
|
308
|
-
|
|
309
301
|
return {
|
|
310
302
|
handleSend,
|
|
311
303
|
handleEditQueueItem,
|
|
@@ -316,6 +308,5 @@ export function useChatTimelineActions(deps: ChatTimelineActionsDeps): ChatTimel
|
|
|
316
308
|
stopGenerating,
|
|
317
309
|
handleThinkingLevelChange,
|
|
318
310
|
handleModelChange,
|
|
319
|
-
closeExtensionDialog,
|
|
320
311
|
};
|
|
321
312
|
}
|