From b82ef5b73f10014c65724d0685f56cdba1dcbe85 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Fri, 21 Nov 2025 01:52:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(vscode-ide-companion):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=B8=8A=E4=B8=8B=E6=96=87=E9=99=84=E4=BB=B6=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ContextAttachmentManager 管理上下文附件 - 新增 ContextPills 组件用于显示上下文标签 - 支持文件、符号、选区等多种上下文类型 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/webview/ContextAttachmentManager.ts | 97 +++++++++++++++++++ .../src/webview/components/ContextPills.css | 79 +++++++++++++++ .../src/webview/components/ContextPills.tsx | 96 ++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 packages/vscode-ide-companion/src/webview/ContextAttachmentManager.ts create mode 100644 packages/vscode-ide-companion/src/webview/components/ContextPills.css create mode 100644 packages/vscode-ide-companion/src/webview/components/ContextPills.tsx diff --git a/packages/vscode-ide-companion/src/webview/ContextAttachmentManager.ts b/packages/vscode-ide-companion/src/webview/ContextAttachmentManager.ts new file mode 100644 index 00000000..41d5e6f3 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/ContextAttachmentManager.ts @@ -0,0 +1,97 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Context attachment types + * Based on vscode-copilot-chat implementation + */ +export interface ContextAttachment { + id: string; + type: 'file' | 'symbol' | 'selection' | 'variable'; + name: string; + value: string | { uri: string; range?: { start: number; end: number } }; + icon?: string; +} + +/** + * Manages context attachments for the chat + * Similar to ChatContextAttachments in vscode-copilot-chat + */ +export class ContextAttachmentManager { + private attachments: Map = new Map(); + private listeners: Array<(attachments: ContextAttachment[]) => void> = []; + + /** + * Add a context attachment + */ + addAttachment(attachment: ContextAttachment): void { + this.attachments.set(attachment.id, attachment); + this.notifyListeners(); + } + + /** + * Remove a context attachment + */ + removeAttachment(id: string): void { + this.attachments.delete(id); + this.notifyListeners(); + } + + /** + * Get all attachments + */ + getAttachments(): ContextAttachment[] { + return Array.from(this.attachments.values()); + } + + /** + * Check if an attachment exists + */ + hasAttachment(id: string): boolean { + return this.attachments.has(id); + } + + /** + * Clear all attachments + */ + clearAttachments(): void { + this.attachments.clear(); + this.notifyListeners(); + } + + /** + * Subscribe to attachment changes + */ + subscribe(listener: (attachments: ContextAttachment[]) => void): () => void { + this.listeners.push(listener); + return () => { + const index = this.listeners.indexOf(listener); + if (index > -1) { + this.listeners.splice(index, 1); + } + }; + } + + /** + * Notify all listeners of changes + */ + private notifyListeners(): void { + const attachments = this.getAttachments(); + this.listeners.forEach((listener) => listener(attachments)); + } + + /** + * Get context for message sending + */ + getContextForMessage(): Array> { + return this.getAttachments().map((att) => ({ + id: att.id, + type: att.type, + name: att.name, + value: att.value, + })); + } +} diff --git a/packages/vscode-ide-companion/src/webview/components/ContextPills.css b/packages/vscode-ide-companion/src/webview/components/ContextPills.css new file mode 100644 index 00000000..785b7bcc --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ContextPills.css @@ -0,0 +1,79 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +.context-pills-container { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 8px 12px; + background: var(--vscode-input-background, #3c3c3c); + border-bottom: 1px solid var(--vscode-input-border, #454545); +} + +.context-pill { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 8px 4px 6px; + background: var(--vscode-badge-background, #4d4d4d); + color: var(--vscode-badge-foreground, #ffffff); + border-radius: 12px; + font-size: 12px; + max-width: 200px; + transition: background-color 0.1s ease; +} + +.context-pill:hover { + background: var(--vscode-badge-background, #5a5a5a); +} + +.context-pill-icon { + display: flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + flex-shrink: 0; +} + +.context-pill-icon svg { + width: 14px; + height: 14px; +} + +.context-pill-label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.2; +} + +.context-pill-remove { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + padding: 0; + background: transparent; + border: none; + color: inherit; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.1s ease; + flex-shrink: 0; +} + +.context-pill-remove:hover { + opacity: 1; +} + +.context-pill-remove svg { + width: 12px; + height: 12px; +} + diff --git a/packages/vscode-ide-companion/src/webview/components/ContextPills.tsx b/packages/vscode-ide-companion/src/webview/components/ContextPills.tsx new file mode 100644 index 00000000..44a1821c --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ContextPills.tsx @@ -0,0 +1,96 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import type React from 'react'; +import type { ContextAttachment } from '../ContextAttachmentManager.js'; +import './ContextPills.css'; + +interface ContextPillsProps { + attachments: ContextAttachment[]; + onRemove: (id: string) => void; +} + +/** + * Display attached context as pills/chips + * Similar to ChatContextAttachments UI in vscode-copilot-chat + */ +export const ContextPills: React.FC = ({ + attachments, + onRemove, +}) => { + if (attachments.length === 0) { + return null; + } + + const getIcon = (type: string) => { + switch (type) { + case 'file': + return ( + + + + ); + case 'symbol': + return ( + + + + ); + case 'selection': + return ( + + + + ); + default: + return ( + + + + ); + } + }; + + return ( +
+ {attachments.map((attachment) => ( +
+
{getIcon(attachment.type)}
+
{attachment.name}
+ +
+ ))} +
+ ); +};