Skip to content

Commit e4eda2f

Browse files
committed
feat(vscode-ide-companion): 优化聊天界面及消息处理功能
- 新增消息组件支持用户提问功能,添加相关回调处理 - AssistantBubble组件加入更多操作按钮和下拉菜单 - 移除BubbleDot中无用的connectToPrev属性,修正样式应用 - ToolBubble组件改为通过回调通知父组件显示问题轮播,避免重复分发 - 优化ThinkingLiveBubble,移除未使用的连接逻辑和部分时间计算代码 - InputPrompt组件精简样式,增加AI生成内容的文字提示 - 改正router.ts中userPrompt的text字段名错误 - SkillsPanel中按钮文本统一改为复数形式"Skills" - 调整SystemBubble和ThinkingBubble连接线渲染逻辑,避免无连接时显示线条 - 添加.vscode/tasks.json任务依赖,确保运行时自动启动watch任务 - wrpc客户端增加请求日志与错误捕获,方便调试RPC调用 - package.json添加发布文件清单,覆盖dist、assets和templates文件夹 - 多处组件调整小型UI细节,包括按钮图标尺寸统一改为size-3.5 - 删除无效console.log,多处捕获异常时添加错误输出方便定位问题
1 parent 84cdf40 commit e4eda2f

17 files changed

