Skip to content

Commit 807879b

Browse files
committed
feat(webview): 支持会话重命名与删除及聊天面板独立打开
- 实现AlertDialog组件及相关子组件用于UI交互弹窗 - AssistantBubble组件新增反馈弹窗及请求ID复制功能 - ChatProvider新增renameSession与deleteSession方法,并调用chatService接口 - chatService新增重命名和删除会话相关RPC方法 - 在App组件中传递重命名与删除会话的action - Header组件新增重命名和删除会话的回调支持 - router层新增重命名、删除会话、打开聊天面板等RPC端点 - extension新增openChatPanel实现,支持面板独占显示及切换 - 添加ContextMenu组件库用于右键菜单拓展 - 修改InputPrompt类型支持SessionMessage类型 - 统一Messages组件中消息内容的空值处理 - 调整webview主题CSS变量以匹配侧边栏背景 - SessionList组件引入编辑与删除图标及相关回调支持 - 修正launch.json预启动任务名称以支持开发启动配置
1 parent e4eda2f commit 807879b

19 files changed

Lines changed: 1068 additions & 99 deletions

packages/vscode-ide-companion/.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"request": "launch",
88
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
99
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
10-
"preLaunchTask": "npm: dev:webview",
10+
"preLaunchTask": "Start Dev Watch",
1111
"presentation": {
1212
"group": "deepcode"
1313
}

