Skip to content

Commit deabaee

Browse files
committed
feat(ui): 改进代码块和确认提示的显示效果
- 在 MessageArea 中禁用代码块的纯文本渲染,以支持语法高亮 - 简化 ConfirmationStage 中的确认标题,移除冗余的"权限确认:"前缀 - 移除 useCommandHandler 中最大轮次确认的硬编码标题 - 重构 ConfirmationPrompt 组件,不再显示副标题,动态设置主标题 - 在 rawStreamRenderer 中为代码块和 diff 添加视觉边框和语法着色
1 parent 6786a77 commit deabaee

5 files changed

Lines changed: 57 additions & 17 deletions

File tree

packages/cli/src/tools/execution/PipelineStages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ export class ConfirmationStage implements PipelineStage {
394394

395395
// 从权限检查结果构建确认详情
396396
const confirmationDetails = {
397-
title: `权限确认: ${signature}`,
397+
title: signature,
398398
message: confirmationReason || '此操作需要用户确认',
399399
kind: tool.kind, // 工具类型,用于 ACP 权限模式判断
400400
details: this.generatePreviewForTool(tool.name, execution.params),

packages/cli/src/ui/components/ConfirmationPrompt.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ interface ConfirmationContentProps {
3030
const ConfirmationContent = React.memo<ConfirmationContentProps>(
3131
({ details, headerColor, isPlanModeExit, isPlanModeEnter, terminalWidth }) => (
3232
<>
33-
{/* 副标题:显示工具签名等上下文信息(如 "Bash(git status)") */}
34-
{details.title && (
35-
<Box marginBottom={1}>
36-
<Text dimColor>{details.title}</Text>
37-
</Box>
38-
)}
39-
4033
<Box marginBottom={1}>
4134
<Text>{details.message}</Text>
4235
</Box>
@@ -292,8 +285,8 @@ export const ConfirmationPrompt: React.FC<ConfirmationPromptProps> = React.memo(
292285
if (isMaxTurnsExceeded) {
293286
return { color: 'yellow' as const, title: '已达最大轮次' };
294287
}
295-
return { color: 'yellow' as const, title: '操作确认' };
296-
}, [isPlanModeExit, isPlanModeEnter, isMaxTurnsExceeded]);
288+
return { color: 'yellow' as const, title: details.title || '操作确认' };
289+
}, [isPlanModeExit, isPlanModeEnter, isMaxTurnsExceeded, details.title]);
297290

298291
return (
299292
<Box

packages/cli/src/ui/components/MessageArea.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ export const MessageArea: React.FC = React.memo(() => {
239239
hidePrefix={hidePrefix}
240240
noMargin={true}
241241
blocksOverride={blocksToRender}
242-
renderCodeBlocksAsPlainText={true}
242+
renderCodeBlocksAsPlainText={false}
243243
/>
244244
</Box>,
245245
]);

packages/cli/src/ui/hooks/useCommandHandler.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ export const useCommandHandler = (
250250
? async (data: { turnsCount: number }) => {
251251
const response = await confirmationHandler.requestConfirmation({
252252
type: 'maxTurnsExceeded',
253-
title: '对话轮次上限',
254253
message: `已进行 ${data.turnsCount} 轮对话。是否继续?`,
255254
risks: ['继续执行可能导致更长的等待时间', '可能产生更多的 API 费用'],
256255
});

packages/cli/src/ui/utils/rawStreamRenderer.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export function renderTail(
100100
const outputLines: string[] = [];
101101
const theme = themeManager.getTheme();
102102
const indent = ' '.repeat(state.prefixIndent);
103+
const borderColor = chalk.dim.hex(theme.colors.border.light);
103104

104105
// 隐藏行提示
105106
if (hiddenLines > 0) {
@@ -108,27 +109,74 @@ export function renderTail(
108109
);
109110
}
110111

112+
// 代码块模式:从 lines 中分离 fence 行和实际代码
113+
const isCodeMode = mode === 'code';
114+
const isDiffMode = mode === 'diff';
115+
let codeLang = '';
116+
let contentLines = lines;
117+
118+
if (isCodeMode && lines.length > 0) {
119+
// 第一行是 fence(如 "```typescript"),解析语言并跳过
120+
const fenceMatch = lines[0].match(/^```(\w+)?/);
121+
codeLang = fenceMatch?.[1] || '';
122+
contentLines = lines.slice(1);
123+
} else if (isDiffMode && lines.length > 0 && lines[0] === '<<<DIFF>>>') {
124+
contentLines = lines.slice(1);
125+
}
126+
127+
const visibleLines = contentLines.slice(-maxDisplayLines);
128+
129+
// 代码块:渲染顶部边框 + 语言标签
130+
if (isCodeMode && hiddenLines === 0) {
131+
const langLabel = codeLang
132+
? ` ${chalk.hex(theme.colors.text.secondary)(codeLang)}`
133+
: '';
134+
outputLines.push(`${indent}${borderColor('╭─')}${langLabel}`);
135+
}
136+
111137
// 内容行
112-
const visibleLines = lines.slice(-maxDisplayLines);
113138
for (let i = 0; i < visibleLines.length; i++) {
114139
const line = visibleLines[i];
115140
let prefix = indent;
116141

117-
// 首行前缀
118-
if (i === 0 && !hidePrefix && state.isFirstRender) {
142+
// 首行前缀(仅 text 模式使用 bullet)
143+
if (i === 0 && !hidePrefix && state.isFirstRender && !isCodeMode && !isDiffMode) {
119144
prefix = chalk.bold.hex(theme.colors.success)('• ') + ' ';
120145
state.isFirstRender = false;
121146
}
122147

123148
// 截断超宽行
124-
const maxContentWidth = state.terminalWidth - state.prefixIndent - 2;
149+
const borderExtra = isCodeMode ? 4 : 0; // "│ " 占 2 字符 + 2 边距
150+
const maxContentWidth = state.terminalWidth - state.prefixIndent - 2 - borderExtra;
125151
let displayLine = line;
126152
if (stringWidth(line) > maxContentWidth) {
127153
// 简单截断(不处理 ANSI,因为 tail 是纯文本)
128154
displayLine = line.slice(0, maxContentWidth);
129155
}
130156

131-
outputLines.push(`${prefix}${displayLine}`);
157+
if (isCodeMode) {
158+
// 代码块行:添加左边框
159+
outputLines.push(`${indent}${borderColor('│')} ${displayLine}`);
160+
} else if (isDiffMode) {
161+
// Diff 行:按前缀着色
162+
const trimmed = displayLine;
163+
if (trimmed.startsWith('+')) {
164+
outputLines.push(`${prefix}${chalk.green(displayLine)}`);
165+
} else if (trimmed.startsWith('-')) {
166+
outputLines.push(`${prefix}${chalk.red(displayLine)}`);
167+
} else if (trimmed.startsWith('@@')) {
168+
outputLines.push(`${prefix}${chalk.dim(displayLine)}`);
169+
} else {
170+
outputLines.push(`${prefix}${displayLine}`);
171+
}
172+
} else {
173+
outputLines.push(`${prefix}${displayLine}`);
174+
}
175+
176+
// 首次渲染标记(代码块和 diff 模式在首行已输出边框,此处仍需标记)
177+
if (i === 0 && !hidePrefix && state.isFirstRender) {
178+
state.isFirstRender = false;
179+
}
132180
}
133181

134182
// 差量渲染:对比上一帧,只更新变化的行

0 commit comments

Comments
 (0)