Skip to content

Commit a6b71de

Browse files
committed
refactor(webview): 重构输入组件并优化消息渲染与权限处理
- 将 Composer 组件重命名并替换为 InputPrompt,增加附件支持和粘贴处理 - 移除消息中的 html 字段,统一使用 content 字段存储文本内容 - 调整 ChatProvider 和路由层消息格式,去除多余的 renderMarkdown 调用 - 在 PlanRenderer 中支持有序列表任务项标记及图标展示优化 - 优化 DiffPreview 组件样式,增加换行与边距调整 - PermissionPrompt 改进权限决策映射,明确允许或拒绝权限权限类型 - Markdown 组件集成 highlight.js 和 taskLists 支持代码高亮及任务列表 - 向 body 标签添加 data-vscode-context 属性,防止默认上下文菜单项 - 更新单元测试适配 InputPrompt 组件及修改消息测试去除 html 内容 - 调整 SkillsTags、ThinkingBubble、ToolBubble 和 SystemBubble 等组件样式和结构 - 移除 extension 中 markdown-it 实例及相关渲染逻辑,保证消息内容安全文本渲染 - 升级 package.json 添加 highlight.js 和 markdown-it-task-lists 依赖支持新功能
1 parent 0a3c0b2 commit a6b71de

31 files changed

Lines changed: 547 additions & 102 deletions

package-lock.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,8 @@
4848
"tsx": "^4.21.0",
4949
"typescript": "^6.0.3",
5050
"typescript-eslint": "^8.59.2"
51+
},
52+
"dependencies": {
53+
"@adobe/css-tools": "^4.5.0"
5154
}
5255
}

packages/core/src/common/openai-client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): {
7373
apiKey: settings.apiKey,
7474
baseURL: settings.baseURL || undefined,
7575
// eslint-disable-next-line @typescript-eslint/no-explicit-any
76-
fetch: (url: any, init: any) => undiciFetch(url, { ...init, dispatcher: keepAliveAgent }),
76+
fetch: ((url: any, init: any) =>
77+
undiciFetch(url, { ...init, dispatcher: keepAliveAgent })) as unknown as typeof fetch,
7778
});
7879
cachedOpenAIKey = cacheKey;
7980

packages/vscode-ide-companion/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@
102102
"@webview-rpc/react-query": "^1.0.17",
103103
"cmdk": "^1.1.1",
104104
"framer-motion": "^12.42.2",
105+
"highlight.js": "^11.11.1",
105106
"lucide-react": "^1.21.0",
106107
"markdown-it": "^14.2.0",
108+
"markdown-it-task-lists": "^2.1.1",
107109
"radix-ui": "^1.6.0",
108110
"react": "^19.2.0",
109111
"react-dom": "^19.2.0",