packages/vscode-ide-companion/.vscode/tasks.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
{
22
"version": "2.0.0",
33
"tasks": [
4+
{
5+
"label": "Start Dev Watch",
6+
"dependsOn": ["npm: watch", "npm: dev:webview"],
7+
"dependsOrder": "parallel",
8+
"problemMatcher": []
9+
},
410
{
511
"type": "npm",
612
"script": "dev:webview",
713
"isBackground": true,
8-
"dependsOn": ["npm: watch"],
914
"problemMatcher": {
1015
"owner": "custom",
1116
"pattern": {
@@ -33,8 +38,8 @@
3338
},
3439
"background": {
3540
"activeOnStart": true,
36-
"beginsPattern": "^.*Watching.*$",
37-
"endsPattern": "^$"
41+
"beginsPattern": "^.*Copied core/templates.*$",
42+
"endsPattern": "^.*Watching for changes.*$"
3843
}
3944
},
4045
"group": "none"

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider {
3636

3737
private readonly context: vscode.ExtensionContext;
3838
private webviewView: vscode.WebviewView | undefined;
39+
private activeChatPanel: vscode.WebviewPanel | undefined;
3940
private readonly sessionManager: SessionManager;
4041

4142
constructor(context: vscode.ExtensionContext) {
@@ -132,6 +133,47 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider {
132133
}
133134
return null;
134135
},
136+
openChatPanel: (sessionId, viewColumn) => {
137+
// Dispose previous chat panel if exists (mutual exclusion)
138+
if (this.activeChatPanel) {
139+
this.activeChatPanel.dispose();
140+
this.activeChatPanel = undefined;
141+
}
142+
143+
const panel = vscode.window.createWebviewPanel(
144+
"deepcode.chatPanel",
145+
"Deep Code",
146+
{ viewColumn: viewColumn as unknown as vscode.ViewColumn, preserveFocus: true },
147+
{
148+
enableScripts: true,
149+
retainContextWhenHidden: true,
150+
localResourceRoots: [vscode.Uri.file(path.join(this.context.extensionPath, "dist", "webview"))],
151+
}
152+
);
153+
154+
// Track panel and clean up on dispose
155+
this.activeChatPanel = panel;
156+
panel.onDidDispose(() => {
157+
if (this.activeChatPanel === panel) {
158+
this.activeChatPanel = undefined;
159+
}
160+
});
161+
162+
// Build RPC context for this panel
163+
const panelContext: RouterContext = {
164+
...rpcContext,
165+
postMessage: (message) => panel.webview.postMessage(message),
166+
};
167+
168+
// Attach RPC router and load content
169+
attachRouterToPanel(appRouter, panel, panelContext);
170+
getWebviewContent(this.context, panel.webview).then((html) => {
171+
panel.webview.html = html;
172+
});
173+
174+
// Switch to this session
175+
this.sessionManager.setActiveSessionId(sessionId);
176+
},
135177
};
136178

137179
// Attach RPC router

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { initWRPC } from "@webview-rpc/host";
2+
import * as os from "node:os";
3+
import * as path from "node:path";
24
import * as vscodeApi from "vscode";
35
import z from "zod";
6+
import { getProjectCode } from "@vegamo/deepcode-core";
47
import type { SessionManager, SkillInfo, UserToolPermission, PermissionScope } from "@vegamo/deepcode-core";
58

69
export interface RouterContext {
@@ -11,6 +14,7 @@ export interface RouterContext {
1114
getWorkspaceRoot: () => string;
1215
openSettings: () => Promise<void>;
1316
getActiveEditor: () => { fileName: string; languageId: string; lineCount: number } | null;
17+
openChatPanel: (sessionId: string, viewColumn: number) => void;
1418
}
1519

1620
export const { router, procedure } = initWRPC.context<RouterContext>().create();
@@ -38,6 +42,11 @@ function serializeProcesses(
3842
return serialized;
3943
}
4044

45+
function getSessionJsonlPath(workspaceRoot: string, sessionId: string): string {
46+
const projectCode = getProjectCode(workspaceRoot);
47+
return path.join(os.homedir(), ".deepcode", "projects", projectCode, `${sessionId}.jsonl`);
48+
}
49+
4150
const sendPromptInput = z.object({
4251
prompt: z.string().default(""),
4352
skills: z
@@ -77,6 +86,9 @@ export const appRouter = router({
7786
.listSessionMessages(activeSessionId)
7887
.filter((m) => m.visible)
7988
.map((m) => ({
89+
...m,
90+
id: m.id,
91+
sessionId: m.sessionId,
8092
role: m.role,
8193
content: m.content,
8294
html:
@@ -195,6 +207,7 @@ export const appRouter = router({
195207
messages: messages
196208
.filter((m) => m.visible)
197209
.map((m) => ({
210+
...m,
198211
role: m.role,
199212
content: m.content || (m.messageParams as { reasoning_content?: string } | null)?.reasoning_content || "",
200213
meta: m.meta,
@@ -264,6 +277,62 @@ export const appRouter = router({
264277
ctx.sessionManager.addSessionSystemMessage(activeSessionId, input.content, true, input.meta);
265278
return { ok: true };
266279
}),
280+
281+
renameSession: procedure
282+
.input(
283+
z.object({
284+
sessionId: z.string(),
285+
summary: z.string().min(1, "Summary cannot be empty"),
286+
})
287+
)
288+
.resolve(({ ctx, input }) => {
289+
const ok = ctx.sessionManager.renameSession(input.sessionId, input.summary);
290+
if (!ok) {
291+
return { ok: false, error: "Session not found or empty summary" };
292+
}
293+
return { ok: true };
294+
}),
295+
296+
deleteSession: procedure.input(z.object({ sessionId: z.string() })).resolve(async ({ ctx, input }) => {
297+
const activeSessionId = ctx.sessionManager.getActiveSessionId();
298+
const ok = ctx.sessionManager.deleteSession(input.sessionId);
299+
if (!ok) {
300+
return { ok: false, error: "Session not found" };
301+
}
302+
// If the deleted session was the active one, clear the active session
303+
if (activeSessionId === input.sessionId) {
304+
ctx.sessionManager.setActiveSessionId(null);
305+
}
306+
return { ok: true, wasActiveSession: activeSessionId === input.sessionId };
307+
}),
308+
309+
getSessionFilePath: procedure.input(z.object({ sessionId: z.string() })).resolve(({ ctx, input }) => {
310+
const filePath = getSessionJsonlPath(ctx.getWorkspaceRoot(), input.sessionId);
311+
return { filePath };
312+
}),
313+
314+
openChatPanel: procedure
315+
.input(
316+
z.object({
317+
sessionId: z.string(),
318+
viewColumn: z.number().describe("ViewColumn: 1=Active, 2=Beside"),
319+
})
320+
)
321+
.resolve(({ ctx, input }) => {
322+
ctx.openChatPanel(input.sessionId, input.viewColumn);
323+
return { ok: true };
324+
}),
325+
326+
openChatInNewWindow: procedure.input(z.object({ sessionId: z.string() })).resolve(async ({ ctx, input }) => {
327+
// Open in current window's editor area (fills it like a dedicated window)
328+
ctx.openChatPanel(input.sessionId, vscodeApi.ViewColumn.Active);
329+
return { ok: true };
330+
}),
331+
332+
openExternal: procedure.input(z.object({ url: z.string() })).resolve(async ({ input }) => {
333+
await vscodeApi.env.openExternal(vscodeApi.Uri.parse(input.url));
334+
return { ok: true };
335+
}),
267336
});
268337

269338
export type AppRouter = typeof appRouter;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export default function App() {
2525
activeSessionId={state.activeSessionId}
2626
onSelectSession={actions.selectSession}
2727
onCreateNewSession={actions.createNewSession}
28+
onRenameSession={actions.renameSession}
29+
onDeleteSession={actions.deleteSession}
2830
/>
2931
<Messages
3032
messages={state.messages}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,18 @@ interface HeaderProps {
1212
activeSessionId: string | null;
1313
onSelectSession: (sessionId: string) => void;
1414
onCreateNewSession: () => void;
15+
onRenameSession: (sessionId: string, summary: string) => void;
16+
onDeleteSession: (sessionId: string) => void;
1517
}
1618

17-
export default function Header({ sessions, activeSessionId, onSelectSession, onCreateNewSession }: HeaderProps) {
19+
export default function Header({
20+
sessions,
21+
activeSessionId,
22+
onSelectSession,
23+
onCreateNewSession,
24+
onRenameSession,
25+
onDeleteSession,
26+
}: HeaderProps) {
1827
const handleSelect = useCallback(
1928
(sessionId: string) => {
2029
onSelectSession(sessionId);
@@ -76,6 +85,8 @@ export default function Header({ sessions, activeSessionId, onSelectSession, onC
7685
activeSessionId={activeSessionId}
7786
onSelect={handleSelect}
7887
onCreateNewSession={onCreateNewSession}
88+
onRename={onRenameSession}
89+
onDelete={onDeleteSession}
7990
/>
8091
</div>
8192
</div>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SkillsPanel from "@/webview/components/SkillsPanel";
33
import SkillsTags from "@/webview/components/SkillsTags";
44
import ContextIndicator from "@/webview/components/ContextIndicator";
55
import { PromptAttachments, usePromptAttachments } from "@/webview/components/PromptAttachments";
6-
import type { ActiveEditor, EditingMessage, SkillInfo, TokenTelemetry } from "@/webview/types";
6+
import type { ActiveEditor, EditingMessage, SessionMessage, SkillInfo, TokenTelemetry } from "@/webview/types";
77
import { FileCodeIcon, Reply, Square } from "lucide-react";
88
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea } from "@/webview/components/ui/input-group";
99
import { Separator } from "@/webview/components/ui/separator";
@@ -20,7 +20,7 @@ export interface InputPromptProps {
2020
tokenTelemetry?: TokenTelemetry;
2121
activeEditor: ActiveEditor | null;
2222
editingMessage: EditingMessage | null;
23-
messages?: { role: string; content: string }[]; // Historical messages for history navigation
23+
messages?: SessionMessage[]; // Historical messages for history navigation
2424
onSendPrompt: (
2525
prompt: string,
2626
skills?: SkillInfo[],

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import ToolBubble, {
88
type AskUserQuestionMetadata,
99
} from "@/webview/components/bubbles/ToolBubble";
1010
import SystemBubble from "@/webview/components/bubbles/SystemBubble";
11-
import type { SessionMessage, EditingMessage } from "@/webview/types";
11+
import type { EditingMessage, SessionMessage } from "@/webview/types";
1212

1313
interface MessagesProps {
1414
messages: SessionMessage[];
@@ -55,15 +55,17 @@ export default function Messages({ messages, loading, onEditMessage, onAskUserQu
5555
case "assistant": {
5656
const meta = msg.meta as { asThinking?: boolean } | undefined;
5757
if (meta?.asThinking) {
58-
return <ThinkingBubble key={`msg-${index}`} content={msg.content} shouldConnect={shouldConnect} />;
58+
return (
59+
<ThinkingBubble key={`msg-${index}`} content={msg.content || ""} shouldConnect={shouldConnect} />
60+
);
5961
}
60-
return <AssistantBubble key={`msg-${index}`} content={msg.content} shouldConnect={shouldConnect} />;
62+
return <AssistantBubble key={`msg-${index}`} message={msg} />;
6163
}
6264
case "tool":
6365
return (
6466
<ToolBubble
6567
key={`msg-${index}`}
66-
content={msg.content}
68+
content={msg.content || ""}
6769
meta={msg.meta as ToolBubbleProps["meta"]}
6870
shouldConnect={shouldConnect}
6971
isLastMessage={index === messages.length - 1}
@@ -74,7 +76,7 @@ export default function Messages({ messages, loading, onEditMessage, onAskUserQu
7476
return (
7577
<SystemBubble
7678
key={`msg-${index}`}
77-
content={msg.content}
79+
content={msg.content || ""}
7880
meta={msg.meta}
7981
shouldConnect={shouldConnect}
8082
/>

packages/vscode-ide-companion/src/webview/components/SessionList.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ describe("SessionList", () => {
9898
activeSessionId: null,
9999
onSelect: vi.fn(),
100100
onCreateNewSession: vi.fn(),
101+
onRename: vi.fn(),
102+
onDelete: vi.fn(),
101103
};
102104

103105
beforeEach(() => {

0 commit comments

Comments
 (0)