Lines changed: 127 additions & 92 deletions
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Generated by build, content mirrored from packages/core/templates/
2+
templates/

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"type": "npm",
66
"script": "dev:webview",
77
"isBackground": true,
8+
"dependsOn": ["npm: watch"],
89
"problemMatcher": {
910
"owner": "custom",
1011
"pattern": {

packages/vscode-ide-companion/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@
3434
"onStartupFinished",
3535
"onView:deepcode.chatView"
3636
],
37+
"files": [
38+
"dist/**",
39+
"assets/**",
40+
"templates/**",
41+
"LICENSE"
42+
],
3743
"contributes": {
3844
"commands": [
3945
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export const appRouter = router({
144144
try {
145145
const userPrompt = {
146146
type: "userPrompt",
147-
prompt: prompt,
147+
text: prompt,
148148
skills: skills.length > 0 ? (skills as SkillInfo[]) : undefined,
149149
imageUrls: normalizedImages.length > 0 ? normalizedImages : undefined,
150150
permissions: permissions && permissions.length > 0 ? (permissions as UserToolPermission[]) : undefined,

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1+
import { useCallback } from "react";
12
import Header from "@/webview/components/Header";
23
import Messages from "@/webview/components/Messages";
34
import InputPrompt from "@/webview/components/InputPrompt";
45
import ThinkingLiveBubble from "@/webview/components/ThinkingLiveBubble";
56
import PermissionPrompt from "@/webview/components/PermissionPrompt";
67
import { useChat } from "@/webview/context/ChatProvider";
78
import AskQuestionCarousel from "@/webview/components/AskQuestionCarousel";
9+
import type { AskUserQuestionMetadata } from "@/webview/components/bubbles/ToolBubble";
810

911
export default function App() {
1012
const { state, dispatch, actions } = useChat();
1113

14+
const handleAskUserQuestions = useCallback(
15+
(questions: AskUserQuestionMetadata["questions"]) => {
16+
dispatch({ type: "SET_ASK_USER_QUESTIONS", data: { questions } });
17+
},
18+
[dispatch]
19+
);
20+
1221
return (
1322
<div className="relative flex flex-col h-screen min-h-screen w-full overflow-hidden">
1423
<Header
@@ -17,7 +26,12 @@ export default function App() {
1726
onSelectSession={actions.selectSession}
1827
onCreateNewSession={actions.createNewSession}
1928
/>
20-
<Messages messages={state.messages} loading={state.loading} onEditMessage={actions.editMessage} />
29+
<Messages
30+
messages={state.messages}
31+
loading={state.loading}
32+
onEditMessage={actions.editMessage}
33+
onAskUserQuestions={handleAskUserQuestions}
34+
/>
2135
<PermissionPrompt
2236
askPermissions={state.askPermissions}
2337
sessionStatus={state.activeSessionStatus}
@@ -33,7 +47,7 @@ export default function App() {
3347
<ThinkingLiveBubble
3448
llmStreamProgress={state.llmStreamProgress}
3549
processes={state.processes}
36-
shouldConnect={state.lastMessageRole !== null && state.lastMessageRole !== "user"}
50+
// shouldConnect={state.lastMessageRole !== null && state.lastMessageRole !== "user"}
3751
/>
3852
)}
3953
{state.askUserQuestions && (
@@ -47,8 +61,8 @@ export default function App() {
4761
selectedSkills={state.selectedSkills}
4862
availableSkills={state.skills}
4963
pendingPermissionReply={state.pendingPermissionReply}
50-
askPermissions={state.askPermissions}
51-
activeSessionStatus={state.activeSessionStatus}
64+
// askPermissions={state.askPermissions}
65+
// activeSessionStatus={state.activeSessionStatus}
5266
tokenTelemetry={state.tokenTelemetry}
5367
activeEditor={state.activeEditor}
5468
editingMessage={state.editingMessage}

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,14 @@ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea } fro
99
import { Separator } from "@/webview/components/ui/separator";
1010
import { HoverCard, HoverCardContent, HoverCardTrigger } from "./ui/hover-card";
1111
import { cn } from "@/webview/lib/utils";
12-
import { Field, FieldGroup } from "./ui/field";
12+
import { Field, FieldDescription, FieldGroup } from "./ui/field";
1313
import { Spinner } from "@/webview/components/ui/spinner";
1414

1515
export interface InputPromptProps {
1616
loading: boolean;
1717
selectedSkills: SkillInfo[];
1818
availableSkills: SkillInfo[];
1919
pendingPermissionReply: unknown;
20-
askPermissions: unknown[];
21-
activeSessionStatus: string | null;
2220
tokenTelemetry?: TokenTelemetry;
2321
activeEditor: ActiveEditor | null;
2422
editingMessage: EditingMessage | null;
@@ -39,8 +37,6 @@ export default function InputPrompt({
3937
selectedSkills,
4038
availableSkills,
4139
pendingPermissionReply,
42-
askPermissions,
43-
activeSessionStatus,
4440
tokenTelemetry,
4541
activeEditor,
4642
editingMessage,
@@ -57,8 +53,6 @@ export default function InputPrompt({
5753
const textareaRef = useRef<HTMLTextAreaElement>(null);
5854
const { attachments, handlePaste, removeAttachment, clearAttachments, getImageUrls, loadImages } =
5955
usePromptAttachments();
60-
console.log("askPermissions:", askPermissions);
61-
console.log("activeSessionStatus:", activeSessionStatus);
6256

6357
// Rebuild history from messages when messages change (e.g., loading a session)
6458
useEffect(() => {
@@ -195,8 +189,8 @@ export default function InputPrompt({
195189
const hasContent = value.trim().length > 0 || attachments.length > 0;
196190

197191
return (
198-
<FieldGroup className="w-full max-w-237.5 mx-auto min-w-sm px-4 pt-1.5 pb-4">
199-
<Field>
192+
<FieldGroup className="w-full max-w-237.5 mx-auto min-w-sm px-4 pt-1.5 pb-1">
193+
<Field className="gap-0.5">
200194
<InputGroup>
201195
<div className="flex flex-col w-full">
202196
<InputGroupTextarea
@@ -283,6 +277,9 @@ export default function InputPrompt({
283277
/>
284278
<PromptAttachments attachments={attachments} onRemove={removeAttachment} />
285279
</InputGroup>
280+
<FieldDescription className="text-center text-[12px]">
281+
AI-generated content, for reference only
282+
</FieldDescription>
286283
</Field>
287284
</FieldGroup>
288285
);

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@ import { ScrollArea, ScrollBar } from "@/webview/components/ui/scroll-area";
33
import UserBubble from "@/webview/components/bubbles/UserBubble";
44
import AssistantBubble from "@/webview/components/bubbles/AssistantBubble";
55
import ThinkingBubble from "@/webview/components/bubbles/ThinkingBubble";
6-
import ToolBubble, { type ToolBubbleProps } from "@/webview/components/bubbles/ToolBubble";
6+
import ToolBubble, {
7+
type ToolBubbleProps,
8+
type AskUserQuestionMetadata,
9+
} from "@/webview/components/bubbles/ToolBubble";
710
import SystemBubble from "@/webview/components/bubbles/SystemBubble";
811
import type { SessionMessage, EditingMessage } from "@/webview/types";
912

1013
interface MessagesProps {
1114
messages: SessionMessage[];
1215
loading: boolean;
1316
onEditMessage?: (editing: EditingMessage) => void;
17+
onAskUserQuestions?: (questions: AskUserQuestionMetadata["questions"]) => void;
1418
}
1519

16-
export default function Messages({ messages, loading, onEditMessage }: MessagesProps) {
20+
export default function Messages({ messages, loading, onEditMessage, onAskUserQuestions }: MessagesProps) {
1721
const bottomRef = useRef<HTMLDivElement>(null);
1822

1923
useEffect(() => {
@@ -29,7 +33,6 @@ export default function Messages({ messages, loading, onEditMessage }: MessagesP
2933

3034
switch (msg.role) {
3135
case "user":
32-
console.log("msg:", msg);
3336
return (
3437
<UserBubble
3538
key={`msg-${index}`}
@@ -64,6 +67,7 @@ export default function Messages({ messages, loading, onEditMessage }: MessagesP
6467
meta={msg.meta as ToolBubbleProps["meta"]}
6568
shouldConnect={shouldConnect}
6669
isLastMessage={index === messages.length - 1}
70+
onAskUserQuestions={onAskUserQuestions}
6771
/>
6872
);
6973
case "system":

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default function SkillsPanel({ availableSkills, selectedSkills, onToggle
2525
return (
2626
<InputGroupButton disabled className="text-xs h-8 text-muted-foreground">
2727
<GraduationCap className="h-3 w-3" />
28-
<span>Skill</span>
28+
<span>Skills</span>
2929
</InputGroupButton>
3030
);
3131
}
@@ -34,7 +34,7 @@ export default function SkillsPanel({ availableSkills, selectedSkills, onToggle
3434
<>
3535
<InputGroupButton className="text-xs h-8 text-muted-foreground cursor-pointer" onClick={() => setOpen(true)}>
3636
<GraduationCap className="h-3 w-3" />
37-
<span>Skill</span>
37+
<span>Skills</span>
3838
</InputGroupButton>
3939
<CommandDialog open={open} onOpenChange={setOpen}>
4040
<Command autoFocus className="w-full" label="Skills command palette">

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

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { useEffect, useState, useRef, useCallback } from "react";
2-
import BubbleDot from "@/webview/components/bubbles/BubbleDot";
1+
import { useEffect, useState, useRef } from "react";
32
import { Spinner } from "@/webview/components/ui/spinner";
43
import type { LlmStreamProgressData } from "@/webview/types";
54
import ProgressShimmer from "@/webview/components/ProgressShimmer";
65

76
interface ThinkingLiveBubbleProps {
87
llmStreamProgress: LlmStreamProgressData | null;
98
processes: Record<string, { startTime: string; command: string }> | null;
10-
shouldConnect?: boolean;
119
}
1210

1311
function formatElapsed(startTime: string): string {
@@ -33,48 +31,10 @@ function getStatusText(
3331
return "Processing...";
3432
}
3533

36-
function formatElapsedTime(startTimeIso: string) {
37-
const startTime = new Date(startTimeIso).getTime();
38-
const elapsedMs = Date.now() - startTime;
39-
const elapsedSeconds = Math.floor(elapsedMs / 1000);
40-
const minutes = Math.floor(elapsedSeconds / 60);
41-
const seconds = elapsedSeconds % 60;
42-
if (minutes > 0) {
43-
return `${minutes}m${seconds}s`;
44-
}
45-
return `${seconds}s`;
46-
}
47-
48-
export default function ThinkingLiveBubble({
49-
llmStreamProgress,
50-
processes,
51-
shouldConnect = false,
52-
}: ThinkingLiveBubbleProps) {
34+
export default function ThinkingLiveBubble({ llmStreamProgress, processes }: ThinkingLiveBubbleProps) {
5335
const [statusText, setStatusText] = useState(() => getStatusText(llmStreamProgress, processes));
5436
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
5537

56-
const getThinkingContentText = useCallback(() => {
57-
if (processes && Object.keys(processes).length > 0) {
58-
const firstPid = Object.keys(processes)[0];
59-
const process = processes[firstPid];
60-
const elapsed = formatElapsedTime(process.startTime);
61-
return `(${elapsed}) ${process.command}`;
62-
}
63-
64-
if (llmStreamProgress && llmStreamProgress.startedAt) {
65-
const startedAt = new Date(llmStreamProgress.startedAt).getTime();
66-
const elapsedSeconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000));
67-
const formattedTokens = llmStreamProgress.formattedTokens || "0";
68-
if (elapsedSeconds >= 3) {
69-
return `(${elapsedSeconds}s) · ↓ ${formattedTokens} tokens`;
70-
}
71-
}
72-
73-
return "Processing...";
74-
}, [llmStreamProgress, processes]);
75-
76-
console.log("getThinkingContentText:", getThinkingContentText());
77-
7838
useEffect(() => {
7939
setStatusText(getStatusText(llmStreamProgress, processes));
8040
intervalRef.current = setInterval(() => {

packages/vscode-ide-companion/src/webview/components/bubbles/AssistantBubble.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ import { useState, useCallback } from "react";
22
import BubbleDot from "@/webview/components/bubbles/BubbleDot";
33
import { Button } from "@/webview/components/ui/button";
44
import Markdown from "@/webview/components/markdown";
5-
import { Check, Copy } from "lucide-react";
5+
import { ArchiveIcon, Check, Copy, MailCheckIcon, MoreHorizontalIcon, Trash2Icon } from "lucide-react";
66
import { Tooltip, TooltipContent, TooltipTrigger } from "@/webview/components/ui/tooltip";
7+
import {
8+
DropdownMenu,
9+
DropdownMenuContent,
10+
DropdownMenuGroup,
11+
DropdownMenuItem,
12+
DropdownMenuTrigger,
13+
} from "@/webview/components/ui/dropdown-menu";
714

815
interface AssistantBubbleProps {
916
content?: string;
@@ -29,10 +36,10 @@ export default function AssistantBubble({ content, shouldConnect = false }: Assi
2936

3037
return (
3138
<div className="flex gap-2 mb-3 group">
32-
<BubbleDot connectToPrev={shouldConnect} />
39+
<BubbleDot />
3340
<div className="flex-1 min-w-0">
3441
<Markdown>{content}</Markdown>
35-
<div className="invisible group-hover:visible text-sm mt-1">
42+
<div className="text-sm mt-1">
3643
<Tooltip>
3744
<TooltipTrigger asChild>
3845
<Button
@@ -42,13 +49,32 @@ export default function AssistantBubble({ content, shouldConnect = false }: Assi
4249
onClick={handleCopy}
4350
title={copied ? "Copied!" : "Copy"}
4451
>
45-
{copied ? <Check className="h-3 w-3 text-success" /> : <Copy className="h-3 w-3" />}
52+
{copied ? <Check className="size-3.5 text-success" /> : <Copy className="size-3.5" />}
4653
</Button>
4754
</TooltipTrigger>
4855
<TooltipContent side="bottom">
4956
<p>Copy</p>
5057
</TooltipContent>
5158
</Tooltip>
59+
<DropdownMenu>
60+
<DropdownMenuTrigger asChild>
61+
<Button variant="ghost" size="icon-sm" aria-label="More Options">
62+
<MoreHorizontalIcon className="size-3.5" />
63+
</Button>
64+
</DropdownMenuTrigger>
65+
<DropdownMenuContent align="start" className="w-40">
66+
<DropdownMenuGroup>
67+
<DropdownMenuItem className="text-xs">
68+
<MailCheckIcon />
69+
Feedback
70+
</DropdownMenuItem>
71+
<DropdownMenuItem className="text-xs">
72+
<ArchiveIcon />
73+
Copy Request ID
74+
</DropdownMenuItem>
75+
</DropdownMenuGroup>
76+
</DropdownMenuContent>
77+
</DropdownMenu>
5278
</div>
5379
</div>
5480
</div>

0 commit comments

Comments
 (0)