@steerable/agent-shell-web 0.6.27 → 0.6.29
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/package.json +4 -4
- package/src/components/AgentSidebar.tsx +27 -27
- package/src/components/BrandLockup.tsx +20 -4
- package/src/components/ChatHeader.tsx +5 -15
- package/src/components/LocalLlmSettingsModal.tsx +5 -5
- package/src/components/TerminalPanel.tsx +1 -1
- package/src/components/chat/ApprovalModal.tsx +5 -5
- package/src/components/chat/AskUserQuestionMenu.tsx +13 -13
- package/src/components/chat/AssistantMessage.tsx +122 -36
- package/src/components/chat/ChatInput.tsx +34 -26
- package/src/components/chat/ExecPolicyPicker.tsx +4 -4
- package/src/components/chat/ExecutedActionsCard.tsx +398 -29
- package/src/components/chat/FilePathCode.tsx +2 -1
- package/src/components/chat/LocalChatPanel.tsx +42 -11
- package/src/components/chat/Markdown.tsx +15 -14
- package/src/components/chat/MessageList.tsx +23 -5
- package/src/components/chat/ModelPicker.tsx +6 -6
- package/src/components/chat/OrchestrationChildrenCard.tsx +69 -5
- package/src/components/chat/SessionTodoList.tsx +234 -0
- package/src/components/chat/SessionTreeModal.tsx +3 -3
- package/src/components/chat/SuggestedReplies.tsx +14 -5
- package/src/components/chat/TaskPanelModal.tsx +3 -3
- package/src/components/chat/TaskProcessPanel.tsx +67 -21
- package/src/components/chat/TurnFilesCard.tsx +2 -2
- package/src/components/chat/TurnProcessGroup.tsx +252 -99
- package/src/components/chat/UserMessage.tsx +7 -7
- package/src/components/chat/executed-actions-model.ts +469 -0
- package/src/components/chat/next-steps-tags.ts +28 -0
- package/src/components/chat/orchestration-children-model.ts +120 -0
- package/src/components/chat/process-status.ts +125 -53
- package/src/components/chat/todo-list-model.ts +167 -0
- package/src/components/chat/turn-timeline.ts +123 -11
- package/src/components/settings/AgentsSettingsPanel.tsx +2 -2
- package/src/components/settings/AppearanceSettingsPanel.tsx +3 -3
- package/src/components/settings/DiagnoseSettingsPanel.tsx +2 -2
- package/src/components/settings/InsightsSettingsPanel.tsx +4 -4
- package/src/components/settings/LlmSettingsPanel.tsx +10 -10
- package/src/components/settings/McpSettingsPanel.tsx +3 -3
- package/src/components/settings/ModelIdCombobox.tsx +2 -2
- package/src/components/settings/SecuritySettingsPanel.tsx +1 -1
- package/src/components/settings/SettingsSaveButton.tsx +1 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +2 -2
- package/src/components/settings/TelemetrySettingsPanel.tsx +2 -2
- package/src/components/settings/UsagePanel.tsx +14 -10
- package/src/components/settings/WebSearchSettingsPanel.tsx +2 -2
- package/src/layouts/AgentLayout.tsx +3 -3
- package/src/lib/chat-transport.ts +68 -12
- package/src/lib/local-api.ts +11 -0
- package/src/lib/show-thinking-content.ts +4 -4
- package/src/pages/AgentPage.tsx +82 -11
- package/src/pages/ChatPanelPreviewPage.tsx +26 -4
- package/src/pages/SettingsPage.tsx +39 -39
- package/src/styles/index.css +12 -2
|
@@ -5,7 +5,9 @@ import type { ChatMessage } from '@steerable/agent-protocol';
|
|
|
5
5
|
import type { LocalChat, LocalChatAgent } from '@/lib/local-api';
|
|
6
6
|
import { Markdown } from './Markdown';
|
|
7
7
|
import { ExecutedActionsCard, type ExecutedAction } from './ExecutedActionsCard';
|
|
8
|
-
import {
|
|
8
|
+
import type { ChildInfo } from './OrchestrationChildrenCard';
|
|
9
|
+
import { collectTurnParticipants, extractMentionedAgentIds } from './executed-actions-model';
|
|
10
|
+
import type { InspectTaskInput } from './executed-actions-model';
|
|
9
11
|
import { StreamingStatus } from './StreamingStatus';
|
|
10
12
|
import { getFriendlyDate } from './timestamp';
|
|
11
13
|
import { useCopy } from './useCopy';
|
|
@@ -13,6 +15,12 @@ import type { TurnBlock } from './turn-timeline';
|
|
|
13
15
|
import { TurnProcessGroup } from './TurnProcessGroup';
|
|
14
16
|
import { TurnFilesCard } from './TurnFilesCard';
|
|
15
17
|
import type { TurnFile } from './turn-files';
|
|
18
|
+
import { stripNextStepsTags } from './next-steps-tags';
|
|
19
|
+
import {
|
|
20
|
+
formatTokenSpeed,
|
|
21
|
+
llmRequestElapsedMs,
|
|
22
|
+
type LlmSpeedSnapshot,
|
|
23
|
+
} from './process-status';
|
|
16
24
|
|
|
17
25
|
/**
|
|
18
26
|
* AssistantMessage — Tier-1 port of `deeppath`'s assistant-bubble.
|
|
@@ -77,8 +85,15 @@ interface AssistantMessageProps {
|
|
|
77
85
|
* `content` keep the legacy stacked layout.
|
|
78
86
|
*/
|
|
79
87
|
timeline?: TurnBlock[];
|
|
80
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* P3.1 子代理生命周期。看板卡片已不再渲染——每个 `委派 · X` 工具行从
|
|
90
|
+
* 「执行中」到「已完成」本身就是同一份进度,还能展开看任务书与回报。
|
|
91
|
+
* 这份列表现在喂两处:顶栏参与者徽章(没有 `@提及` 时的回落来源),
|
|
92
|
+
* 以及委派行跳转右侧过程栏所需的子代理 record。
|
|
93
|
+
*/
|
|
81
94
|
orchestrationChildren?: ChildInfo[];
|
|
95
|
+
/** 上一轮用户消息:顶栏按里面的 `@提及` 画参与的智能体。 */
|
|
96
|
+
previousUser?: Pick<ChatMessage, 'content'> & { messageMetadata?: string | null };
|
|
82
97
|
/**
|
|
83
98
|
* Current round number for the in-flight turn (1-based). Only meaningful
|
|
84
99
|
* when `isStreaming === true` and this is the tail message — used by
|
|
@@ -98,6 +113,12 @@ interface AssistantMessageProps {
|
|
|
98
113
|
startedAtMs?: number;
|
|
99
114
|
/** Frozen wall-clock of a finished turn. */
|
|
100
115
|
durationMs?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Model-request generation speed (reasoning + reply + any other LLM
|
|
118
|
+
* output). Elapsed excludes tool waits. Live snapshots keep ticking
|
|
119
|
+
* while `live` is true.
|
|
120
|
+
*/
|
|
121
|
+
llmSpeed?: LlmSpeedSnapshot;
|
|
101
122
|
/**
|
|
102
123
|
* 本回合产生/修改的文件列表(local-backend 回合收尾时收集,经
|
|
103
124
|
* `turn_files` SSE + messageMetadata.turnFiles 持久化)。渲染在回答
|
|
@@ -109,37 +130,42 @@ interface AssistantMessageProps {
|
|
|
109
130
|
* 行上,跟复制 / 重新生成同一排。
|
|
110
131
|
*/
|
|
111
132
|
onShare?: () => Promise<boolean>;
|
|
133
|
+
onInspectTask?: (task: InspectTaskInput) => void;
|
|
112
134
|
}
|
|
113
135
|
|
|
114
|
-
function agentInitial(
|
|
115
|
-
|
|
116
|
-
return agent.name.trim()[0]?.toUpperCase() ?? 'A';
|
|
136
|
+
function agentInitial(name: string): string {
|
|
137
|
+
return name.trim()[0]?.toUpperCase() ?? 'A';
|
|
117
138
|
}
|
|
118
139
|
|
|
119
140
|
function AgentBadge({
|
|
120
|
-
|
|
141
|
+
name,
|
|
142
|
+
color,
|
|
143
|
+
title,
|
|
121
144
|
isStreaming,
|
|
122
145
|
}: {
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
name: string;
|
|
147
|
+
color: string;
|
|
148
|
+
title?: string;
|
|
149
|
+
isStreaming?: boolean;
|
|
125
150
|
}) {
|
|
126
151
|
return (
|
|
127
152
|
<div
|
|
128
153
|
className="inline-flex items-center gap-1.5 text-[11px] leading-none text-agent-muted-foreground"
|
|
129
|
-
title={
|
|
154
|
+
title={title || name}
|
|
130
155
|
>
|
|
131
156
|
<span
|
|
157
|
+
data-agent-color-dot=""
|
|
132
158
|
className="inline-flex items-center justify-center rounded-full text-[9px] font-semibold text-white"
|
|
133
159
|
style={{
|
|
134
160
|
width: 14,
|
|
135
161
|
height: 14,
|
|
136
|
-
backgroundColor:
|
|
162
|
+
backgroundColor: color,
|
|
137
163
|
}}
|
|
138
164
|
>
|
|
139
|
-
{agentInitial(
|
|
165
|
+
{agentInitial(name)}
|
|
140
166
|
</span>
|
|
141
167
|
<span className="max-w-[160px] truncate rounded bg-agent-canvas/90 px-1">
|
|
142
|
-
{
|
|
168
|
+
{name}
|
|
143
169
|
</span>
|
|
144
170
|
{isStreaming && (
|
|
145
171
|
<span className="ml-1 inline-flex h-1.5 w-1.5 animate-pulse rounded-full bg-agent-foreground/30" />
|
|
@@ -152,11 +178,7 @@ const META_ACTION =
|
|
|
152
178
|
'inline-flex items-center gap-0.5 rounded transition-all duration-200 hover:text-agent-foreground opacity-0 focus:opacity-100 group-hover/message:opacity-100';
|
|
153
179
|
|
|
154
180
|
function bubbleClass(isPlanMode: boolean): string {
|
|
155
|
-
return
|
|
156
|
-
isPlanMode
|
|
157
|
-
? 'border-amber-400/50 dark:border-amber-500/30 bg-amber-50/10 dark:bg-amber-950/5 shadow-amber-500/5'
|
|
158
|
-
: 'border-agent-border bg-agent-canvas'
|
|
159
|
-
}`;
|
|
181
|
+
return isPlanMode ? 'rounded-agent-lg bg-amber-50/10 dark:bg-amber-950/5' : '';
|
|
160
182
|
}
|
|
161
183
|
|
|
162
184
|
// 框架 useChatStream 收到 SSE error 事件时把最后一条助手消息的内容打成
|
|
@@ -192,9 +214,20 @@ function readTurnFailure(
|
|
|
192
214
|
return null;
|
|
193
215
|
}
|
|
194
216
|
|
|
217
|
+
function readPersistedAgentId(message: ChatMessage): string | undefined {
|
|
218
|
+
const raw = (message as { messageMetadata?: unknown }).messageMetadata;
|
|
219
|
+
if (typeof raw !== 'string' || !raw) return undefined;
|
|
220
|
+
try {
|
|
221
|
+
const agentId = (JSON.parse(raw) as { agentId?: unknown }).agentId;
|
|
222
|
+
return typeof agentId === 'string' && agentId ? agentId : undefined;
|
|
223
|
+
} catch {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
195
228
|
function TurnErrorBubble({ reason }: { reason: string }) {
|
|
196
229
|
return (
|
|
197
|
-
<div className="rounded-agent-lg border border-agent-destructive/40 bg-agent-destructive/5
|
|
230
|
+
<div className="rounded-agent-lg border border-agent-destructive/40 bg-agent-destructive/5 px-2.5 py-2 text-xs leading-relaxed text-agent-destructive shadow-sm">
|
|
198
231
|
请求失败:{reason}
|
|
199
232
|
</div>
|
|
200
233
|
);
|
|
@@ -206,6 +239,17 @@ function StreamingCursor() {
|
|
|
206
239
|
);
|
|
207
240
|
}
|
|
208
241
|
|
|
242
|
+
function useLiveNow(enabled: boolean): number {
|
|
243
|
+
const [now, setNow] = useState(() => Date.now());
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
if (!enabled) return;
|
|
246
|
+
setNow(Date.now());
|
|
247
|
+
const id = window.setInterval(() => setNow(Date.now()), 1000);
|
|
248
|
+
return () => window.clearInterval(id);
|
|
249
|
+
}, [enabled]);
|
|
250
|
+
return now;
|
|
251
|
+
}
|
|
252
|
+
|
|
209
253
|
export function AssistantMessage({
|
|
210
254
|
message,
|
|
211
255
|
isStreaming,
|
|
@@ -216,34 +260,56 @@ export function AssistantMessage({
|
|
|
216
260
|
executedActions,
|
|
217
261
|
timeline,
|
|
218
262
|
orchestrationChildren,
|
|
263
|
+
previousUser,
|
|
219
264
|
currentRound = 1,
|
|
220
265
|
isPlanMode = false,
|
|
221
266
|
onRegenerate,
|
|
222
267
|
startedAtMs,
|
|
223
268
|
durationMs,
|
|
269
|
+
llmSpeed,
|
|
224
270
|
turnFiles,
|
|
225
271
|
onShare,
|
|
272
|
+
onInspectTask,
|
|
226
273
|
}: AssistantMessageProps) {
|
|
227
274
|
const content = message.content || '';
|
|
228
275
|
const failure = readTurnFailure(message, content);
|
|
229
276
|
// live 错误文本("[stream error] ...")不当正文渲染;落库的失败回合可能
|
|
230
277
|
// 留有部分正文,此时正文与错误气泡都展示。
|
|
231
278
|
const displayContent = failure?.contentIsError ? '' : content;
|
|
232
|
-
const { copied, copy } = useCopy(content);
|
|
279
|
+
const { copied, copy } = useCopy(stripNextStepsTags(content));
|
|
233
280
|
const [regenerating, setRegenerating] = useState(false);
|
|
234
281
|
const [regenerateError, setRegenerateError] = useState<string | null>(null);
|
|
235
|
-
const
|
|
236
|
-
|
|
282
|
+
const persistedAgentId =
|
|
283
|
+
typeof message.agentId === 'string' && message.agentId
|
|
284
|
+
? message.agentId
|
|
285
|
+
: readPersistedAgentId(message);
|
|
286
|
+
const persistedAgent = persistedAgentId
|
|
287
|
+
? agents.find((a) => a.id === persistedAgentId) ?? null
|
|
237
288
|
: null;
|
|
238
289
|
// Stream-time fallback: only allow the chat-level agent to "claim" a turn
|
|
239
290
|
// while it's actively streaming and hasn't flushed agentId to the DB yet.
|
|
240
291
|
const displayAgent = persistedAgent ?? (isStreaming ? currentAgent : null);
|
|
241
292
|
const useTimeline = timeline !== undefined;
|
|
242
293
|
const blocks = timeline ?? [];
|
|
294
|
+
const toolActions = [
|
|
295
|
+
...(executedActions ?? []),
|
|
296
|
+
...blocks.flatMap((block) => (block.type === 'tools' ? block.actions : [])),
|
|
297
|
+
];
|
|
298
|
+
const participants = collectTurnParticipants(displayAgent, {
|
|
299
|
+
agents,
|
|
300
|
+
children: orchestrationChildren,
|
|
301
|
+
actions: toolActions,
|
|
302
|
+
mentionedAgentIds: extractMentionedAgentIds(previousUser, agents),
|
|
303
|
+
});
|
|
304
|
+
const now = useLiveNow(Boolean(isStreaming && llmSpeed?.live));
|
|
305
|
+
const tokenSpeed = formatTokenSpeed(
|
|
306
|
+
llmSpeed?.tokens ?? 0,
|
|
307
|
+
llmRequestElapsedMs(llmSpeed, now),
|
|
308
|
+
);
|
|
243
309
|
|
|
244
310
|
return (
|
|
245
311
|
<motion.div
|
|
246
|
-
className="group/message
|
|
312
|
+
className="group/message"
|
|
247
313
|
data-message-role="assistant"
|
|
248
314
|
data-message-id={message.id}
|
|
249
315
|
initial={{ opacity: 0, y: 8 }}
|
|
@@ -251,10 +317,22 @@ export function AssistantMessage({
|
|
|
251
317
|
transition={{ duration: 0.25 }}
|
|
252
318
|
>
|
|
253
319
|
<div className="mx-auto w-full max-w-[var(--chat-input-box-width)] px-1">
|
|
254
|
-
{(
|
|
255
|
-
<div className="flex items-center justify-between
|
|
256
|
-
{
|
|
257
|
-
<
|
|
320
|
+
{(participants.length > 0 || isPlanMode) && (
|
|
321
|
+
<div className="mb-1 flex min-h-4 items-center justify-between gap-2">
|
|
322
|
+
{participants.length > 0 ? (
|
|
323
|
+
<div
|
|
324
|
+
data-testid="turn-agent-badges"
|
|
325
|
+
className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1"
|
|
326
|
+
>
|
|
327
|
+
{participants.map((agent, index) => (
|
|
328
|
+
<AgentBadge
|
|
329
|
+
key={agent.key}
|
|
330
|
+
name={agent.name}
|
|
331
|
+
color={agent.color}
|
|
332
|
+
isStreaming={isStreaming && index === 0}
|
|
333
|
+
/>
|
|
334
|
+
))}
|
|
335
|
+
</div>
|
|
258
336
|
) : (
|
|
259
337
|
<div />
|
|
260
338
|
)}
|
|
@@ -266,9 +344,6 @@ export function AssistantMessage({
|
|
|
266
344
|
)}
|
|
267
345
|
</div>
|
|
268
346
|
)}
|
|
269
|
-
{orchestrationChildren && orchestrationChildren.length > 0 && (
|
|
270
|
-
<OrchestrationChildrenCard children={orchestrationChildren} />
|
|
271
|
-
)}
|
|
272
347
|
{useTimeline ? (
|
|
273
348
|
<>
|
|
274
349
|
<TurnProcessGroup
|
|
@@ -279,6 +354,8 @@ export function AssistantMessage({
|
|
|
279
354
|
agents={agents}
|
|
280
355
|
chats={chats}
|
|
281
356
|
chatId={chatId}
|
|
357
|
+
onInspectTask={onInspectTask}
|
|
358
|
+
orchestrationChildren={orchestrationChildren}
|
|
282
359
|
emptyFallback={
|
|
283
360
|
failure ? (
|
|
284
361
|
<TurnErrorBubble reason={failure.reason} />
|
|
@@ -291,7 +368,7 @@ export function AssistantMessage({
|
|
|
291
368
|
/>
|
|
292
369
|
</div>
|
|
293
370
|
) : (
|
|
294
|
-
<div className={`${bubbleClass(isPlanMode)} text-
|
|
371
|
+
<div className={`${bubbleClass(isPlanMode)} text-xs italic text-agent-muted-foreground`}>
|
|
295
372
|
(空消息)
|
|
296
373
|
</div>
|
|
297
374
|
)
|
|
@@ -305,7 +382,7 @@ export function AssistantMessage({
|
|
|
305
382
|
}
|
|
306
383
|
renderAnswer={(block, isLast) => (
|
|
307
384
|
<div className={bubbleClass(isPlanMode)}>
|
|
308
|
-
<div className="markdown-content text-
|
|
385
|
+
<div className="markdown-content text-xs leading-relaxed text-agent-foreground">
|
|
309
386
|
<Markdown agents={agents} chats={chats} chatId={chatId}>{block.content}</Markdown>
|
|
310
387
|
</div>
|
|
311
388
|
{isStreaming && isLast && <StreamingCursor />}
|
|
@@ -323,11 +400,17 @@ export function AssistantMessage({
|
|
|
323
400
|
) : (
|
|
324
401
|
<>
|
|
325
402
|
{executedActions && executedActions.length > 0 && (
|
|
326
|
-
<ExecutedActionsCard
|
|
403
|
+
<ExecutedActionsCard
|
|
404
|
+
actions={executedActions}
|
|
405
|
+
agents={agents}
|
|
406
|
+
chatId={chatId}
|
|
407
|
+
onInspectTask={onInspectTask}
|
|
408
|
+
orchestrationChildren={orchestrationChildren}
|
|
409
|
+
/>
|
|
327
410
|
)}
|
|
328
411
|
{displayContent ? (
|
|
329
412
|
<div className={bubbleClass(isPlanMode)}>
|
|
330
|
-
<div className="markdown-content text-
|
|
413
|
+
<div className="markdown-content text-xs leading-relaxed text-agent-foreground">
|
|
331
414
|
<Markdown agents={agents} chats={chats} chatId={chatId}>{displayContent}</Markdown>
|
|
332
415
|
</div>
|
|
333
416
|
{isStreaming && <StreamingCursor />}
|
|
@@ -343,7 +426,7 @@ export function AssistantMessage({
|
|
|
343
426
|
/>
|
|
344
427
|
</div>
|
|
345
428
|
) : (
|
|
346
|
-
<div className={`${bubbleClass(isPlanMode)} text-
|
|
429
|
+
<div className={`${bubbleClass(isPlanMode)} text-xs italic text-agent-muted-foreground`}>
|
|
347
430
|
(空消息)
|
|
348
431
|
</div>
|
|
349
432
|
)}
|
|
@@ -357,16 +440,19 @@ export function AssistantMessage({
|
|
|
357
440
|
{/* 回合产物文件列表:钉在回答之下、时间戳行之上(Codex 式收尾)。
|
|
358
441
|
回合收尾才有数据,流式期间天然为空。 */}
|
|
359
442
|
{!isStreaming && turnFiles && turnFiles.length > 0 && (
|
|
360
|
-
<div className="mt-
|
|
443
|
+
<div className="mt-2.5 mb-1">
|
|
361
444
|
<TurnFilesCard files={turnFiles} />
|
|
362
445
|
</div>
|
|
363
446
|
)}
|
|
364
|
-
<div className="mt-
|
|
447
|
+
<div className="mt-2 flex items-center gap-2 text-[11px] text-agent-muted-foreground/80">
|
|
365
448
|
<span>
|
|
366
449
|
{message.createdAt
|
|
367
450
|
? getFriendlyDate(new Date(message.createdAt))
|
|
368
451
|
: ''}
|
|
369
452
|
</span>
|
|
453
|
+
{tokenSpeed ? (
|
|
454
|
+
<span data-testid="turn-token-speed">{tokenSpeed}</span>
|
|
455
|
+
) : null}
|
|
370
456
|
{content && !isStreaming && (
|
|
371
457
|
<button
|
|
372
458
|
type="button"
|
|
@@ -81,7 +81,7 @@ export type { ExecPolicy, McpToolItem, SkillItem };
|
|
|
81
81
|
* - Compact-mode responsive layout.
|
|
82
82
|
*/
|
|
83
83
|
|
|
84
|
-
const MIN_HEIGHT_PX =
|
|
84
|
+
const MIN_HEIGHT_PX = 36;
|
|
85
85
|
const MAX_HEIGHT_PX = 220;
|
|
86
86
|
const MENTION_PATTERN = /(@[^\s@]+)/g;
|
|
87
87
|
const MAX_MENTION_SUGGESTIONS = 8;
|
|
@@ -141,6 +141,8 @@ export interface ChatInputProps {
|
|
|
141
141
|
/** Slot in the composer meta row above the input box, before the agent
|
|
142
142
|
* picker (e.g. the project badge). */
|
|
143
143
|
leadingChrome?: ReactNode;
|
|
144
|
+
/** Slot in the composer meta row above the input box, right-aligned (e.g. SessionTodoList). */
|
|
145
|
+
trailingChrome?: ReactNode;
|
|
144
146
|
/** Current chat mode. When unset the toggle is hidden (defaults to 'agent'). */
|
|
145
147
|
mode?: ChatMode;
|
|
146
148
|
/** Called when the user switches between Agent / Plan mode. */
|
|
@@ -696,6 +698,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
696
698
|
onOpenSettings,
|
|
697
699
|
toolbarExtras,
|
|
698
700
|
leadingChrome,
|
|
701
|
+
trailingChrome,
|
|
699
702
|
mode,
|
|
700
703
|
onModeChange,
|
|
701
704
|
execPolicy,
|
|
@@ -1512,7 +1515,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1512
1515
|
|
|
1513
1516
|
if (approvalPrompt?.current) {
|
|
1514
1517
|
return (
|
|
1515
|
-
<div className="chat-input-container @container relative px-
|
|
1518
|
+
<div className="chat-input-container @container relative px-2.5 pb-2 pt-0.5">
|
|
1516
1519
|
<ApprovalPromptMenu
|
|
1517
1520
|
key={approvalPrompt.current.requestId}
|
|
1518
1521
|
request={approvalPrompt.current}
|
|
@@ -1527,7 +1530,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1527
1530
|
const request = askUserPrompt.current;
|
|
1528
1531
|
return (
|
|
1529
1532
|
<div
|
|
1530
|
-
className="chat-input-container @container relative px-
|
|
1533
|
+
className="chat-input-container @container relative px-2.5 pb-2 pt-0.5"
|
|
1531
1534
|
data-testid="ask-user-composer"
|
|
1532
1535
|
>
|
|
1533
1536
|
<AskUserQuestionMenu
|
|
@@ -1549,10 +1552,10 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1549
1552
|
return (
|
|
1550
1553
|
// @container:底栏元素的显隐/宽度用容器查询(@sm = 384px)而不是
|
|
1551
1554
|
// 视口断点——侧边栏占宽后,视口够宽但输入框本身可能已经很窄。
|
|
1552
|
-
<div className="chat-input-container @container relative px-
|
|
1553
|
-
{(leadingChrome || selectedAgent) && (
|
|
1555
|
+
<div className="chat-input-container @container relative px-2.5 pb-2.5 pt-1">
|
|
1556
|
+
{(leadingChrome || selectedAgent || trailingChrome) && (
|
|
1554
1557
|
<div
|
|
1555
|
-
className="relative mb-1 flex min-w-0 items-center gap-1.5"
|
|
1558
|
+
className="relative mb-1.5 flex min-w-0 items-center gap-1.5"
|
|
1556
1559
|
data-testid="composer-meta-row"
|
|
1557
1560
|
>
|
|
1558
1561
|
{leadingChrome}
|
|
@@ -1568,6 +1571,11 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1568
1571
|
mentionReferences={mentionReferences}
|
|
1569
1572
|
/>
|
|
1570
1573
|
)}
|
|
1574
|
+
{trailingChrome && (
|
|
1575
|
+
<div className="ml-auto flex items-center min-w-0">
|
|
1576
|
+
{trailingChrome}
|
|
1577
|
+
</div>
|
|
1578
|
+
)}
|
|
1571
1579
|
</div>
|
|
1572
1580
|
)}
|
|
1573
1581
|
{mentionQuery && mentionSuggestions.length > 0 && (
|
|
@@ -1712,7 +1720,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1712
1720
|
onDrop={handleDrop}
|
|
1713
1721
|
>
|
|
1714
1722
|
{pendingFollowUps.length > 0 && (
|
|
1715
|
-
<div className="flex flex-col gap-1 border-b border-agent-border bg-agent-muted/30 px-
|
|
1723
|
+
<div className="flex flex-col gap-1 border-b border-agent-border bg-agent-muted/30 px-2.5 py-1.5">
|
|
1716
1724
|
<div className="text-[11px] font-medium text-agent-muted-foreground">
|
|
1717
1725
|
排队中({pendingFollowUps.length})· 本轮结束后自动发出 · 停止将丢弃
|
|
1718
1726
|
</div>
|
|
@@ -1739,14 +1747,14 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1739
1747
|
{steerNotice && (
|
|
1740
1748
|
<div
|
|
1741
1749
|
role="status"
|
|
1742
|
-
className="border-b border-agent-border bg-agent-muted/20 px-
|
|
1750
|
+
className="border-b border-agent-border bg-agent-muted/20 px-2.5 py-1 text-[11px] text-agent-muted-foreground"
|
|
1743
1751
|
>
|
|
1744
1752
|
{steerNotice}
|
|
1745
1753
|
</div>
|
|
1746
1754
|
)}
|
|
1747
1755
|
<div className="chat-input-surface relative overflow-hidden">
|
|
1748
1756
|
<div
|
|
1749
|
-
className="chat-input-placeholder pointer-events-none absolute inset-x-0 top-0 px-
|
|
1757
|
+
className="chat-input-placeholder pointer-events-none absolute inset-x-0 top-0 px-2.5 pt-2 text-xs text-agent-muted-foreground"
|
|
1750
1758
|
style={{ visibility: value ? 'hidden' : undefined }}
|
|
1751
1759
|
>
|
|
1752
1760
|
{placeholder}
|
|
@@ -1769,7 +1777,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1769
1777
|
onCompositionStart={handleCompositionStart}
|
|
1770
1778
|
onCompositionEnd={handleCompositionEnd}
|
|
1771
1779
|
onScroll={handleEditorScroll}
|
|
1772
|
-
className="chat-input-editor relative z-10 block w-full overflow-y-auto whitespace-pre-wrap break-words border-0 bg-transparent px-
|
|
1780
|
+
className="chat-input-editor relative z-10 block w-full overflow-y-auto whitespace-pre-wrap break-words border-0 bg-transparent px-2.5 pt-2 text-xs text-agent-foreground caret-agent-foreground outline-none selection:bg-agent-foreground/20"
|
|
1773
1781
|
style={{
|
|
1774
1782
|
minHeight: MIN_HEIGHT_PX,
|
|
1775
1783
|
maxHeight: MAX_HEIGHT_PX,
|
|
@@ -1777,7 +1785,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1777
1785
|
/>
|
|
1778
1786
|
</div>
|
|
1779
1787
|
{actualFiles.length > 0 && (
|
|
1780
|
-
<div className="flex flex-wrap gap-1.5 px-
|
|
1788
|
+
<div className="flex flex-wrap gap-1.5 px-2.5 pb-1.5 pt-0.5">
|
|
1781
1789
|
{actualFiles.map((file, idx) => (
|
|
1782
1790
|
<div
|
|
1783
1791
|
key={idx}
|
|
@@ -1802,7 +1810,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1802
1810
|
)}
|
|
1803
1811
|
<div
|
|
1804
1812
|
ref={toolbarRowRef}
|
|
1805
|
-
className="flex items-center justify-between gap-
|
|
1813
|
+
className="flex items-center justify-between gap-1.5 px-2 py-1 text-[12px] leading-[1.45]"
|
|
1806
1814
|
>
|
|
1807
1815
|
{/* Left toolbar: mode + exec sandbox + host extras + settings.
|
|
1808
1816
|
Agent picker lives in the meta row above the box, next to
|
|
@@ -1827,11 +1835,11 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1827
1835
|
type="button"
|
|
1828
1836
|
onClick={handlePickFiles}
|
|
1829
1837
|
disabled={disabled || isStreaming}
|
|
1830
|
-
className="flex h-
|
|
1838
|
+
className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
|
1831
1839
|
title="上传文件(可多选)"
|
|
1832
1840
|
aria-label="上传文件"
|
|
1833
1841
|
>
|
|
1834
|
-
<LuPaperclip className="h-3
|
|
1842
|
+
<LuPaperclip className="h-3 w-3" />
|
|
1835
1843
|
</button>
|
|
1836
1844
|
{/* 隐藏的原生多选文件输入:点击回形针按钮触发系统选择框。 */}
|
|
1837
1845
|
<input
|
|
@@ -1847,12 +1855,12 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1847
1855
|
<button
|
|
1848
1856
|
type="button"
|
|
1849
1857
|
onClick={onOpenSettings}
|
|
1850
|
-
className="flex h-
|
|
1858
|
+
className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground"
|
|
1851
1859
|
title="LLM 设置"
|
|
1852
1860
|
aria-label="LLM 设置"
|
|
1853
1861
|
data-testid="chat-llm-settings"
|
|
1854
1862
|
>
|
|
1855
|
-
<LuSettings className="h-3
|
|
1863
|
+
<LuSettings className="h-3 w-3" />
|
|
1856
1864
|
</button>
|
|
1857
1865
|
)}
|
|
1858
1866
|
</div>
|
|
@@ -1921,10 +1929,10 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1921
1929
|
disabled={!isStreaming && !canSend}
|
|
1922
1930
|
className={
|
|
1923
1931
|
isStreaming
|
|
1924
|
-
? 'flex h-
|
|
1932
|
+
? 'flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-agent-destructive text-white transition hover:opacity-90'
|
|
1925
1933
|
: canSend
|
|
1926
|
-
? 'flex h-
|
|
1927
|
-
: 'flex h-
|
|
1934
|
+
? 'flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-agent-foreground text-agent-canvas transition hover:opacity-90'
|
|
1935
|
+
: 'flex h-6 w-6 shrink-0 cursor-not-allowed items-center justify-center rounded-full bg-agent-muted text-agent-muted-foreground'
|
|
1928
1936
|
}
|
|
1929
1937
|
title={
|
|
1930
1938
|
isStreaming
|
|
@@ -1937,9 +1945,9 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
|
|
1937
1945
|
data-testid="chat-send"
|
|
1938
1946
|
>
|
|
1939
1947
|
{isStreaming ? (
|
|
1940
|
-
<LuSquare className="h-3
|
|
1948
|
+
<LuSquare className="h-3 w-3 fill-current" />
|
|
1941
1949
|
) : (
|
|
1942
|
-
<LuArrowUp className="h-
|
|
1950
|
+
<LuArrowUp className="h-3.5 w-3.5" />
|
|
1943
1951
|
)}
|
|
1944
1952
|
</button>
|
|
1945
1953
|
</div>
|
|
@@ -1971,7 +1979,7 @@ function ModeToggle({
|
|
|
1971
1979
|
// shrink-0:分段控件被 flex 压缩会压扁文字;极窄容器(<@sm)退化为
|
|
1972
1980
|
// 纯图标(title 兜底语义),把收缩量让给可截断的选择器。
|
|
1973
1981
|
<div
|
|
1974
|
-
className="inline-flex h-
|
|
1982
|
+
className="inline-flex h-6 shrink-0 items-center rounded-full border border-agent-border bg-agent-canvas p-0.5"
|
|
1975
1983
|
role="radiogroup"
|
|
1976
1984
|
aria-label="对话模式"
|
|
1977
1985
|
data-testid="mode-toggle"
|
|
@@ -1985,13 +1993,13 @@ function ModeToggle({
|
|
|
1985
1993
|
title="Agent 模式:直接执行任务"
|
|
1986
1994
|
data-testid="mode-agent"
|
|
1987
1995
|
className={[
|
|
1988
|
-
'inline-flex h-
|
|
1996
|
+
'inline-flex h-5 items-center gap-1 rounded-full px-1.5 text-[12px] leading-[1.45] transition-colors disabled:cursor-not-allowed disabled:opacity-70',
|
|
1989
1997
|
mode === 'agent'
|
|
1990
1998
|
? 'bg-agent-foreground/10 text-agent-foreground'
|
|
1991
1999
|
: 'text-agent-muted-foreground hover:text-agent-foreground',
|
|
1992
2000
|
].join(' ')}
|
|
1993
2001
|
>
|
|
1994
|
-
<LuInfinity className="h-3
|
|
2002
|
+
<LuInfinity className="h-3 w-3" />
|
|
1995
2003
|
<span className="@max-sm:hidden">Agent</span>
|
|
1996
2004
|
</button>
|
|
1997
2005
|
<button
|
|
@@ -2003,13 +2011,13 @@ function ModeToggle({
|
|
|
2003
2011
|
title="Plan 模式:先制定计划,只读不执行"
|
|
2004
2012
|
data-testid="mode-plan"
|
|
2005
2013
|
className={[
|
|
2006
|
-
'inline-flex h-
|
|
2014
|
+
'inline-flex h-5 items-center gap-1 rounded-full px-1.5 text-[12px] leading-[1.45] transition-colors disabled:cursor-not-allowed disabled:opacity-70',
|
|
2007
2015
|
mode === 'plan'
|
|
2008
2016
|
? 'bg-amber-400/20 text-amber-600 dark:text-amber-400'
|
|
2009
2017
|
: 'text-agent-muted-foreground hover:text-agent-foreground',
|
|
2010
2018
|
].join(' ')}
|
|
2011
2019
|
>
|
|
2012
|
-
<LuListChecks className="h-3
|
|
2020
|
+
<LuListChecks className="h-3 w-3" />
|
|
2013
2021
|
<span className="@max-sm:hidden">Plan</span>
|
|
2014
2022
|
</button>
|
|
2015
2023
|
</div>
|
|
@@ -64,16 +64,16 @@ export function ExecPolicyPicker({
|
|
|
64
64
|
title={current.description}
|
|
65
65
|
onClick={() => setOpen((next) => !next)}
|
|
66
66
|
className={[
|
|
67
|
-
'inline-flex h-
|
|
67
|
+
'inline-flex h-6 max-w-[140px] items-center gap-1 rounded-full border px-1.5 text-[12px] leading-[1.45] transition-colors disabled:cursor-not-allowed disabled:opacity-70',
|
|
68
68
|
isFull
|
|
69
69
|
? 'border-amber-400/50 bg-amber-400/10 text-amber-700 dark:text-amber-400'
|
|
70
70
|
: 'border-agent-border bg-agent-canvas text-agent-foreground hover:bg-agent-foreground/5',
|
|
71
71
|
].join(' ')}
|
|
72
72
|
>
|
|
73
73
|
{isFull ? (
|
|
74
|
-
<LuLockOpen className="h-3
|
|
74
|
+
<LuLockOpen className="h-3 w-3 shrink-0" />
|
|
75
75
|
) : (
|
|
76
|
-
<LuFolderLock className="h-3
|
|
76
|
+
<LuFolderLock className="h-3 w-3 shrink-0" />
|
|
77
77
|
)}
|
|
78
78
|
<span className="truncate">{current.label}</span>
|
|
79
79
|
<LuChevronDown className="h-3 w-3 shrink-0 text-agent-muted-foreground" />
|
|
@@ -104,7 +104,7 @@ export function ExecPolicyPicker({
|
|
|
104
104
|
: 'hover:bg-agent-foreground/5',
|
|
105
105
|
].join(' ')}
|
|
106
106
|
>
|
|
107
|
-
<span className="text-
|
|
107
|
+
<span className="text-[12px] font-medium leading-[1.45] text-agent-foreground">
|
|
108
108
|
{option.label}
|
|
109
109
|
</span>
|
|
110
110
|
<span className="text-[11px] leading-snug text-agent-muted-foreground">
|