feat:添加上下文功能实现
This commit is contained in:
@ -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 = !!(
|
||||
|
||||
@ -7,14 +7,78 @@
|
||||
*/
|
||||
export function getContextButtonContent(): string {
|
||||
return `
|
||||
<div class="tooltip">
|
||||
<button class="add-context-button" onclick="handleAddContext()">
|
||||
<svg t="1766915545722" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4994" width="200" height="200">
|
||||
<path d="M469.333333 469.333333V170.666667h85.333334v298.666666h298.666666v85.333334h-298.666666v298.666666h-85.333334v-298.666666H170.666667v-85.333334h298.666666z" fill="#8a8a8a" p-id="4995"></path>
|
||||
</svg>
|
||||
<span class="add-context-label">添加上下文</span>
|
||||
</button>
|
||||
<span class="tooltiptext">添加文件或代码片段作为上下文</span>
|
||||
<div class="context-selector-wrapper">
|
||||
<div class="tooltip">
|
||||
<button class="add-context-button" onclick="toggleContextMenu()">
|
||||
<svg t="1766915545722" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4994" width="200" height="200">
|
||||
<path d="M469.333333 469.333333V170.666667h85.333334v298.666666h298.666666v85.333334h-298.666666v298.666666h-85.333334v-298.666666H170.666667v-85.333334h298.666666z" fill="#8a8a8a" p-id="4995"></path>
|
||||
</svg>
|
||||
<span class="add-context-label">添加上下文</span>
|
||||
<svg class="dropdown-arrow" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M512 714.666667L213.333333 416l42.666667-42.666667L512 629.333333l256-256 42.666667 42.666667z" fill="currentColor"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="tooltiptext">添加文件、文件夹、图片或文档作为上下文</span>
|
||||
</div>
|
||||
|
||||
<!-- 上拉菜单 -->
|
||||
<div class="context-menu" id="contextMenu">
|
||||
<!-- 主菜单 -->
|
||||
<div class="context-menu-main" id="contextMenuMain">
|
||||
<div class="context-menu-item" onclick="showFileList()">
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326z m1.8 562H232V136h302v216c0 23.2 18.8 42 42 42h216v494z" fill="currentColor"/>
|
||||
</svg>
|
||||
<span>文件</span>
|
||||
<svg class="arrow-right" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M340.864 149.312l384 384-384 384-45.248-45.248L634.368 533.312 295.616 194.56z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="context-menu-item" onclick="showFolderList()">
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M880 298.4H521L403.7 186.2c-1.5-1.4-3.5-2.2-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32zM840 768H184V256h188.5l119.6 114.4H840V768z" fill="currentColor"/>
|
||||
</svg>
|
||||
<span>文件夹</span>
|
||||
<svg class="arrow-right" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M340.864 149.312l384 384-384 384-45.248-45.248L634.368 533.312 295.616 194.56z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="context-menu-item" onclick="handleAddImage()">
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32z m-40 632H136V232h752v560z m-120-240c0 55.2-44.8 100-100 100s-100-44.8-100-100 44.8-100 100-100 100 44.8 100 100z m-476 0l164 164h476L696 480 536 640l-84-84-160 160z" fill="currentColor"/>
|
||||
</svg>
|
||||
<span>图片</span>
|
||||
</div>
|
||||
<div class="context-menu-item" onclick="handleAddDocument()">
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32z m-40 824H232V136h560v752z m-120-568H352c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h320c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z m0 144H352c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h320c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z m0 144H352c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h320c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z" fill="currentColor"/>
|
||||
</svg>
|
||||
<span>文档库</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件/文件夹列表视图 -->
|
||||
<div class="context-menu-list" id="contextMenuList" style="display: none;">
|
||||
<div class="context-menu-list-header">
|
||||
<button class="context-menu-back" onclick="backToMainMenu()">
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8c-16.4 12.8-16.4 37.5 0 50.3l450.8 352.1c5.3 4.1 12.9 0.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z" fill="currentColor"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span id="contextMenuListTitle">选择文件</span>
|
||||
</div>
|
||||
<div class="context-menu-list-body" id="contextMenuListBody">
|
||||
<!-- 动态加载列表 -->
|
||||
</div>
|
||||
<div class="context-menu-list-footer">
|
||||
<input type="text" id="contextMenuSearch" placeholder="搜索..." />
|
||||
<div class="context-menu-list-actions">
|
||||
<span id="contextMenuListCount">已选择 0 项</span>
|
||||
<button class="primary" onclick="confirmSelection()">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -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) => \`
|
||||
<div class="context-menu-list-item" onclick="toggleItemSelection(\${index})">
|
||||
<input type="checkbox" id="item-\${index}" />
|
||||
<label for="item-\${index}">\${item.relativePath}</label>
|
||||
</div>
|
||||
\`).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);
|
||||
}
|
||||
});
|
||||
`;
|
||||
}
|
||||
|
||||
225
src/views/contextDisplay.ts
Normal file
225
src/views/contextDisplay.ts
Normal file
@ -0,0 +1,225 @@
|
||||
/**
|
||||
* 上下文显示组件
|
||||
* 用于显示已选择的文件、文件夹、图片和文档
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取上下文显示区域的 HTML 内容
|
||||
*/
|
||||
export function getContextDisplayContent(): string {
|
||||
return `
|
||||
<div class="context-display-area" id="contextDisplayArea" style="display: none;">
|
||||
<div class="context-items-container" id="contextItemsContainer">
|
||||
<!-- 动态添加的上下文项将显示在这里 -->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文显示区域的样式
|
||||
*/
|
||||
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 viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7z" fill="currentColor"/></svg>';
|
||||
}
|
||||
|
||||
// 获取文件夹图标 SVG
|
||||
function getFolderIcon() {
|
||||
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M880 298.4H521L403.7 186.2c-1.5-1.4-3.5-2.2-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32z" fill="currentColor"/></svg>';
|
||||
}
|
||||
|
||||
// 获取图片图标 SVG
|
||||
function getImageIcon() {
|
||||
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32z" fill="currentColor"/></svg>';
|
||||
}
|
||||
|
||||
// 获取文档图标 SVG
|
||||
function getDocumentIcon() {
|
||||
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32z" fill="currentColor"/></svg>';
|
||||
}
|
||||
|
||||
// 获取删除图标 SVG
|
||||
function getRemoveIcon() {
|
||||
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9c-4.4 5.2-.7 13.1 6.1 13.1h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z" fill="currentColor"/></svg>';
|
||||
}
|
||||
|
||||
// 提取文件名
|
||||
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 \`
|
||||
<div class="context-item" title="\${item.path}">
|
||||
\${icon}
|
||||
<span class="context-item-name">\${getFileName(item.path)}</span>
|
||||
<span class="context-item-remove" onclick="removeContextItem(\${item.id})">
|
||||
\${getRemoveIcon()}
|
||||
</span>
|
||||
</div>
|
||||
\`;
|
||||
}).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();
|
||||
};
|
||||
`;
|
||||
}
|
||||
@ -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(
|
||||
<div class="input-top-toolbar">
|
||||
${getContextButtonContent()}
|
||||
</div>
|
||||
<!-- 上下文显示区域 -->
|
||||
${getContextDisplayContent()}
|
||||
<textarea
|
||||
id="messageInput"
|
||||
placeholder="输入您的问题,按 Enter 发送,Shift + Enter 换行..."
|
||||
@ -76,6 +83,7 @@ export function getInputAreaStyles(): string {
|
||||
${getModeSelectorStyles()}
|
||||
${getModelSelectorStyles()}
|
||||
${getContextButtonStyles()}
|
||||
${getContextDisplayStyles()}
|
||||
${getContextCompressStyles()}
|
||||
${getOptimizeButtonStyles()}
|
||||
.input-area {
|
||||
@ -281,6 +289,7 @@ export function getInputAreaScript(): string {
|
||||
// 注意:getModeSelectorScript() 已在 webviewContent.ts 开头加载,这里不再重复加载
|
||||
${getModelSelectorScript()}
|
||||
${getContextButtonScript()}
|
||||
${getContextDisplayScript()}
|
||||
${getContextCompressScript()}
|
||||
${getOptimizeButtonScript()}
|
||||
|
||||
@ -391,6 +400,9 @@ export function getInputAreaScript(): string {
|
||||
const model = getCurrentModel(); // 从模型选择器组件获取当前模型
|
||||
const planMode = document.getElementById('planToggle')?.checked || false;
|
||||
|
||||
// 获取上下文项
|
||||
const contextItems = window.getContextItems ? window.getContextItems() : [];
|
||||
|
||||
addMessage(text, 'user');
|
||||
|
||||
// 标记已有消息,切换布局到底部
|
||||
@ -400,7 +412,14 @@ export function getInputAreaScript(): string {
|
||||
// 切换按钮为暂停状态
|
||||
setSendButtonState(true);
|
||||
|
||||
vscode.postMessage({ command: 'sendMessage', text: text, mode: mode, model: model, planMode: planMode });
|
||||
vscode.postMessage({
|
||||
command: 'sendMessage',
|
||||
text: text,
|
||||
mode: mode,
|
||||
model: model,
|
||||
planMode: planMode,
|
||||
contextItems: contextItems
|
||||
});
|
||||
messageInput.value = '';
|
||||
autoResizeTextarea(); // 重置输入框高度
|
||||
messageInput.focus();
|
||||
|
||||
Reference in New Issue
Block a user