@spzxg/cli-ui 0.6.7 → 0.6.9
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/CHANGELOG.md +15 -1
- package/README.md +14 -4
- package/dist/{cli-ui-embed-CB7ibAtF.js → cli-ui-embed-CtZDwP8y.js} +15517 -15389
- package/dist/{cli-ui-index-BmwOLWFI.js → cli-ui-index-D9uwe5kv.js} +2 -2
- package/dist/{cli-ui-jszip.min-Cq0WjNYb.js → cli-ui-jszip.min-BkjzNeOa.js} +2 -2
- package/dist/{cli-ui-jszip.min-BKNOJnhm.js → cli-ui-jszip.min-D-YVhdLU.js} +1 -1
- package/dist/{cli-ui-xlsx-Sl7ypvSe.js → cli-ui-xlsx-c5AN8lBq.js} +1 -1
- package/dist/cli-ui.css +1 -1
- package/dist/cli-ui.js +1 -1
- package/package.json +1 -1
- package/src/chat/CliChat.tsx +35 -5
- package/src/chat/Composer.tsx +7 -7
- package/src/chat/RunControls.tsx +15 -8
- package/src/chat/draft.ts +7 -2
- package/src/chat/useExecutionConfig.ts +93 -0
- package/src/components.tsx +1 -1
- package/src/core/client.ts +21 -4
- package/src/core/config.ts +6 -2
- package/src/core/execution.ts +24 -0
- package/src/core/store.ts +10 -6
- package/src/core/types.ts +2 -0
- package/src/index.ts +1 -0
- package/src/operator/node/Chat.tsx +6 -3
- package/src/operator/node/Composer.tsx +16 -14
- package/src/operator/node/SessionView.tsx +7 -4
- package/src/operator.base.css +21 -19
- package/src/operator.css +21 -19
- package/src/styles.css +16 -15
- package/src/ui/QueuedMessages.tsx +0 -1
|
@@ -18,6 +18,7 @@ import { BranchHistory, useBranchHistory } from "./BranchHistory";
|
|
|
18
18
|
import { BranchNavigator } from "./BranchNavigator";
|
|
19
19
|
import type { Quote } from "./composerDraft";
|
|
20
20
|
import type { RunControlProps } from "./RunControls";
|
|
21
|
+
import type { ResolvedChatConfig } from "../../core/config";
|
|
21
22
|
const QUIET_AFTER_MS = 180_000;
|
|
22
23
|
function RunningIndicator({ agent, startedAt, lastActivityRef }: { agent: string | null; startedAt: number | null; lastActivityRef: React.MutableRefObject<number> }) {
|
|
23
24
|
const [now, setNow] = useState(() => Date.now());
|
|
@@ -68,6 +69,7 @@ export type ChatProps = {
|
|
|
68
69
|
running: boolean; blockedBy?: string[]; transcriptLoading?: boolean;
|
|
69
70
|
older?: number; onLoadOlder?: (beforeApply?: () => void) => Promise<boolean>;
|
|
70
71
|
controls: RunControlProps; configBusy: boolean; active?: boolean;
|
|
72
|
+
composer?: Pick<ResolvedChatConfig["composer"], "show" | "fileUpload">;
|
|
71
73
|
onSelectTask: (id: string) => void; onFork: (text: string, sourceIds: string[], requestId: string) => Promise<boolean>;
|
|
72
74
|
onSend: (text: string) => Promise<boolean>; onStop: () => Promise<boolean>;
|
|
73
75
|
onStart: () => void; onEdit: () => void; onClear: () => void; onSetSendContext: (v: boolean) => void;
|
|
@@ -76,7 +78,7 @@ export type ChatProps = {
|
|
|
76
78
|
};
|
|
77
79
|
export function Chat(props: ChatProps) { return <ChatSession key={props.task.id} {...props} />; }
|
|
78
80
|
function ChatSession({ project, task, tasks, agents, messages, running, transcriptLoading, older=0, onLoadOlder,
|
|
79
|
-
controls, configBusy, active=true, onSelectTask, onFork, onSend, onStop, onClear, onReconnect, onAnswer, onCancelQueued }: ChatProps) {
|
|
81
|
+
controls, configBusy, composer, active=true, onSelectTask, onFork, onSend, onStop, onClear, onReconnect, onAnswer, onCancelQueued }: ChatProps) {
|
|
80
82
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
81
83
|
useChatScrollbar(scrollRef, task.id);
|
|
82
84
|
const [quoteRequest, setQuoteRequest] = useState<Quote|null>(null);
|
|
@@ -439,6 +441,7 @@ function ChatSession({ project, task, tasks, agents, messages, running, transcri
|
|
|
439
441
|
pinned.current=false; setAtBottom(false); flowPositioned.current=true;
|
|
440
442
|
},[active,flow.ready,task.parent_task_id]);
|
|
441
443
|
useEffect(()=>{if(!task.parent_task_id && active) scrollToBottom("auto");},[active,task.id]);
|
|
444
|
+
const queued = <QueuedMessages scopeKey={task.id} messages={messages.filter(message => message.role === "queued")} onCancel={message => stableCancelQueued(message.id)} attachmentUrl={attachment => `/api/tasks/${encodeURIComponent(attachment.taskId)}/uploads/${encodeURIComponent(attachment.name)}`} renderMessage={message => <MessageView m={message} initial={false} hideWho readOnly />} />;
|
|
442
445
|
return (<FileLinkContext.Provider value={fileScope}>
|
|
443
446
|
<>
|
|
444
447
|
<div className="transcript-wrap">
|
|
@@ -508,8 +511,8 @@ function ChatSession({ project, task, tasks, agents, messages, running, transcri
|
|
|
508
511
|
)}
|
|
509
512
|
</div>
|
|
510
513
|
</div>
|
|
511
|
-
<Composer tasks={tasks} onFork={onFork} task={task} agentLabel={agentLabel(agents, task.agent)} disabled={task.started!==1} configBusy={configBusy} running={running} onSend={async text => { const sent = await onSend(text); if (sent && !running) scrollToBottom("auto"); return sent; }} onStop={onStop} onClear={onClear} quoteRequest={quoteRequest} onQuoteHandled={()=>setQuoteRequest(null)} controls={controls}
|
|
512
|
-
|
|
514
|
+
{composer?.show !== false ? <Composer tasks={tasks} onFork={onFork} task={task} agentLabel={agentLabel(agents, task.agent)} disabled={task.started!==1} configBusy={configBusy} running={running} onSend={async text => { const sent = await onSend(text); if (sent && !running) scrollToBottom("auto"); return sent; }} onStop={onStop} onClear={onClear} quoteRequest={quoteRequest} onQuoteHandled={()=>setQuoteRequest(null)} controls={controls}
|
|
515
|
+
fileUpload={composer?.fileUpload} queued={queued} /> : queued}
|
|
513
516
|
</></FileLinkContext.Provider>
|
|
514
517
|
);
|
|
515
518
|
|
|
@@ -21,12 +21,13 @@ import { acknowledgeDraft, DRAFT_EVENT, isTaskSending, setTaskSending, readDraft
|
|
|
21
21
|
import { getOperatorBoardBackend } from "./boardBackend";
|
|
22
22
|
import { OperatorBoardContext, openOperatorBoard } from "./boardInteraction";
|
|
23
23
|
|
|
24
|
-
export function Composer({ task, agentLabel, disabled, running, onSend, onStop, onClear, quoteRequest, onQuoteHandled, controls, configBusy, tasks, onFork, queued }: {
|
|
24
|
+
export function Composer({ task, agentLabel, disabled, running, onSend, onStop, onClear, quoteRequest, onQuoteHandled, controls, configBusy, tasks, onFork, queued, fileUpload = true }: {
|
|
25
25
|
task: TaskRow; agentLabel: string; disabled: boolean; running: boolean;
|
|
26
26
|
tasks: TaskRow[]; onFork: (text: string, sourceIds: string[], requestId: string) => Promise<boolean>;
|
|
27
27
|
onSend: (text: string) => Promise<boolean>; onStop: () => Promise<boolean>; onClear: () => void;
|
|
28
28
|
configBusy: boolean; quoteRequest: Quote | null; onQuoteHandled: () => void; controls: RunControlProps;
|
|
29
29
|
queued?: ReactNode;
|
|
30
|
+
fileUpload?: boolean;
|
|
30
31
|
}) {
|
|
31
32
|
const boardView=useBoardState(task.id);
|
|
32
33
|
const [draft, setDraft] = useState(() => readDraft(task.id));
|
|
@@ -116,7 +117,7 @@ export function Composer({ task, agentLabel, disabled, running, onSend, onStop,
|
|
|
116
117
|
const sharedConfigBusy = useConfigurationBusy(task.id);
|
|
117
118
|
const locked = disabled || sending || configBusy || sharedConfigBusy;
|
|
118
119
|
const startUpload = (key: string, file: File) => {
|
|
119
|
-
if (!backend.uploadFile || locked || uploads.current.has(key)) return;
|
|
120
|
+
if (!fileUpload || !backend.uploadFile || locked || uploads.current.has(key)) return;
|
|
120
121
|
const taskId = task.id;
|
|
121
122
|
const preview = draftRef.current.attachments.find(a => a.key === key)?.preview;
|
|
122
123
|
const controller = new AbortController();
|
|
@@ -143,7 +144,7 @@ export function Composer({ task, agentLabel, disabled, running, onSend, onStop,
|
|
|
143
144
|
});
|
|
144
145
|
};
|
|
145
146
|
const addFiles = (files: File[]) => {
|
|
146
|
-
if (locked) return;
|
|
147
|
+
if (locked || !fileUpload) return;
|
|
147
148
|
setError("");
|
|
148
149
|
if (!backend.uploadFile) { setError("当前服务暂不支持附件上传。"); return; }
|
|
149
150
|
for (const f of files) {
|
|
@@ -195,14 +196,15 @@ export function Composer({ task, agentLabel, disabled, running, onSend, onStop,
|
|
|
195
196
|
const pendingFork=sources.length&&draft.forkRequest?.signature===currentSignature?draft.forkRequest:null;
|
|
196
197
|
const payload=pendingFork?pendingFork.payload??ownPayload:ownPayload&&!isClear?appendBoardContext(ownPayload,boardView,task.id):ownPayload;
|
|
197
198
|
const tooLong = payload.length > MAX_MESSAGE_CHARS;
|
|
198
|
-
const canSend = !!payload && !uploading && !failed && !tooLong && !locked;
|
|
199
|
+
const canSend = !!payload && !uploading && !failed && !tooLong && !locked && !stopping;
|
|
200
|
+
const showStop = running && !sending && (stopping || locked || !payload);
|
|
199
201
|
const clear = () => {
|
|
200
202
|
if (running || locked || task.started !== 1) return;
|
|
201
203
|
onClear(); setSlash(false);
|
|
202
204
|
if (draft.text.trim() === "/clear") update((d) => ({ ...d, text: "" }));
|
|
203
205
|
};
|
|
204
206
|
const submit = async () => {
|
|
205
|
-
if (!canSend || sendingRef.current || isTaskSending(task.id) || isConfigurationBusy(task.id)) return;
|
|
207
|
+
if (!canSend || stoppingRef.current || sendingRef.current || isTaskSending(task.id) || isConfigurationBusy(task.id)) return;
|
|
206
208
|
if (draft.text.trim() === "/clear" && !draft.attachments.length && !draft.quote) { clear(); return; }
|
|
207
209
|
let submittedDraft = draftRef.current;
|
|
208
210
|
let requestId = "";
|
|
@@ -249,7 +251,7 @@ export function Composer({ task, agentLabel, disabled, running, onSend, onStop,
|
|
|
249
251
|
{slash && !running && !locked && "/clear".startsWith(draft.text.trim()) && <div className="slash"><button className="slash-item" onClick={clear}><span className="cmd">/clear</span><span className="cd">保存摘要 · 开启新上下文</span></button></div>}
|
|
250
252
|
{queued}
|
|
251
253
|
<div className={`comp-box${dragging ? " dropping" : ""}`}
|
|
252
|
-
onDragEnter={(e) => { if (hasFileDrag(e)) { e.preventDefault(); if (!locked) { dragDepth.current++; setDragging(true); } } }}
|
|
254
|
+
onDragEnter={(e) => { if (hasFileDrag(e)) { e.preventDefault(); if (fileUpload && !locked) { dragDepth.current++; setDragging(true); } } }}
|
|
253
255
|
onDragOver={(e) => { if (hasFileDrag(e)) e.preventDefault(); }}
|
|
254
256
|
onDragLeave={() => { if (dragDepth.current > 0 && --dragDepth.current === 0) setDragging(false); }}
|
|
255
257
|
onDrop={(e) => { if (!hasFileDrag(e)) return; e.preventDefault(); dragDepth.current = 0; setDragging(false); addFiles(Array.from(e.dataTransfer.files)); }}>
|
|
@@ -258,7 +260,7 @@ export function Composer({ task, agentLabel, disabled, running, onSend, onStop,
|
|
|
258
260
|
{draft.attachments.some(a => a.status !== "ready") && <div className="composer-upload-list">{draft.attachments.filter(a => a.status !== "ready").map(a => <UploadProgress key={a.key} name={a.name}
|
|
259
261
|
status={a.status === "error" ? "error" : a.phase ?? "queued"} bytes={a.bytes} total={a.size} disabled={locked}
|
|
260
262
|
error={`${a.error || "上传失败,请重试。"}${uploadFiles.current.has(a.key) || a.error?.includes("重新选择文件") ? "" : " 请重新选择文件。"}`}
|
|
261
|
-
onCancel={() => removeAtt(a.key)} onRemove={() => removeAtt(a.key)} onRetry={uploadFiles.current.has(a.key) ? () => startUpload(a.key, uploadFiles.current.get(a.key)!) : undefined} />)}</div>}
|
|
263
|
+
onCancel={() => removeAtt(a.key)} onRemove={() => removeAtt(a.key)} onRetry={fileUpload && uploadFiles.current.has(a.key) ? () => startUpload(a.key, uploadFiles.current.get(a.key)!) : undefined} />)}</div>}
|
|
262
264
|
{readyAttachments.length > 0 && <div className="attach-row">{readyAttachments.map((a) => <div key={a.key} className={`attach-chip ${a.kind} ${a.status}`} title={a.name}>
|
|
263
265
|
{a.kind === "image" && a.preview ? <img src={a.preview} alt={a.name} /> : <span className="attach-file">{Icon.clip()} {a.name}</span>}
|
|
264
266
|
<button className="attach-x" disabled={locked} aria-label={`移除 ${a.name}`} onClick={() => removeAtt(a.key)}>×</button>
|
|
@@ -282,16 +284,16 @@ export function Composer({ task, agentLabel, disabled, running, onSend, onStop,
|
|
|
282
284
|
const files = Array.from(e.clipboardData.files);
|
|
283
285
|
if (files.length) { e.preventDefault(); addFiles(files); return; }
|
|
284
286
|
const text = e.clipboardData.getData("text/plain");
|
|
285
|
-
if (text.length > PASTE_ATTACH_THRESHOLD) { e.preventDefault(); addFiles([new File([text], "pasted-text.txt", { type: "text/plain" })]); }
|
|
287
|
+
if (fileUpload && text.length > PASTE_ATTACH_THRESHOLD) { e.preventDefault(); addFiles([new File([text], "pasted-text.txt", { type: "text/plain" })]); }
|
|
286
288
|
}} /></div>
|
|
287
289
|
<div className="comp-toolbar">
|
|
288
|
-
<input ref={fileRef} type="file" accept={FILE_UPLOAD_ACCEPT} multiple hidden onChange={(e) => { addFiles(Array.from(e.target.files ?? [])); e.target.value = ""; }} />
|
|
289
|
-
<div className="comp-control"><button className={`comp-add${addOpen ? " on" : ""}`} disabled={locked} title="附件、派生任务与新上下文" aria-label="添加内容" aria-haspopup="dialog" aria-expanded={addOpen} onClick={(e) => { e.stopPropagation(); setAddOpen(!addOpen); }}>{Icon.plus()}</button>
|
|
290
|
+
{fileUpload && <input ref={fileRef} type="file" accept={FILE_UPLOAD_ACCEPT} multiple hidden onChange={(e) => { addFiles(Array.from(e.target.files ?? [])); e.target.value = ""; }} />}
|
|
291
|
+
<div className="comp-control"><button className={`comp-add${addOpen ? " on" : ""}`} disabled={locked} title={fileUpload ? "附件、派生任务与新上下文" : "派生任务与新上下文"} aria-label="添加内容" aria-haspopup="dialog" aria-expanded={addOpen} onClick={(e) => { e.stopPropagation(); setAddOpen(!addOpen); }}>{Icon.plus()}</button>
|
|
290
292
|
{addOpen && <Popover className="composer-popover composer-add-popover" ariaLabel="添加内容" placement="top" align="start" onClose={() => setAddOpen(false)}>
|
|
291
293
|
<div className="composer-menu-heading">添加内容</div>
|
|
292
|
-
<button className="pop-item composer-option" disabled={locked} onClick={() => { setAddOpen(false); fileRef.current?.click(); }}>
|
|
294
|
+
{fileUpload && <button className="pop-item composer-option" disabled={locked} onClick={() => { setAddOpen(false); fileRef.current?.click(); }}>
|
|
293
295
|
<span className="composer-option-icon">{Icon.clip()}</span><span className="composer-option-copy"><span className="composer-option-title">添加附件</span><span className="composer-option-sub">HTML、Markdown 最多 100 MiB,其他文件最多 10 MiB</span></span>
|
|
294
|
-
</button>
|
|
296
|
+
</button>}
|
|
295
297
|
<button className="pop-item composer-option" disabled={locked} onClick={openMention}>
|
|
296
298
|
<span className="composer-option-icon">{Icon.git()}</span><span className="composer-option-copy"><span className="composer-option-title">从已完成任务派生</span><span className="composer-option-sub">带上已完成的任务,在新分支继续工作</span></span><span className="composer-option-trailing composer-menu-key">@</span>
|
|
297
299
|
</button>
|
|
@@ -302,8 +304,8 @@ export function Composer({ task, agentLabel, disabled, running, onSend, onStop,
|
|
|
302
304
|
</div>
|
|
303
305
|
<RunControls {...controls} disabled={controls.disabled || sending} />
|
|
304
306
|
<div className="send-group">
|
|
305
|
-
{
|
|
306
|
-
|
|
307
|
+
{showStop ? <button type="button" className="send stop" disabled={stopping} aria-busy={stopping} aria-label={stopping ? "正在停止" : "停止当前轮次"} title={stopping ? "正在停止…" : "停止当前轮次"} onClick={() => void stop()}>{stopping ? <span className="spinner" /> : Icon.stop()}</button>
|
|
308
|
+
: <button type="button" className={`send${running ? " queue" : ""}`} disabled={!canSend} aria-busy={sending} aria-label={sending ? "正在发送" : sources.length ? `派生 ${sources.length} 个分支` : running ? "排队发送" : "发送消息"} title={sending ? "发送中…" : running ? "本轮结束后发送" : "发送消息(Enter)"} onClick={() => void submit()}>{sending ? <span className="spinner" /> : Icon.send()}</button>}
|
|
307
309
|
</div>
|
|
308
310
|
</div>
|
|
309
311
|
</div>
|
|
@@ -30,6 +30,7 @@ import { ColResize } from "./Layout";
|
|
|
30
30
|
import { WorkspaceTabs } from "../../ui/WorkspaceTabs";
|
|
31
31
|
import { WorkspaceDrawer, useWorkspaceDrawer } from "../../ui/WorkspaceDrawer";
|
|
32
32
|
import { jget, downloadExport } from "./api";
|
|
33
|
+
import { resolveChatConfig, type CliChatConfig } from "../../core/config";
|
|
33
34
|
|
|
34
35
|
// Non-blocking banner shown when a reopened task's worktree is behind its base
|
|
35
36
|
// branch. Computed (read-only) on open; the actual git op fires only when the user
|
|
@@ -92,8 +93,9 @@ function SyncBanner({ taskId, running, pinned, onResolveWithAI, onSwitchToChat }
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
export function SessionView(props: Parameters<typeof SessionViewInner>[0]) { return <SessionViewInner key={props.task.id} {...props}/>; }
|
|
95
|
-
function SessionViewInner({ project, task, tasks, onSelectTask, onFork, agents, messages, running, blockedBy, transcriptLoading, older=0, onLoadOlder, onSend, onStart, onStop, onClear, clearConfirming, onConfirmClear, onCancelClear, onEdit, onReconnect, onSetStatus, onSetPriority, onSetModel, onSetAgent, onSetReasoning, onSetPermission, onSetSendContext, onResolveWithAI, onMerged, onPrCreated, onAnswer, onCancelQueued, onBack, mobile, railW, onRailWidth, onRailReset, railCollapsed, onRailCollapse, onRailExpand, board }: {
|
|
96
|
+
function SessionViewInner({ project, task, tasks, onSelectTask, onFork, agents, messages, running, blockedBy, transcriptLoading, older=0, onLoadOlder, onSend, onStart, onStop, onClear, clearConfirming, onConfirmClear, onCancelClear, onEdit, onReconnect, onSetStatus, onSetPriority, onSetModel, onSetAgent, onSetReasoning, onSetPermission, onSetSendContext, onResolveWithAI, onMerged, onPrCreated, onAnswer, onCancelQueued, onBack, mobile, railW, onRailWidth, onRailReset, railCollapsed, onRailCollapse, onRailExpand, board, composer }: {
|
|
96
97
|
board?: OperatorBoardOptions;
|
|
98
|
+
composer?: CliChatConfig["composer"];
|
|
97
99
|
tasks: TaskRow[]; onSelectTask: (id: string) => void; onFork: (text: string, sourceIds: string[], requestId: string) => Promise<boolean>;
|
|
98
100
|
project: ProjectRow; task: TaskRow; agents: AgentsBundle; messages: Msg[]; running: boolean; blockedBy?: string[]; transcriptLoading?: boolean;
|
|
99
101
|
older?: number; onLoadOlder?: (beforeApply?: () => void) => Promise<boolean>;
|
|
@@ -181,7 +183,8 @@ function SessionViewInner({ project, task, tasks, onSelectTask, onFork, agents,
|
|
|
181
183
|
// Run-control pickers + feature gates come from this task's agent capabilities,
|
|
182
184
|
// never a hardcoded list — so the options always match the agent it runs under.
|
|
183
185
|
const caps = capsFor(agents, task.agent);
|
|
184
|
-
const
|
|
186
|
+
const composerConfig = resolveChatConfig({ composer }).composer;
|
|
187
|
+
const controls = { task, caps, agents:agents.agents.map(a=>({id:a.id,label:a.label,disabled:!a.authenticated||a.installed===false})), onSetAgent, defaults: findAgent(agents, task.agent)?.defaults, disabled: running, onSetModel, onSetReasoning, onSetPermission, showAgent: composerConfig.agentSelector, showPermission: composerConfig.executionMode };
|
|
185
188
|
// Usage chip: tokens split into fresh work vs re-read cache (the raw total is
|
|
186
189
|
// mostly cache reads and wildly overstates what ran), and a dollar figure whose
|
|
187
190
|
// presentation follows how this agent is signed in — a subscription login's
|
|
@@ -259,7 +262,7 @@ function SessionViewInner({ project, task, tasks, onSelectTask, onFork, agents,
|
|
|
259
262
|
)}
|
|
260
263
|
|
|
261
264
|
{!hasSession ? (
|
|
262
|
-
|
|
265
|
+
<>{composerConfig.show && <div className="hero-controls"><RunControls {...controls} /></div>}<TaskHero task={task} project={project} onStart={onStart} onEdit={onEdit} onSetSendContext={onSetSendContext} running={running || configBusy} blockedBy={blockedBy} /></>
|
|
263
266
|
) : (
|
|
264
267
|
// Keep the chat at the same React position across all widths. Moving
|
|
265
268
|
// it between different parents remounted the composer during resize
|
|
@@ -267,7 +270,7 @@ function SessionViewInner({ project, task, tasks, onSelectTask, onFork, agents,
|
|
|
267
270
|
<div className={tabMode||drawerMode ? "sess-responsive" : "sess-split"} style={tabMode||drawerMode ? undefined : { gridTemplateColumns: `minmax(0,1fr) 0px ${railW}px` }}>
|
|
268
271
|
<div className={`sess-main${tabMode||drawerMode ? " compact-chat" : ""}`} data-chat-layout={tabMode||drawerMode?"centered":"wide"} hidden={tabMode && view !== "chat"}><Chat project={project} task={task} tasks={tasks} agents={agents} messages={messages}
|
|
269
272
|
running={running} blockedBy={blockedBy} transcriptLoading={transcriptLoading} older={older} onLoadOlder={onLoadOlder}
|
|
270
|
-
controls={controls} configBusy={configBusy} active={!tabMode&&!drawerMode || view === "chat"}
|
|
273
|
+
controls={controls} configBusy={configBusy} composer={composerConfig} active={!tabMode&&!drawerMode || view === "chat"}
|
|
271
274
|
onSelectTask={onSelectTask} onFork={onFork} onSend={onSend} onStop={onStop}
|
|
272
275
|
onStart={onStart} onEdit={onEdit} onClear={onClear} onSetSendContext={onSetSendContext}
|
|
273
276
|
onReconnect={onReconnect} onAnswer={onAnswer} onCancelQueued={onCancelQueued} /></div>
|
package/src/operator.base.css
CHANGED
|
@@ -1021,7 +1021,7 @@ button.tpeek-more:hover{color:var(--accent-ink);}
|
|
|
1021
1021
|
.comp-toolbar{display:flex;align-items:center;gap:7px;padding:4px 12px 12px;min-width:0;}
|
|
1022
1022
|
.comp-add{width:36px;height:36px;display:grid;place-items:center;border-radius:50%;flex:none;color:var(--ink-2);background:transparent;}
|
|
1023
1023
|
.comp-add svg{width:22px;height:22px;}
|
|
1024
|
-
.comp-add:hover,.comp-setting:hover{background:var(--surface-2);}
|
|
1024
|
+
@media(hover:hover) and (pointer:fine){.comp-add:hover,.comp-setting:hover{background:var(--surface-2);}}
|
|
1025
1025
|
.comp-control{position:relative;min-width:0;}
|
|
1026
1026
|
.comp-model{margin-left:auto;}
|
|
1027
1027
|
.comp-setting{display:flex;align-items:center;gap:6px;min-height:36px;max-width:100%;padding:6px 8px;border-radius:10px;color:var(--ink-2);background:transparent;font-size:12px;white-space:nowrap;}
|
|
@@ -1029,14 +1029,19 @@ button.tpeek-more:hover{color:var(--accent-ink);}
|
|
|
1029
1029
|
.comp-setting.elevated{color:var(--amber);}
|
|
1030
1030
|
.comp-model-name{min-width:0;max-width:220px;overflow:hidden;text-overflow:ellipsis;}
|
|
1031
1031
|
.comp-setting:disabled,.comp-add:disabled{opacity:.5;cursor:default;}
|
|
1032
|
-
|
|
1033
|
-
.send
|
|
1034
|
-
.send:disabled{
|
|
1035
|
-
.send
|
|
1036
|
-
.send
|
|
1037
|
-
|
|
1032
|
+
/* A 32px visual circle inside a 44px touch target. */
|
|
1033
|
+
.send{width:44px;height:44px;min-width:44px;min-height:44px;flex:none;padding:6px;border-radius:50%;background:var(--accent);background-clip:content-box;color:var(--on-accent);display:grid;place-items:center;transition:filter .12s,opacity .12s,transform .12s;}
|
|
1034
|
+
.send:hover:not(:disabled){filter:brightness(1.08);}
|
|
1035
|
+
.send:active:not(:disabled){transform:scale(.96);filter:brightness(.94);}
|
|
1036
|
+
.send:disabled{opacity:.35;cursor:not-allowed;filter:none;}
|
|
1037
|
+
.send[aria-busy="true"]{opacity:.7;cursor:progress;}
|
|
1038
|
+
.send.stop{background-color:var(--blue);color:var(--on-accent);}
|
|
1039
|
+
.send.stop svg{fill:currentColor;width:20px;height:20px;}
|
|
1040
|
+
.send .spinner{width:18px;height:18px;border-color:color-mix(in srgb,currentColor 30%,transparent);border-top-color:currentColor;}
|
|
1041
|
+
@media(prefers-reduced-motion:reduce){.send{transition:none;}.send .spinner{animation:none;}}
|
|
1042
|
+
/* Send, queue and stop share one primary action slot. */
|
|
1038
1043
|
.send-group{display:flex;align-items:flex-end;gap:8px;flex:0 0 auto;}
|
|
1039
|
-
.send svg{width:
|
|
1044
|
+
.send svg{width:22px;height:22px;}
|
|
1040
1045
|
.comp-foot{display:flex;align-items:center;gap:10px;padding:9px 14px;border-top:1px solid var(--line-2);background:var(--surface-2);}
|
|
1041
1046
|
.comp-foot .hint{font-family:var(--mono);font-size:10px;color:var(--ink-3);display:flex;align-items:center;gap:5px;}
|
|
1042
1047
|
.comp-foot .hint svg{width:13px;height:13px;}
|
|
@@ -1053,7 +1058,6 @@ button.tpeek-more:hover{color:var(--accent-ink);}
|
|
|
1053
1058
|
@container (max-width:420px){.comp-foot .hint .kbd{display:none;}.run-sw{max-width:130px;}}
|
|
1054
1059
|
|
|
1055
1060
|
.send-group{display:flex;align-items:center;gap:6px;flex:none;}
|
|
1056
|
-
.send svg{width:19px;height:19px;}
|
|
1057
1061
|
.comp-error,.control-error{padding:8px 10px;font-size:12px;line-height:1.5;color:var(--red);overflow-wrap:anywhere;}
|
|
1058
1062
|
.comp-upload-status{padding:6px 10px;font-size:11px;color:var(--ink-3);}
|
|
1059
1063
|
.comp-quote{margin:12px 14px 0;}
|
|
@@ -1411,8 +1415,8 @@ button.pop-item:hover{background:var(--surface-2);}
|
|
|
1411
1415
|
.comp-add.on svg{transform:rotate(45deg);}
|
|
1412
1416
|
.composer .comp-setting{min-height:38px;border-radius:6px;padding:7px 10px;background:transparent;}
|
|
1413
1417
|
.composer .comp-add{width:38px;height:38px;border-radius:6px;background:transparent;}
|
|
1414
|
-
.composer .comp-setting:hover:not(:disabled),.composer .comp-add:hover:not(:disabled){background:var(--surface-2);}
|
|
1415
|
-
.composer .send{width:
|
|
1418
|
+
@media(hover:hover) and (pointer:fine){.composer .comp-setting:hover:not(:disabled),.composer .comp-add:hover:not(:disabled){background:var(--surface-2);}}
|
|
1419
|
+
.composer .send{width:44px;height:44px;}
|
|
1416
1420
|
.composer .comp-box{border-radius:26px;border-color:var(--line);box-shadow:0 4px 20px rgba(0,0,0,.035);}
|
|
1417
1421
|
.composer .comp-box:focus-within{border-color:var(--line-strong);box-shadow:0 6px 24px rgba(0,0,0,.055);}
|
|
1418
1422
|
.composer .comp-toolbar{gap:6px;padding:6px 12px 12px;}
|
|
@@ -1434,12 +1438,11 @@ button.pop-item:hover{background:var(--surface-2);}
|
|
|
1434
1438
|
.composer .comp-toolbar{flex-wrap:nowrap;padding-left:8px;padding-right:8px;}
|
|
1435
1439
|
.composer .comp-toolbar>.comp-control{flex:0 0 auto;}
|
|
1436
1440
|
.composer .comp-toolbar>.comp-model{flex:0 1 auto;min-width:44px;margin-left:auto;}
|
|
1437
|
-
.composer .comp-setting{font-size:
|
|
1438
|
-
.composer .comp-setting>svg{
|
|
1441
|
+
.composer .comp-setting{font-size:12px;gap:4px;padding-left:4px;padding-right:4px;}
|
|
1442
|
+
.composer .comp-setting>svg{flex:none;}
|
|
1439
1443
|
.composer .comp-model-name{max-width:42px;}
|
|
1440
|
-
.composer .
|
|
1441
|
-
.composer .comp-
|
|
1442
|
-
.composer .comp-toolbar:not(:has(.send.stop)) .comp-model .comp-setting>svg{display:block;width:12px;height:12px;}
|
|
1444
|
+
.composer .permission>svg:first-child{width:14px;height:14px;}
|
|
1445
|
+
.composer .comp-model .comp-setting>svg{width:12px;height:12px;}
|
|
1443
1446
|
.composer .comp-foot{flex-wrap:wrap;gap:6px;}
|
|
1444
1447
|
.composer .comp-foot>.send-group{margin-left:auto;}
|
|
1445
1448
|
}
|
|
@@ -1478,7 +1481,6 @@ button.pop-item:hover{background:var(--surface-2);}
|
|
|
1478
1481
|
/* generic separators / utility */
|
|
1479
1482
|
.divider{height:1px;background:var(--line);margin:6px 0;}
|
|
1480
1483
|
|
|
1481
|
-
.send svg{width:17px;height:17px;}
|
|
1482
1484
|
.sug-btn svg,.btn-sm svg{width:13px;height:13px;}
|
|
1483
1485
|
|
|
1484
1486
|
/* status label (opt-in via .slabel consumers; hidden by default) */
|
|
@@ -1549,7 +1551,7 @@ button.pop-item:hover{background:var(--surface-2);}
|
|
|
1549
1551
|
the home indicator. The keyboard hints are mouse-era noise on a phone. */
|
|
1550
1552
|
.app.mobile .composer{padding:10px 12px calc(12px + env(safe-area-inset-bottom));}
|
|
1551
1553
|
.app.mobile .comp-area textarea{font-size:16px;}
|
|
1552
|
-
.app.mobile .send{width:
|
|
1554
|
+
.app.mobile .send{width:44px;height:44px;}
|
|
1553
1555
|
|
|
1554
1556
|
/* Ask card options are tap targets — give them room. */
|
|
1555
1557
|
.app.mobile .ask-opt{padding:10px 13px;}
|
|
@@ -1838,7 +1840,7 @@ button.pop-item:hover{background:var(--surface-2);}
|
|
|
1838
1840
|
[data-chat-layout]>.composer>.composer-inner{width:100%;max-width:var(--cli-chat-content-max);margin-inline:auto;}
|
|
1839
1841
|
[data-chat-layout]>.cli-refs,[data-chat-layout]>.cli-queue{width:calc(100% - 2 * var(--cli-chat-gutter) - var(--cli-chat-scrollbar,0px));max-width:var(--cli-chat-content-max);margin-inline:auto;margin-inline-start:max(var(--cli-chat-gutter),calc((100% - var(--cli-chat-scrollbar,0px) - var(--cli-chat-content-max)) / 2));flex-shrink:0;}
|
|
1840
1842
|
@container chat-column (max-width:900px){[data-chat-layout]>*{--cli-chat-content-max:100%;}}
|
|
1841
|
-
@container chat-column (max-width:640px){[data-chat-layout]>*{--cli-chat-gutter:12px;}}
|
|
1843
|
+
@container chat-column (max-width:640px){[data-chat-layout]>*{--cli-chat-gutter:12px;}.session [data-chat-layout]>.composer{padding-inline-end:var(--cli-chat-gutter);}}
|
|
1842
1844
|
.task-origin{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--ink-3);padding-top:7px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
1843
1845
|
.fork-sources{display:flex;flex-wrap:wrap;gap:6px;padding:12px 14px 0;}
|
|
1844
1846
|
.fork-sources>span{max-width:100%;display:flex;align-items:center;gap:6px;padding:6px 8px;border:1px solid var(--line);background:var(--surface-2);border-radius:8px;font-size:11px;color:var(--ink-2);overflow-wrap:anywhere;}
|
package/src/operator.css
CHANGED
|
@@ -1021,7 +1021,7 @@
|
|
|
1021
1021
|
.cli-ui .comp-toolbar{display:flex;align-items:center;gap:7px;padding:4px 12px 12px;min-width:0;}
|
|
1022
1022
|
.cli-ui .comp-add{width:36px;height:36px;display:grid;place-items:center;border-radius:50%;flex:none;color:var(--ink-2);background:transparent;}
|
|
1023
1023
|
.cli-ui .comp-add svg{width:22px;height:22px;}
|
|
1024
|
-
.cli-ui .comp-add:hover,.cli-ui .comp-setting:hover{background:var(--surface-2);}
|
|
1024
|
+
@media(hover:hover) and (pointer:fine){.cli-ui .comp-add:hover,.cli-ui .comp-setting:hover{background:var(--surface-2);}}
|
|
1025
1025
|
.cli-ui .comp-control{position:relative;min-width:0;}
|
|
1026
1026
|
.cli-ui .comp-model{margin-left:auto;}
|
|
1027
1027
|
.cli-ui .comp-setting{display:flex;align-items:center;gap:6px;min-height:36px;max-width:100%;padding:6px 8px;border-radius:10px;color:var(--ink-2);background:transparent;font-size:12px;white-space:nowrap;}
|
|
@@ -1029,14 +1029,19 @@
|
|
|
1029
1029
|
.cli-ui .comp-setting.elevated{color:var(--amber);}
|
|
1030
1030
|
.cli-ui .comp-model-name{min-width:0;max-width:220px;overflow:hidden;text-overflow:ellipsis;}
|
|
1031
1031
|
.cli-ui .comp-setting:disabled,.cli-ui .comp-add:disabled{opacity:.5;cursor:default;}
|
|
1032
|
-
|
|
1033
|
-
.cli-ui .send
|
|
1034
|
-
.cli-ui .send:disabled{
|
|
1035
|
-
.cli-ui .send
|
|
1036
|
-
.cli-ui .send
|
|
1037
|
-
|
|
1032
|
+
/* A 32px visual circle inside a 44px touch target. */
|
|
1033
|
+
.cli-ui .send{width:44px;height:44px;min-width:44px;min-height:44px;flex:none;padding:6px;border-radius:50%;background:var(--accent);background-clip:content-box;color:var(--on-accent);display:grid;place-items:center;transition:filter .12s,opacity .12s,transform .12s;}
|
|
1034
|
+
.cli-ui .send:hover:not(:disabled){filter:brightness(1.08);}
|
|
1035
|
+
.cli-ui .send:active:not(:disabled){transform:scale(.96);filter:brightness(.94);}
|
|
1036
|
+
.cli-ui .send:disabled{opacity:.35;cursor:not-allowed;filter:none;}
|
|
1037
|
+
.cli-ui .send[aria-busy="true"]{opacity:.7;cursor:progress;}
|
|
1038
|
+
.cli-ui .send.stop{background-color:var(--blue);color:var(--on-accent);}
|
|
1039
|
+
.cli-ui .send.stop svg{fill:currentColor;width:20px;height:20px;}
|
|
1040
|
+
.cli-ui .send .spinner{width:18px;height:18px;border-color:color-mix(in srgb,currentColor 30%,transparent);border-top-color:currentColor;}
|
|
1041
|
+
@media(prefers-reduced-motion:reduce){.cli-ui .send{transition:none;}.cli-ui .send .spinner{animation:none;}}
|
|
1042
|
+
/* Send, queue and stop share one primary action slot. */
|
|
1038
1043
|
.cli-ui .send-group{display:flex;align-items:flex-end;gap:8px;flex:0 0 auto;}
|
|
1039
|
-
.cli-ui .send svg{width:
|
|
1044
|
+
.cli-ui .send svg{width:22px;height:22px;}
|
|
1040
1045
|
.cli-ui .comp-foot{display:flex;align-items:center;gap:10px;padding:9px 14px;border-top:1px solid var(--line-2);background:var(--surface-2);}
|
|
1041
1046
|
.cli-ui .comp-foot .hint{font-family:var(--mono);font-size:10px;color:var(--ink-3);display:flex;align-items:center;gap:5px;}
|
|
1042
1047
|
.cli-ui .comp-foot .hint svg{width:13px;height:13px;}
|
|
@@ -1053,7 +1058,6 @@
|
|
|
1053
1058
|
@container (max-width:420px){.cli-ui .comp-foot .hint .kbd{display:none;}.cli-ui .run-sw{max-width:130px;}}
|
|
1054
1059
|
|
|
1055
1060
|
.cli-ui .send-group{display:flex;align-items:center;gap:6px;flex:none;}
|
|
1056
|
-
.cli-ui .send svg{width:19px;height:19px;}
|
|
1057
1061
|
.cli-ui .comp-error,.cli-ui .control-error{padding:8px 10px;font-size:12px;line-height:1.5;color:var(--red);overflow-wrap:anywhere;}
|
|
1058
1062
|
.cli-ui .comp-upload-status{padding:6px 10px;font-size:11px;color:var(--ink-3);}
|
|
1059
1063
|
.cli-ui .comp-quote{margin:12px 14px 0;}
|
|
@@ -1411,8 +1415,8 @@
|
|
|
1411
1415
|
.cli-ui .comp-add.on svg{transform:rotate(45deg);}
|
|
1412
1416
|
.cli-ui .composer .comp-setting{min-height:38px;border-radius:6px;padding:7px 10px;background:transparent;}
|
|
1413
1417
|
.cli-ui .composer .comp-add{width:38px;height:38px;border-radius:6px;background:transparent;}
|
|
1414
|
-
.cli-ui .composer .comp-setting:hover:not(:disabled),.cli-ui .composer .comp-add:hover:not(:disabled){background:var(--surface-2);}
|
|
1415
|
-
.cli-ui .composer .send{width:
|
|
1418
|
+
@media(hover:hover) and (pointer:fine){.cli-ui .composer .comp-setting:hover:not(:disabled),.cli-ui .composer .comp-add:hover:not(:disabled){background:var(--surface-2);}}
|
|
1419
|
+
.cli-ui .composer .send{width:44px;height:44px;}
|
|
1416
1420
|
.cli-ui .composer .comp-box{border-radius:26px;border-color:var(--line);box-shadow:0 4px 20px rgba(0,0,0,.035);}
|
|
1417
1421
|
.cli-ui .composer .comp-box:focus-within{border-color:var(--line-strong);box-shadow:0 6px 24px rgba(0,0,0,.055);}
|
|
1418
1422
|
.cli-ui .composer .comp-toolbar{gap:6px;padding:6px 12px 12px;}
|
|
@@ -1434,12 +1438,11 @@
|
|
|
1434
1438
|
.cli-ui .composer .comp-toolbar{flex-wrap:nowrap;padding-left:8px;padding-right:8px;}
|
|
1435
1439
|
.cli-ui .composer .comp-toolbar>.comp-control{flex:0 0 auto;}
|
|
1436
1440
|
.cli-ui .composer .comp-toolbar>.comp-model{flex:0 1 auto;min-width:44px;margin-left:auto;}
|
|
1437
|
-
.cli-ui .composer .comp-setting{font-size:
|
|
1438
|
-
.cli-ui .composer .comp-setting>svg{
|
|
1441
|
+
.cli-ui .composer .comp-setting{font-size:12px;gap:4px;padding-left:4px;padding-right:4px;}
|
|
1442
|
+
.cli-ui .composer .comp-setting>svg{flex:none;}
|
|
1439
1443
|
.cli-ui .composer .comp-model-name{max-width:42px;}
|
|
1440
|
-
.cli-ui .composer .
|
|
1441
|
-
.cli-ui .composer .comp-
|
|
1442
|
-
.cli-ui .composer .comp-toolbar:not(:has(.send.stop)) .comp-model .comp-setting>svg{display:block;width:12px;height:12px;}
|
|
1444
|
+
.cli-ui .composer .permission>svg:first-child{width:14px;height:14px;}
|
|
1445
|
+
.cli-ui .composer .comp-model .comp-setting>svg{width:12px;height:12px;}
|
|
1443
1446
|
.cli-ui .composer .comp-foot{flex-wrap:wrap;gap:6px;}
|
|
1444
1447
|
.cli-ui .composer .comp-foot>.send-group{margin-left:auto;}
|
|
1445
1448
|
}
|
|
@@ -1478,7 +1481,6 @@
|
|
|
1478
1481
|
/* generic separators / utility */
|
|
1479
1482
|
.cli-ui .divider{height:1px;background:var(--line);margin:6px 0;}
|
|
1480
1483
|
|
|
1481
|
-
.cli-ui .send svg{width:17px;height:17px;}
|
|
1482
1484
|
.cli-ui .sug-btn svg,.cli-ui .btn-sm svg{width:13px;height:13px;}
|
|
1483
1485
|
|
|
1484
1486
|
/* status label (opt-in via .slabel consumers; hidden by default) */
|
|
@@ -1549,7 +1551,7 @@
|
|
|
1549
1551
|
the home indicator. The keyboard hints are mouse-era noise on a phone. */
|
|
1550
1552
|
.cli-ui .app.mobile .composer{padding:10px 12px calc(12px + env(safe-area-inset-bottom));}
|
|
1551
1553
|
.cli-ui .app.mobile .comp-area textarea{font-size:16px;}
|
|
1552
|
-
.cli-ui .app.mobile .send{width:
|
|
1554
|
+
.cli-ui .app.mobile .send{width:44px;height:44px;}
|
|
1553
1555
|
|
|
1554
1556
|
/* Ask card options are tap targets — give them room. */
|
|
1555
1557
|
.cli-ui .app.mobile .ask-opt{padding:10px 13px;}
|
|
@@ -1838,7 +1840,7 @@
|
|
|
1838
1840
|
.cli-ui [data-chat-layout]>.composer>.composer-inner{width:100%;max-width:var(--cli-chat-content-max);margin-inline:auto;}
|
|
1839
1841
|
.cli-ui [data-chat-layout]>.cli-refs,.cli-ui [data-chat-layout]>.cli-queue{width:calc(100% - 2 * var(--cli-chat-gutter) - var(--cli-chat-scrollbar,0px));max-width:var(--cli-chat-content-max);margin-inline:auto;margin-inline-start:max(var(--cli-chat-gutter),calc((100% - var(--cli-chat-scrollbar,0px) - var(--cli-chat-content-max)) / 2));flex-shrink:0;}
|
|
1840
1842
|
@container chat-column (max-width:900px){.cli-ui [data-chat-layout]>*{--cli-chat-content-max:100%;}}
|
|
1841
|
-
@container chat-column (max-width:640px){.cli-ui [data-chat-layout]>*{--cli-chat-gutter:12px;}}
|
|
1843
|
+
@container chat-column (max-width:640px){.cli-ui [data-chat-layout]>*{--cli-chat-gutter:12px;}.cli-ui .session [data-chat-layout]>.composer{padding-inline-end:var(--cli-chat-gutter);}}
|
|
1842
1844
|
.cli-ui .task-origin{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--ink-3);padding-top:7px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
1843
1845
|
.cli-ui .fork-sources{display:flex;flex-wrap:wrap;gap:6px;padding:12px 14px 0;}
|
|
1844
1846
|
.cli-ui .fork-sources>span{max-width:100%;display:flex;align-items:center;gap:6px;padding:6px 8px;border:1px solid var(--line);background:var(--surface-2);border-radius:8px;font-size:11px;color:var(--ink-2);overflow-wrap:anywhere;}
|
package/src/styles.css
CHANGED
|
@@ -54,12 +54,12 @@
|
|
|
54
54
|
.cli-ui .cli-export{white-space:nowrap;}
|
|
55
55
|
.cli-ui .comp-area textarea{min-width:0;}
|
|
56
56
|
.cli-ui .comp-foot{gap:8px;padding:8px 10px;background:transparent;border-top:0;flex-wrap:wrap;}
|
|
57
|
-
.cli-ui .
|
|
58
|
-
.cli-ui .cli-attach
|
|
59
|
-
.cli-ui .cli-attach
|
|
60
|
-
.cli-ui .
|
|
61
|
-
.cli-ui .
|
|
62
|
-
.cli-ui .cli-export:focus-visible,.cli-ui .cli-attach:focus-visible
|
|
57
|
+
.cli-ui .comp-foot>.comp-control{flex:0 0 auto;}
|
|
58
|
+
.cli-ui .cli-attach{display:inline-flex;justify-content:center;align-items:center;gap:5px;min-width:44px;min-height:44px;padding:0 8px;border-radius:7px;color:var(--ink-3);font-size:12px;}
|
|
59
|
+
@media(hover:hover) and (pointer:fine){.cli-ui .cli-attach:hover{background:var(--surface-2);color:var(--ink);}}
|
|
60
|
+
.cli-ui .cli-attach svg{width:22px;height:22px;}
|
|
61
|
+
.cli-ui .comp-foot:has(.comp-model)>.spacer{display:none;}
|
|
62
|
+
.cli-ui .cli-export:focus-visible,.cli-ui .cli-attach:focus-visible{outline:2px solid var(--accent);outline-offset:3px;}
|
|
63
63
|
.cli-ui .cli-task-control{height:34px;padding:0 10px;white-space:nowrap;}
|
|
64
64
|
.cli-ui .cli-task-control:focus-visible{outline:2px solid var(--accent);outline-offset:3px;}
|
|
65
65
|
@container cli-chat (max-width:560px){
|
|
@@ -455,19 +455,20 @@ cli-split{display:block;}
|
|
|
455
455
|
.cli-ui .cli-auto-background pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:220px;overflow:auto;}
|
|
456
456
|
|
|
457
457
|
/* The live queue sits just above the composer; expanded content stays in a viewport-bound card. */
|
|
458
|
-
.cli-ui .cli-queue{flex:none;min-width:0;max-height:154px;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;margin:0
|
|
459
|
-
.cli-ui .cli-queue-row{display:flex;align-items:center;gap:8px;min-width:0;min-height:
|
|
458
|
+
.cli-ui .cli-queue{flex:none;min-width:0;max-height:154px;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;margin:0 16px -12px;padding:4px 10px 16px;border:1px solid var(--line);border-radius:18px 18px 0 0;background:var(--raise);}
|
|
459
|
+
.cli-ui .cli-queue-row{display:flex;align-items:center;gap:8px;min-width:0;min-height:44px;}
|
|
460
460
|
.cli-ui .cli-queue-row+.cli-queue-row{border-top:1px solid var(--line-2);}
|
|
461
461
|
.cli-ui .cli-queue-icon{display:grid;place-items:center;flex:none;width:20px;color:var(--ink-4);}
|
|
462
462
|
.cli-ui .cli-queue-icon svg{width:16px;height:16px;}
|
|
463
463
|
.cli-ui .cli-queue-summary{display:flex;align-items:center;gap:8px;flex:1;min-width:0;min-height:40px;padding:4px 0;text-align:left;color:var(--ink-2);}
|
|
464
464
|
.cli-ui .cli-queue-thumb{flex:none;width:32px;height:32px;object-fit:cover;border:1px solid var(--line);border-radius:7px;}
|
|
465
465
|
.cli-ui .cli-queue-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:1.5;}
|
|
466
|
-
.cli-ui .cli-queue-state{flex:none;color:var(--ink-4);font-size:10px;white-space:nowrap;}
|
|
467
466
|
.cli-ui .cli-queue-remove,.cli-ui .cli-queue-more{display:grid;place-items:center;flex:none;width:30px;height:34px;border-radius:9px;color:var(--ink-3);}
|
|
468
467
|
.cli-ui .cli-queue-remove svg,.cli-ui .cli-queue-more svg{width:17px;height:17px;}
|
|
469
|
-
|
|
470
|
-
.cli-ui .cli-queue-remove:hover{color:var(--
|
|
468
|
+
@media(hover:hover) and (pointer:fine){
|
|
469
|
+
.cli-ui .cli-queue-remove:hover,.cli-ui .cli-queue-more:hover{background:var(--surface-2);color:var(--ink);}
|
|
470
|
+
.cli-ui .cli-queue-remove:hover{color:var(--red);}
|
|
471
|
+
}
|
|
471
472
|
.cli-ui .cli-queue-summary:focus-visible,.cli-ui .cli-queue-remove:focus-visible,.cli-ui .cli-queue-more:focus-visible{outline:2px solid var(--accent);outline-offset:1px;border-radius:8px;}
|
|
472
473
|
.cli-ui .cli-queue-popover{width:420px;}
|
|
473
474
|
.cli-ui .cli-queue-detail{max-height:min(44vh,320px);overflow:auto;overscroll-behavior:contain;overflow-wrap:anywhere;}
|
|
@@ -480,11 +481,10 @@ cli-split{display:block;}
|
|
|
480
481
|
.cli-ui .cli-queue-feedback[role="alert"]{color:var(--red);}
|
|
481
482
|
.cli-ui .session>.cli-queue{width:calc(100% - 48px);max-width:800px;align-self:center;margin:8px 24px 16px;border-radius:16px;}
|
|
482
483
|
@container(max-width:420px){
|
|
483
|
-
.cli-ui .cli-queue{margin:0
|
|
484
|
+
.cli-ui .cli-queue{margin:0 10px -12px;padding:4px 6px 16px;max-height:112px;}
|
|
484
485
|
.cli-ui .cli-queue-row{gap:5px;}
|
|
485
|
-
.cli-ui .cli-queue-state{display:none;}
|
|
486
486
|
}
|
|
487
|
-
@media(pointer:coarse){.cli-ui .cli-queue-remove,.cli-ui .cli-queue-more{width:
|
|
487
|
+
@media(pointer:coarse){.cli-ui .cli-queue-remove,.cli-ui .cli-queue-more{width:44px;height:44px;}}
|
|
488
488
|
@media(max-height:500px){.cli-ui .cli-queue{max-height:70px;}}
|
|
489
489
|
|
|
490
490
|
/* Keep controls reachable when a keyboard, split host or landscape view leaves little height. */
|
|
@@ -495,7 +495,8 @@ cli-split{display:block;}
|
|
|
495
495
|
.cli-ui .comp-area textarea,.cli-ui .ask-other,.cli-ui .bp-addr,.cli-ui .bp-dialog-in{font-size:16px;}
|
|
496
496
|
.cli-ui .rail-ftab-x{width:32px;height:36px;opacity:1;}
|
|
497
497
|
.cli-ui .cli-ref-x{width:32px;height:32px;}
|
|
498
|
-
.cli-ui .cli-fv-btn,.cli-ui .cli-fv-back,.cli-ui .cli-fv-icon,.cli-ui .bp-nav
|
|
498
|
+
.cli-ui .cli-fv-btn,.cli-ui .cli-fv-back,.cli-ui .cli-fv-icon,.cli-ui .bp-nav{min-height:36px;min-width:36px;}
|
|
499
|
+
.cli-ui .cli-attach{min-height:44px;min-width:44px;}
|
|
499
500
|
.cli-ui .rail-collapse{min-width:36px;min-height:36px;}
|
|
500
501
|
.cli-ui .cli-crumb{min-height:36px;}
|
|
501
502
|
.cli-ui .cli-fv-selq{min-height:40px;}
|
|
@@ -55,7 +55,6 @@ function QueuedMessage({ message, index, onCancel, attachmentUrl, renderMessage
|
|
|
55
55
|
{thumbnail?.url && <img className="cli-queue-thumb" src={thumbnail.url} alt={thumbnail.attachment.name} loading="lazy" onError={event => { event.currentTarget.hidden = true; }} />}
|
|
56
56
|
<span className="cli-queue-text">{preview.summary}</span>
|
|
57
57
|
</button>
|
|
58
|
-
<span className="cli-queue-state">排队中</span>
|
|
59
58
|
{onCancel && <button className="cli-queue-remove" title="从队列中移除" aria-label={`移除第 ${index + 1} 条排队消息`} onClick={() => onCancel(message)}>{Icon.x()}</button>}
|
|
60
59
|
<button className="cli-queue-more" title="更多排队消息操作" aria-label={`第 ${index + 1} 条排队消息的更多操作`} aria-expanded={open} aria-haspopup="dialog" onClick={toggle}>{Icon.dots()}</button>
|
|
61
60
|
{open && <Popover className="composer-popover cli-queue-popover" ariaLabel="排队消息内容" placement="top" align="start" returnFocusRef={returnFocusRef} onClose={() => setOpen(false)}>
|