packages/vscode-ide-companion/src/extension.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as fs from "node:fs";
33
import * as path from "node:path";
44
import * as os from "node:os";
55
import OpenAI from "openai";
6-
import MarkdownIt from "markdown-it";
76
import type { SessionMessage } from "@vegamo/deepcode-core";
87
import {
98
SessionManager,
@@ -32,27 +31,21 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider {
3231

3332
private readonly context: vscode.ExtensionContext;
3433
private webviewView: vscode.WebviewView | undefined;
35-
private readonly md: MarkdownIt;
3634
private readonly sessionManager: SessionManager;
3735

3836
constructor(context: vscode.ExtensionContext) {
3937
this.context = context;
40-
this.md = new MarkdownIt({
41-
html: false,
42-
linkify: false,
43-
breaks: true,
44-
});
4538
this.sessionManager = new SessionManager({
4639
projectRoot: this.getWorkspaceRoot(),
4740
createOpenAIClient: () => this.createOpenAIClient(),
4841
getResolvedSettings: () => this.resolveCurrentSettings(),
49-
renderMarkdown: (text) => this.md.render(text),
42+
renderMarkdown: (text) => text,
5043
onAssistantMessage: (message: SessionMessage, shouldConnect: boolean) => {
5144
if (!this.webviewView) return;
52-
if (message.visible === false) return;
45+
if (!message.visible) return;
5346
if (message.role !== "tool") {
5447
const reasoningContent = (message.messageParams as ReasoningMessageParams | null)?.reasoning_content;
55-
message.html = this.md.render(message.content || reasoningContent || "");
48+
message.content = message.content || reasoningContent || "";
5649
}
5750
this.webviewView.webview.postMessage({ type: "appendMessage", message, shouldConnect });
5851
},
@@ -100,7 +93,6 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider {
10093
const rpcContext: RouterContext = {
10194
sessionManager: this.sessionManager,
10295
postMessage: (message) => this.webviewView?.webview.postMessage(message),
103-
renderMarkdown: (text) => this.md.render(text),
10496
copyToClipboard: (text) => void vscode.env.clipboard.writeText(text),
10597
openFileInEditor: (filePath, line) => this.openFileInEditor(filePath, line),
10698
getWorkspaceRoot: () => this.getWorkspaceRoot(),

packages/vscode-ide-companion/src/getWebviewContent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ function createDevHTML(nonce: string, uris: DevUris, csp: string): string {
7474
<meta name="viewport" content="width=device-width, initial-scale=1" />
7575
<title>Deep Code (Dev)</title>
7676
</head>
77-
<body>
77+
<body data-vscode-context='{"preventDefaultContextMenuItems": true}'>
7878
<div id="root"></div>
7979
8080
<script type="module" nonce="${nonce}">

packages/vscode-ide-companion/src/router.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { SessionManager, SkillInfo, UserToolPermission, PermissionScope } f
55
export interface RouterContext {
66
sessionManager: SessionManager;
77
postMessage: (message: unknown) => void;
8-
renderMarkdown: (text: string) => string;
98
copyToClipboard: (text: string) => void;
109
openFileInEditor: (filePath: string, line: number) => Promise<void>;
1110
getWorkspaceRoot: () => string;
@@ -81,9 +80,7 @@ export const appRouter = router({
8180
content: m.content,
8281
html:
8382
m.role !== "tool"
84-
? ctx.renderMarkdown(
85-
m.content || (m.messageParams as { reasoning_content?: string } | null)?.reasoning_content || ""
86-
)
83+
? m.content || (m.messageParams as { reasoning_content?: string } | null)?.reasoning_content || ""
8784
: undefined,
8885
meta: m.meta,
8986
}));
@@ -145,7 +142,8 @@ export const appRouter = router({
145142

146143
try {
147144
const userPrompt = {
148-
text: prompt,
145+
type: "userPrompt",
146+
prompt: prompt,
149147
skills: skills.length > 0 ? (skills as SkillInfo[]) : undefined,
150148
imageUrls: normalizedImages.length > 0 ? normalizedImages : undefined,
151149
permissions: permissions && permissions.length > 0 ? (permissions as UserToolPermission[]) : undefined,
@@ -158,7 +156,7 @@ export const appRouter = router({
158156
const message = error instanceof Error ? error.message : String(error);
159157
ctx.postMessage({
160158
type: "assistant",
161-
html: ctx.renderMarkdown(`Request failed: ${message}`),
159+
content: `Request failed: ${message}`,
162160
});
163161
return { ok: false, error: message };
164162
} finally {
@@ -197,13 +195,7 @@ export const appRouter = router({
197195
.filter((m) => m.visible)
198196
.map((m) => ({
199197
role: m.role,
200-
content: m.content,
201-
html:
202-
m.role !== "tool"
203-
? ctx.renderMarkdown(
204-
m.content || (m.messageParams as { reasoning_content?: string } | null)?.reasoning_content || ""
205-
)
206-
: undefined,
198+
content: m.content || (m.messageParams as { reasoning_content?: string } | null)?.reasoning_content || "",
207199
meta: m.meta,
208200
})),
209201
};

packages/vscode-ide-companion/src/webview/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Header from "@/webview/components/Header";
22
import Messages from "@/webview/components/Messages";
3-
import Composer from "@/webview/components/Composer";
3+
import InputPrompt from "@/webview/components/InputPrompt";
44
import ThinkingLiveBubble from "@/webview/components/ThinkingLiveBubble";
55
import PermissionPrompt from "@/webview/components/PermissionPrompt";
66
import { useChat } from "@/webview/context/ChatProvider";
@@ -40,7 +40,7 @@ export default function App() {
4040
shouldConnect={state.lastMessageRole !== null && state.lastMessageRole !== "user"}
4141
/>
4242
)}
43-
<Composer
43+
<InputPrompt
4444
loading={state.loading}
4545
selectedSkills={state.selectedSkills}
4646
availableSkills={state.skills}

packages/vscode-ide-companion/src/webview/components/AskUserQuestion.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import * as z from "zod";
1616
import { zodResolver } from "@hookform/resolvers/zod";
1717
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "./ui/card";
1818
import { buildAskUserQuestionReply } from "@/webview/utils";
19+
import { useChat } from "@/webview/context";
1920

2021
interface QuestionOption {
2122
label: string;
@@ -66,6 +67,7 @@ const formSchema = z.object({
6667
export type AnswerFormValues = z.infer<typeof formSchema>;
6768

6869
export default function AskUserQuestion({ toolData }: AskUserQuestionProps) {
70+
const { actions } = useChat();
6971
const questions = toolData.metadata?.questions || [];
7072

7173
const form = useForm<AnswerFormValues>({
@@ -99,8 +101,9 @@ export default function AskUserQuestion({ toolData }: AskUserQuestionProps) {
99101
return;
100102
}
101103
// sendUserPromptText(reply.text);
104+
actions.sendPrompt(reply?.text || "");
102105
},
103-
[form]
106+
[form, actions]
104107
);
105108

106109
if (questions.length === 0) {
@@ -144,7 +147,7 @@ export default function AskUserQuestion({ toolData }: AskUserQuestionProps) {
144147
/>
145148
<FieldContent>
146149
<FieldLabel htmlFor={`q-${qIdx}-${optIdx}`}>
147-
<span className="font-medium">{opt.label}</span>
150+
<span className="font-medium cursor-pointer">{opt.label}</span>
148151
</FieldLabel>
149152
{opt.description && (
150153
<FieldDescription className="block text-xs text-muted-foreground">

packages/vscode-ide-companion/src/webview/components/DiffPreview.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ export default function DiffPreview({ toolData }: DiffPreviewProps) {
3030

3131
return (
3232
<div className="space-y-2">
33-
{toolData.output && <div className="text-xs text-muted-foreground">{toolData.output.trim()}</div>}
33+
{toolData.output && <div className="text-xs text-muted-foreground pl-3">{toolData.output.trim()}</div>}
3434

35-
<div className="flex items-center gap-2 text-xs">
35+
<div className="flex items-center gap-2 text-xs pl-3">
3636
<span className="text-muted-foreground">File</span>
3737
<Button
38-
type="button"
38+
variant="link"
3939
className="text-(--vscode-textLink-foreground) hover:underline cursor-pointer border-none bg-transparent p-0 text-xs truncate"
4040
title={meta.file_path}
4141
>
@@ -58,7 +58,7 @@ export default function DiffPreview({ toolData }: DiffPreviewProps) {
5858
return (
5959
<div key={i} className={`flex px-2 py-0.5 ${cls}`}>
6060
<span className="w-4 shrink-0 select-none">{prefix}</span>
61-
<span className="truncate">{line.slice(1)}</span>
61+
<div className="w-auto text-wrap break-all flex-1">{line.slice(1)}</div>
6262
</div>
6363
);
6464
})}

0 commit comments

Comments
 (0)