Skip to content

Commit 902062f

Browse files
committed
feat: enhance message rendering for user answers
1 parent 4cd408d commit 902062f

12 files changed

Lines changed: 92 additions & 27 deletions

File tree

‎packages/cli/src/tests/ask-user-question.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,18 @@ test("formatAskUserQuestionAnswers creates model-readable answer text", () => {
104104
}),
105105
[
106106
"Questions 2/2 answered",
107-
" - `Which package manager?`",
107+
" - Which package manager?",
108108
" answer: yarn",
109-
" - `Any notes?`",
109+
" - Any notes?",
110110
" answer: Use the existing lockfile",
111111
].join("\n")
112112
);
113113
});
114114

115-
test("formatAskUserQuestionAnswers normalizes multiline answers and escapes question backticks", () => {
115+
test("formatAskUserQuestionAnswers normalizes multiline questions and answers", () => {
116116
assert.equal(
117-
formatAskUserQuestionAnswers({ "Which `mode`?": "Use fast\nmode" }),
118-
["Questions 1/1 answered", " - `Which \\`mode\\`?`", " answer: Use fast mode"].join("\n")
117+
formatAskUserQuestionAnswers({ "Which\nmode?": "Use fast\nmode" }),
118+
["Questions 1/1 answered", " - Which mode?", " answer: Use fast mode"].join("\n")
119119
);
120120
});
121121

‎packages/cli/src/tests/message-view.test.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,37 @@ test("renderMessageToStdout renders user messages with > prefix", () => {
120120
assert.ok(output.includes("> fix the bug"));
121121
});
122122

123+
test("MessageView renders only answer user messages as Markdown", () => {
124+
const regular = makeSessionMessage({ role: "user", content: "**Questions answered**" });
125+
const answers = makeSessionMessage({
126+
role: "user",
127+
content: "**Questions answered**",
128+
meta: { isAnswers: true },
129+
});
130+
131+
const regularOutput = stripAnsi(
132+
renderToString(React.createElement(MessageView, { message: regular, width: 80 }), { columns: 80 })
133+
);
134+
const answersOutput = stripAnsi(
135+
renderToString(React.createElement(MessageView, { message: answers, width: 80 }), { columns: 80 })
136+
);
137+
138+
assert.match(regularOutput, /\*\*Questions answered\*\*/);
139+
assert.doesNotMatch(answersOutput, /\*\*/);
140+
assert.match(answersOutput, /Questions answered/);
141+
});
142+
143+
test("renderMessageToStdout renders answer user messages as Markdown", () => {
144+
const msg = makeSessionMessage({
145+
role: "user",
146+
content: "**Questions answered**",
147+
meta: { isAnswers: true },
148+
});
149+
const output = stripAnsi(renderMessageToStdout(msg, RawMode.Raw));
150+
151+
assert.equal(output, "> Questions answered");
152+
});
153+
123154
test("renderMessageToStdout shows (no content) for empty user messages", () => {
124155
const msg = makeSessionMessage({ role: "user", content: "" });
125156
const output = renderMessageToStdout(msg, RawMode.Raw);

‎packages/cli/src/ui/components/MessageView/index.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export function MessageView({ message, collapsed, width = 80 }: MessageViewProps
2222
}
2323

2424
if (message.role === "user") {
25-
const text = message.content || "(no content)";
25+
const content = message.content || "(no content)";
26+
const text = message.meta?.isAnswers ? renderMarkdown(content) : content;
2627
return (
2728
<PromptEchoLine
2829
text={text}

‎packages/cli/src/ui/components/MessageView/utils.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { DiffPreviewLine, ToolSummary } from "./types";
22
import type { SessionMessage } from "@vegamo/deepcode-core";
33
import { RawMode } from "../../contexts";
44
import chalk from "chalk";
5+
import { renderMarkdown } from "./markdown";
56

67
/** Type guard that checks whether a value is a plain object (not null, not an array). */
78
export function isPlainRecord(value: unknown): value is Record<string, unknown> {
@@ -227,7 +228,8 @@ export function renderMessageToStdout(message: SessionMessage, mode: RawMode): s
227228
}
228229

229230
if (message.role === "user") {
230-
const text = message.content || "(no content)";
231+
const content = message.content || "(no content)";
232+
const text = message.meta?.isAnswers ? renderMarkdown(content) : content;
231233
return chalk(`> ${text}`);
232234
}
233235

‎packages/cli/src/ui/core/ask-user-question.ts‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ export function formatAskUserQuestionAnswers(answers: AskUserQuestionAnswers): s
5050
const entries = Object.entries(answers);
5151
const lines = [`Questions ${entries.length}/${entries.length} answered`];
5252
for (const [question, answer] of entries) {
53-
lines.push(` - \`${formatQuestion(question)}\``);
54-
lines.push(` answer: ${formatAnswer(answer)}`);
53+
lines.push(` - ${formatAnswerPart(question)}`);
54+
lines.push(` answer: ${formatAnswerPart(answer)}`);
5555
}
5656
return lines.join("\n");
5757
}
@@ -131,10 +131,6 @@ function normalizeOption(raw: unknown): AskUserQuestionOption | null {
131131
};
132132
}
133133

134-
function formatQuestion(value: string): string {
135-
return value.replace(/`/g, "\\`").replace(/\s+/g, " ").trim();
136-
}
137-
138-
function formatAnswer(value: string): string {
134+
function formatAnswerPart(value: string): string {
139135
return value.replace(/\s+/g, " ").trim();
140136
}

‎packages/cli/src/ui/utils/index.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { PromptDraft } from "../views/PromptInput";
55
import type { ModelConfigSelection } from "@vegamo/deepcode-core";
66
import type { SessionEntry, SessionMessage } from "@vegamo/deepcode-core";
77
import type { SessionManager } from "@vegamo/deepcode-core";
8+
import type { MessageMeta } from "@vegamo/deepcode-core";
89

910
/**
1011
* Render all messages directly to stdout for Raw mode display.
@@ -26,7 +27,7 @@ export function renderRawModeMessages(allMessages: SessionMessage[], mode: strin
2627
}
2728
}
2829

29-
export function buildSyntheticUserMessage(content: string, imageCount: number): SessionMessage {
30+
export function buildSyntheticUserMessage(content: string, imageCount: number, meta?: MessageMeta): SessionMessage {
3031
const now = new Date().toISOString();
3132
return {
3233
id: `local-${Math.random().toString(36).slice(2)}`,
@@ -45,6 +46,7 @@ export function buildSyntheticUserMessage(content: string, imageCount: number):
4546
visible: true,
4647
createTime: now,
4748
updateTime: now,
49+
meta,
4850
};
4951
}
5052

‎packages/cli/src/ui/views/App.tsx‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,14 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
424424
(submission.imageUrls.length > 0 ? "[Image]" : "");
425425

426426
if (userDisplayContent && submission.command !== "continue") {
427-
setMessages((prev) => [...prev, buildSyntheticUserMessage(userDisplayContent, submission.imageUrls.length)]);
427+
setMessages((prev) => [
428+
...prev,
429+
buildSyntheticUserMessage(
430+
userDisplayContent,
431+
submission.imageUrls.length,
432+
submission.isAnswers ? { isAnswers: true } : undefined
433+
),
434+
]);
428435
}
429436

430437
setBusy(true);

‎packages/core/src/tests/session.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ test("SessionManager tags AskUserQuestion answer messages", async () => {
631631
createChatResponse("continued", { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }),
632632
]);
633633
const sessionId = await manager.createSession({
634-
text: "Questions 1/1 answered\n - `Continue?`\n answer: Yes",
634+
text: "Questions 1/1 answered\n - Continue?\n answer: Yes",
635635
isAnswers: true,
636636
});
637637
const message = manager.listSessionMessages(sessionId).find((item) => item.role === "user");

‎packages/vscode-ide-companion/resources/webview.css‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,10 @@ body {
733733
max-width: 80%;
734734
}
735735

736+
.bubble.user.answers {
737+
white-space: normal;
738+
}
739+
736740
.bubble.system {
737741
background: transparent;
738742
border-color: transparent;

‎packages/vscode-ide-companion/resources/webview.html‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -562,8 +562,10 @@
562562
if (otherText) {
563563
answers.push(otherText);
564564
}
565-
lines.push(` - \`${String(question.question || "").replace(/`/g, "\\`")}\``);
566-
lines.push(` answer: ${answers.join(", ")}`);
565+
const questionText = String(question.question || "").replace(/\s+/g, " ").trim();
566+
const answerText = answers.join(", ").replace(/\s+/g, " ").trim();
567+
lines.push(` - ${questionText}`);
568+
lines.push(` answer: ${answerText}`);
567569
}
568570

569571
return {
@@ -698,8 +700,13 @@
698700
}
699701

700702
if (role === "user") {
701-
// 普通用户气泡
702-
bubble.textContent = content;
703+
if (meta && meta.isAnswers) {
704+
bubble.classList.add("answers");
705+
bubble.innerHTML = content;
706+
} else {
707+
// 普通用户气泡
708+
bubble.textContent = content;
709+
}
703710
} else if (role === "assistant") {
704711
if (meta && meta.asThinking === true) {
705712
// 思考气泡(可折叠)
@@ -2168,7 +2175,7 @@
21682175
if (msg.role === "system") {
21692176
addBubble(msg.content, "system", msg.meta, shouldConnect);
21702177
} else if (msg.role === "user") {
2171-
addBubble(msg.content, "user", msg.meta, false);
2178+
addBubble(msg.meta?.isAnswers ? msg.html || msg.content : msg.content, "user", msg.meta, false);
21722179
} else if (msg.role === "assistant") {
21732180
addBubble(msg.html || msg.content, "assistant", msg.meta, shouldConnect, msg.content || "");
21742181
} else if (msg.role === "tool") {
@@ -2327,8 +2334,12 @@
23272334
}
23282335
updateThinkingBubbleContent();
23292336
} else if (message.type === "userMessage") {
2330-
// 显示用户消息(纯文本)
2331-
addBubble(message.content || "", "user", message.meta, false);
2337+
addBubble(
2338+
message.meta?.isAnswers ? message.html || message.content || "" : message.content || "",
2339+
"user",
2340+
message.meta,
2341+
false
2342+
);
23322343

23332344
// Record user message into history
23342345
if (typeof message.content === "string") {

0 commit comments

Comments
 (0)