diff --git a/src/panels/ICHelperPanel.ts b/src/panels/ICHelperPanel.ts index 11c57a7..0a45b08 100644 --- a/src/panels/ICHelperPanel.ts +++ b/src/panels/ICHelperPanel.ts @@ -281,6 +281,109 @@ export async function showICHelperPanel( } } break; + // 添加文件上下文 - 显示工作区文件列表 + case "addContextFile": + { + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (!workspaceFolder) { + vscode.window.showWarningMessage("请先打开一个工作区"); + break; + } + + // 获取工作区所有文件 + const files = await vscode.workspace.findFiles( + "**/*", + "**/node_modules/**" + ); + + panel.webview.postMessage({ + command: "showWorkspaceFileList", + files: files.map((uri) => ({ + path: uri.fsPath, + relativePath: vscode.workspace.asRelativePath(uri), + })), + }); + } + break; + // 添加文件夹上下文 - 显示工作区文件夹列表 + case "addContextFolder": + { + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + if (!workspaceFolder) { + vscode.window.showWarningMessage("请先打开一个工作区"); + break; + } + + // 获取工作区所有文件夹 + const fs = require("fs"); + const path = require("path"); + const folders: Array<{ path: string; relativePath: string }> = []; + + function scanFolders(dir: string, baseDir: string) { + try { + const items = fs.readdirSync(dir, { withFileTypes: true }); + for (const item of items) { + if (item.isDirectory() && item.name !== "node_modules" && !item.name.startsWith(".")) { + const fullPath = path.join(dir, item.name); + const relativePath = path.relative(baseDir, fullPath); + folders.push({ path: fullPath, relativePath }); + scanFolders(fullPath, baseDir); + } + } + } catch (error) { + console.error("扫描文件夹失败:", error); + } + } + + scanFolders(workspaceFolder.uri.fsPath, workspaceFolder.uri.fsPath); + + panel.webview.postMessage({ + command: "showWorkspaceFolderList", + folders: folders, + }); + } + break; + // 添加图片上下文 + case "addContextImage": + { + const imageUris = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: true, + openLabel: "选择图片", + filters: { + "图片文件": ["png", "jpg", "jpeg", "gif", "bmp", "svg", "webp"], + }, + }); + if (imageUris && imageUris.length > 0) { + panel.webview.postMessage({ + command: "contextImagesSelected", + images: imageUris.map((uri) => uri.fsPath), + }); + } + } + break; + // 添加文档库上下文 + case "addContextDocument": + { + const docUris = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: true, + openLabel: "选择文档", + filters: { + "文档文件": ["pdf", "doc", "docx", "txt", "md"], + "所有文件": ["*"], + }, + }); + if (docUris && docUris.length > 0) { + panel.webview.postMessage({ + command: "contextDocumentsSelected", + documents: docUris.map((uri) => uri.fsPath), + }); + } + } + break; // 新增:检查工作区状态 case "checkWorkspace": const hasWorkspace = !!( diff --git a/src/views/contextButton.ts b/src/views/contextButton.ts index b1d6fed..621069b 100644 --- a/src/views/contextButton.ts +++ b/src/views/contextButton.ts @@ -7,14 +7,78 @@ */ export function getContextButtonContent(): string { return ` -
- - 添加文件或代码片段作为上下文 +
+
+ + 添加文件、文件夹、图片或文档作为上下文 +
+ + +
+ +
+
+ + + + 文件 + + + +
+
+ + + + 文件夹 + + + +
+
+ + + + 图片 +
+
+ + + + 文档库 +
+
+ + + +
`; } @@ -24,6 +88,12 @@ export function getContextButtonContent(): string { */ export function getContextButtonStyles(): string { return ` + /* 上下文选择器容器 */ + .context-selector-wrapper { + position: relative; + display: inline-block; + } + /* 添加上下文按钮样式 */ .add-context-button { display: flex; @@ -45,15 +115,218 @@ export function getContextButtonStyles(): string { border-color: var(--vscode-focusBorder); } - .add-context-button svg { + .add-context-button svg.icon { width: 16px; height: 16px; color: #409eff; } + .add-context-button .dropdown-arrow { + width: 12px; + height: 12px; + transition: transform 0.2s ease; + } + + .add-context-button.active .dropdown-arrow { + transform: rotate(180deg); + } + .add-context-label { white-space: nowrap; } + + /* 上拉菜单样式 */ + .context-menu { + position: absolute; + bottom: calc(100% + 8px); + left: 0; + background: var(--vscode-dropdown-background); + border: 1px solid var(--vscode-dropdown-border); + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + min-width: 180px; + z-index: 1000; + display: none; + overflow: hidden; + } + + .context-menu.show { + display: block; + animation: slideUp 0.2s ease; + } + + @keyframes slideUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .context-menu-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 16px; + cursor: pointer; + transition: background 0.2s ease; + color: var(--vscode-foreground); + } + + .context-menu-item:hover { + background: var(--vscode-list-hoverBackground); + } + + .context-menu-item svg { + width: 18px; + height: 18px; + flex-shrink: 0; + color: var(--vscode-foreground); + opacity: 0.8; + } + + .context-menu-item span { + font-size: 13px; + white-space: nowrap; + flex: 1; + } + + .context-menu-item .arrow-right { + width: 14px; + height: 14px; + opacity: 0.6; + margin-left: auto; + } + + /* 列表视图样式 */ + .context-menu-list { + display: flex; + flex-direction: column; + max-height: 350px; + } + + .context-menu-list-header { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + border-bottom: 1px solid var(--vscode-panel-border); + } + + .context-menu-back { + width: 28px; + height: 28px; + padding: 0; + border: none; + background: transparent; + color: var(--vscode-foreground); + cursor: pointer; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + } + + .context-menu-back:hover { + background: var(--vscode-toolbar-hoverBackground); + } + + .context-menu-back svg { + width: 16px; + height: 16px; + } + + .context-menu-list-header span { + font-size: 14px; + font-weight: 500; + flex: 1; + } + + .context-menu-list-body { + flex: 1; + overflow-y: auto; + padding: 4px; + } + + .context-menu-list-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + cursor: pointer; + border-radius: 4px; + transition: background 0.2s ease; + } + + .context-menu-list-item:hover { + background: var(--vscode-list-hoverBackground); + } + + .context-menu-list-item.selected { + background: var(--vscode-list-activeSelectionBackground); + } + + .context-menu-list-item input[type="checkbox"] { + width: 14px; + height: 14px; + flex-shrink: 0; + } + + .context-menu-list-item label { + flex: 1; + font-size: 12px; + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .context-menu-list-footer { + padding: 8px 12px; + border-top: 1px solid var(--vscode-panel-border); + display: flex; + flex-direction: column; + gap: 8px; + } + + .context-menu-list-footer input { + width: 100%; + padding: 6px 10px; + background: var(--vscode-input-background); + border: 1px solid var(--vscode-input-border); + border-radius: 4px; + color: var(--vscode-input-foreground); + font-size: 12px; + box-sizing: border-box; + } + + .context-menu-list-actions { + display: flex; + justify-content: space-between; + align-items: center; + } + + .context-menu-list-footer span { + font-size: 12px; + color: var(--vscode-descriptionForeground); + } + + .context-menu-list-footer button { + padding: 4px 12px; + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + } + + .context-menu-list-footer button:hover { + background: var(--vscode-button-hoverBackground); + } `; } @@ -62,10 +335,174 @@ export function getContextButtonStyles(): string { */ export function getContextButtonScript(): string { return ` - // 添加上下文处理函数 - function handleAddContext() { - // 发送添加上下文请求到扩展 - vscode.postMessage({ command: 'addContext' }); + // 上下文菜单状态 + let currentListData = []; + let currentListType = ''; + let selectedItems = new Set(); + + // 切换上下文菜单显示/隐藏 + function toggleContextMenu() { + const menu = document.getElementById('contextMenu'); + const button = document.querySelector('.add-context-button'); + + if (menu && button) { + const isShown = menu.classList.contains('show'); + + if (isShown) { + menu.classList.remove('show'); + button.classList.remove('active'); + backToMainMenu(); // 关闭时回到主菜单 + } else { + menu.classList.add('show'); + button.classList.add('active'); + } + } } + + // 点击外部关闭菜单 + document.addEventListener('click', function(event) { + const wrapper = document.querySelector('.context-selector-wrapper'); + const menu = document.getElementById('contextMenu'); + const button = document.querySelector('.add-context-button'); + + if (wrapper && menu && button && !wrapper.contains(event.target)) { + menu.classList.remove('show'); + button.classList.remove('active'); + backToMainMenu(); + } + }); + + // 显示文件列表 + function showFileList() { + vscode.postMessage({ command: 'addContextFile' }); + } + + // 显示文件夹列表 + function showFolderList() { + vscode.postMessage({ command: 'addContextFolder' }); + } + + // 返回主菜单 + function backToMainMenu() { + const mainMenu = document.getElementById('contextMenuMain'); + const listView = document.getElementById('contextMenuList'); + + if (mainMenu && listView) { + mainMenu.style.display = 'block'; + listView.style.display = 'none'; + } + + selectedItems.clear(); + currentListData = []; + } + + // 切换到列表视图 + function switchToListView(title, type, data) { + const mainMenu = document.getElementById('contextMenuMain'); + const listView = document.getElementById('contextMenuList'); + const titleEl = document.getElementById('contextMenuListTitle'); + + if (mainMenu && listView && titleEl) { + mainMenu.style.display = 'none'; + listView.style.display = 'flex'; + titleEl.textContent = title; + + currentListType = type; + currentListData = data; + selectedItems.clear(); + + renderList(data); + updateSelectedCount(); + } + } + + // 渲染列表 + function renderList(data) { + const body = document.getElementById('contextMenuListBody'); + if (!body) return; + + body.innerHTML = data.map((item, index) => \` +
+ + +
+ \`).join(''); + } + + // 切换项选择 + function toggleItemSelection(index) { + const checkbox = document.getElementById('item-' + index); + const item = document.querySelectorAll('.context-menu-list-item')[index]; + + if (checkbox && item) { + checkbox.checked = !checkbox.checked; + + if (checkbox.checked) { + selectedItems.add(index); + item.classList.add('selected'); + } else { + selectedItems.delete(index); + item.classList.remove('selected'); + } + + updateSelectedCount(); + } + } + + // 更新选中数量 + function updateSelectedCount() { + const countEl = document.getElementById('contextMenuListCount'); + if (countEl) { + countEl.textContent = '已选择 ' + selectedItems.size + ' 项'; + } + } + + // 确认选择 + function confirmSelection() { + const selected = Array.from(selectedItems).map(index => currentListData[index]); + + if (selected.length > 0) { + selected.forEach(item => { + addContextItem(currentListType, item.path); + }); + } + + toggleContextMenu(); + } + + // 添加图片 + function handleAddImage() { + vscode.postMessage({ command: 'addContextImage' }); + toggleContextMenu(); + } + + // 添加文档 + function handleAddDocument() { + vscode.postMessage({ command: 'addContextDocument' }); + toggleContextMenu(); + } + + // 搜索功能 + const searchInput = document.getElementById('contextMenuSearch'); + if (searchInput) { + searchInput.addEventListener('input', function(e) { + const keyword = e.target.value.toLowerCase(); + const filtered = currentListData.filter(item => + item.relativePath.toLowerCase().includes(keyword) + ); + renderList(filtered); + }); + } + + // 处理后端消息 + window.addEventListener('message', event => { + const message = event.data; + + if (message.command === 'showWorkspaceFileList') { + switchToListView('选择文件', 'file', message.files); + } else if (message.command === 'showWorkspaceFolderList') { + switchToListView('选择文件夹', 'folder', message.folders); + } + }); `; } diff --git a/src/views/contextDisplay.ts b/src/views/contextDisplay.ts new file mode 100644 index 0000000..f1b0f7b --- /dev/null +++ b/src/views/contextDisplay.ts @@ -0,0 +1,225 @@ +/** + * 上下文显示组件 + * 用于显示已选择的文件、文件夹、图片和文档 + */ + +/** + * 获取上下文显示区域的 HTML 内容 + */ +export function getContextDisplayContent(): string { + return ` + + `; +} + +/** + * 获取上下文显示区域的样式 + */ +export function getContextDisplayStyles(): string { + return ` + /* 上下文显示区域 */ + .context-display-area { + margin-bottom: 8px; + padding: 8px; + background: rgba(128, 128, 128, 0.1); + border-radius: 6px; + border: 1px solid var(--vscode-input-border); + } + + .context-items-container { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + + /* 上下文项样式 */ + .context-item { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + background: var(--vscode-input-background); + border: 1px solid var(--vscode-input-border); + border-radius: 4px; + font-size: 12px; + color: var(--vscode-foreground); + max-width: 300px; + transition: all 0.2s ease; + } + + .context-item:hover { + background: var(--vscode-list-hoverBackground); + } + + .context-item svg { + width: 14px; + height: 14px; + flex-shrink: 0; + opacity: 0.8; + } + + .context-item-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .context-item-remove { + width: 14px; + height: 14px; + cursor: pointer; + opacity: 0.6; + transition: opacity 0.2s ease; + flex-shrink: 0; + } + + .context-item-remove:hover { + opacity: 1; + color: #f56c6c; + } + + /* 图片预览样式 */ + .context-item.image-item { + position: relative; + } + + .context-item-preview { + width: 40px; + height: 40px; + object-fit: cover; + border-radius: 3px; + border: 1px solid var(--vscode-input-border); + } + `; +} + +/** + * 获取上下文显示区域的脚本 + */ +export function getContextDisplayScript(): string { + return ` + // 存储上下文项 + let contextItems = []; + + // 获取文件图标 SVG + function getFileIcon() { + return ''; + } + + // 获取文件夹图标 SVG + function getFolderIcon() { + return ''; + } + + // 获取图片图标 SVG + function getImageIcon() { + return ''; + } + + // 获取文档图标 SVG + function getDocumentIcon() { + return ''; + } + + // 获取删除图标 SVG + function getRemoveIcon() { + return ''; + } + + // 提取文件名 + function getFileName(path) { + return path.split(/[\\\\/]/).pop(); + } + + // 添加上下文项 + function addContextItem(type, path) { + const id = Date.now() + Math.random(); + contextItems.push({ id, type, path }); + renderContextItems(); + } + + // 删除上下文项 + function removeContextItem(id) { + contextItems = contextItems.filter(item => item.id !== id); + renderContextItems(); + } + + // 渲染上下文项 + function renderContextItems() { + const container = document.getElementById('contextItemsContainer'); + const displayArea = document.getElementById('contextDisplayArea'); + + if (!container || !displayArea) return; + + if (contextItems.length === 0) { + displayArea.style.display = 'none'; + return; + } + + displayArea.style.display = 'block'; + container.innerHTML = contextItems.map(item => { + let icon = ''; + switch(item.type) { + case 'file': icon = getFileIcon(); break; + case 'folder': icon = getFolderIcon(); break; + case 'image': icon = getImageIcon(); break; + case 'document': icon = getDocumentIcon(); break; + } + + return \` +
+ \${icon} + \${getFileName(item.path)} + + \${getRemoveIcon()} + +
+ \`; + }).join(''); + } + + // 处理后端返回的文件选择结果 + window.addEventListener('message', event => { + const message = event.data; + + switch(message.command) { + case 'contextFilesSelected': + if (message.files && message.files.length > 0) { + message.files.forEach(file => addContextItem('file', file)); + } + break; + case 'contextFoldersSelected': + if (message.folders && message.folders.length > 0) { + message.folders.forEach(folder => addContextItem('folder', folder)); + } + break; + case 'contextImagesSelected': + if (message.images && message.images.length > 0) { + message.images.forEach(image => addContextItem('image', image)); + } + break; + case 'contextDocumentsSelected': + if (message.documents && message.documents.length > 0) { + message.documents.forEach(doc => addContextItem('document', doc)); + } + break; + } + }); + + // 获取所有上下文项(供发送消息时使用) + window.getContextItems = function() { + return contextItems; + }; + + // 清空上下文项(供清空对话时使用) + window.clearContextItems = function() { + contextItems = []; + renderContextItems(); + }; + `; +} diff --git a/src/views/inputArea.ts b/src/views/inputArea.ts index 5c90150..8aa6815 100644 --- a/src/views/inputArea.ts +++ b/src/views/inputArea.ts @@ -14,6 +14,11 @@ import { getContextButtonStyles, getContextButtonScript, } from "./contextButton"; +import { + getContextDisplayContent, + getContextDisplayStyles, + getContextDisplayScript, +} from "./contextDisplay"; import { getContextCompressContent, getContextCompressStyles, @@ -43,6 +48,8 @@ export function getInputAreaContent(
${getContextButtonContent()}
+ + ${getContextDisplayContent()}