feat: 实现文档集管理系统
新增功能: - 文档集创建:支持添加 .md/.txt/.v/.sv/.pdf 格式文件 - 智能限制:单文件 10MB,总容量 50MB,最多 1000 个文件 - 状态管理:实时显示索引状态(待索引/索引中/已索引) - 交互优化:支持文件预览、删除操作,带 loading 动画 技术实现: - 新增 docsetDialog 组件处理文档集对话框 - 新增 contextSettingsComponent 管理上下文设置 - 扩展 contextHelper 支持文档集 CRUD 操作 - 优化 messageRouter 处理文档集相关消息
This commit is contained in:
@ -102,3 +102,46 @@ export async function handleAddContextDocument(panel: vscode.WebviewPanel) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function handleAddContextDocumentSet(panel: vscode.WebviewPanel) {
|
||||||
|
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||||
|
if (!workspaceFolder) {
|
||||||
|
vscode.window.showWarningMessage("请先打开一个工作区");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await vscode.workspace.findFiles(
|
||||||
|
"**/*.{md,txt,pdf,doc,docx}",
|
||||||
|
"**/node_modules/**",
|
||||||
|
);
|
||||||
|
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "showWorkspaceDocumentSetList",
|
||||||
|
files: files.map((uri) => ({
|
||||||
|
path: uri.fsPath,
|
||||||
|
relativePath: vscode.workspace.asRelativePath(uri),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleGetDocumentSetList(panel: vscode.WebviewPanel) {
|
||||||
|
const documents = getDocumentSet();
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "showDocumentSetList",
|
||||||
|
documents: documents,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let documentSet: Array<{ path: string; relativePath: string }> = [];
|
||||||
|
|
||||||
|
export function getDocumentSet() {
|
||||||
|
return documentSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addToDocumentSet(docs: Array<{ path: string; relativePath: string }>) {
|
||||||
|
documentSet = [...documentSet, ...docs];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveDocumentSet(docs: Array<{ path: string; relativePath: string }>) {
|
||||||
|
documentSet = docs;
|
||||||
|
}
|
||||||
|
|||||||
@ -32,6 +32,8 @@ import {
|
|||||||
handleAddContextFolder,
|
handleAddContextFolder,
|
||||||
handleAddContextImage,
|
handleAddContextImage,
|
||||||
handleAddContextDocument,
|
handleAddContextDocument,
|
||||||
|
handleAddContextDocumentSet,
|
||||||
|
handleGetDocumentSetList,
|
||||||
} from "./contextHelper";
|
} from "./contextHelper";
|
||||||
import { openFile, openFileWithSelection, openFilePathTag } from "./fileHelper";
|
import { openFile, openFileWithSelection, openFilePathTag } from "./fileHelper";
|
||||||
|
|
||||||
@ -356,6 +358,90 @@ export async function handleWebviewMessage(
|
|||||||
await handleAddContextFolder(panel);
|
await handleAddContextFolder(panel);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "addContextDocumentSet":
|
||||||
|
await handleAddContextDocumentSet(panel);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "getDocumentSetList":
|
||||||
|
await handleGetDocumentSetList(panel);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "saveDocumentSet":
|
||||||
|
const { saveDocumentSet } = await import("./contextHelper");
|
||||||
|
saveDocumentSet(message.documents || []);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "openContextSettings":
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "openSettingsTab",
|
||||||
|
tab: "context"
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "selectFilesForDocset":
|
||||||
|
const uris = await vscode.window.showOpenDialog({
|
||||||
|
canSelectMany: true,
|
||||||
|
canSelectFiles: true,
|
||||||
|
canSelectFolders: false,
|
||||||
|
openLabel: "选择文件",
|
||||||
|
filters: {
|
||||||
|
"支持的文件": ["md", "txt", "v", "sv", "pdf"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (uris && uris.length > 0) {
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const selectedFiles: Array<{ path: string; relativePath: string; size: number }> = [];
|
||||||
|
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||||
|
const MAX_TOTAL_SIZE = 50 * 1024 * 1024; // 50 MB
|
||||||
|
const MAX_FILES = 1000;
|
||||||
|
let totalSize = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
for (const uri of uris) {
|
||||||
|
const filePath = uri.fsPath;
|
||||||
|
const ext = path.extname(filePath).toLowerCase();
|
||||||
|
|
||||||
|
if (![".md", ".txt", ".v", ".sv", ".pdf"].includes(ext)) {
|
||||||
|
errors.push(`文件 ${path.basename(filePath)} 格式不支持`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stat = fs.statSync(filePath);
|
||||||
|
if (stat.size > MAX_FILE_SIZE) {
|
||||||
|
errors.push(`文件 ${path.basename(filePath)} 超过 10 MB`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (totalSize + stat.size > MAX_TOTAL_SIZE) {
|
||||||
|
errors.push("文档集总大小超过 50 MB");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (selectedFiles.length >= MAX_FILES) {
|
||||||
|
errors.push("文件数量超过 1000 个");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedFiles.push({
|
||||||
|
path: filePath,
|
||||||
|
relativePath: vscode.workspace.asRelativePath(filePath),
|
||||||
|
size: stat.size,
|
||||||
|
});
|
||||||
|
totalSize += stat.size;
|
||||||
|
} catch (err) {
|
||||||
|
errors.push(`无法读取文件 ${path.basename(filePath)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "filesSelectedForDocset",
|
||||||
|
files: selectedFiles,
|
||||||
|
errors: errors.length > 0 ? errors : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case "addContextImage":
|
case "addContextImage":
|
||||||
await handleAddContextImage(panel);
|
await handleAddContextImage(panel);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -41,18 +41,15 @@ export function getContextButtonContent(): string {
|
|||||||
<path d="M340.864 149.312l384 384-384 384-45.248-45.248L634.368 533.312 295.616 194.56z" fill="currentColor"/>
|
<path d="M340.864 149.312l384 384-384 384-45.248-45.248L634.368 533.312 295.616 194.56z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="context-menu-item" onclick="handleAddImage()">
|
<div class="context-menu-item" onclick="handleAddDocumentSet()">
|
||||||
<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">
|
<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"/>
|
<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>
|
</svg>
|
||||||
<span>文档库</span>
|
<span>文档集</span>
|
||||||
</div> -->
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- 文件/文件夹列表视图 -->
|
<!-- 文件/文件夹列表视图 -->
|
||||||
@ -382,6 +379,48 @@ export function getContextButtonScript(): string {
|
|||||||
vscode.postMessage({ command: 'addContextFolder' });
|
vscode.postMessage({ command: 'addContextFolder' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示文档集列表
|
||||||
|
function showDocumentSetList() {
|
||||||
|
vscode.postMessage({ command: 'getDocumentSetList' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示文档集视图
|
||||||
|
function showDocumentSetView(documents) {
|
||||||
|
const mainMenu = document.getElementById('contextMenuMain');
|
||||||
|
const listView = document.getElementById('contextMenuList');
|
||||||
|
const titleEl = document.getElementById('contextMenuListTitle');
|
||||||
|
const bodyEl = document.getElementById('contextMenuListBody');
|
||||||
|
|
||||||
|
if (mainMenu && listView && titleEl && bodyEl) {
|
||||||
|
mainMenu.style.display = 'none';
|
||||||
|
listView.style.display = 'flex';
|
||||||
|
titleEl.textContent = '文档集';
|
||||||
|
|
||||||
|
if (documents.length === 0) {
|
||||||
|
bodyEl.innerHTML = \`
|
||||||
|
<div class="context-empty" style="padding: 40px 20px; text-align: center;">
|
||||||
|
<p style="margin: 0 0 12px 0; color: var(--vscode-descriptionForeground);">暂无文档</p>
|
||||||
|
<button class="context-add-empty-btn" onclick="addDocumentToSet()" style="padding: 8px 20px; background: transparent; color: var(--vscode-textLink-foreground); border: 1px solid var(--vscode-textLink-foreground); border-radius: 4px; cursor: pointer; font-size: 13px;">添加文档集</button>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
} else {
|
||||||
|
currentListType = 'documentSetItem';
|
||||||
|
currentListData = documents;
|
||||||
|
filteredListData = documents;
|
||||||
|
selectedItems.clear();
|
||||||
|
renderList(documents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文档到文档集
|
||||||
|
function addDocumentToSet() {
|
||||||
|
vscode.postMessage({ command: 'openContextSettings' });
|
||||||
|
toggleContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文档集项到上下文(已删除,使用统一的确认选择)
|
||||||
|
|
||||||
// 返回主菜单
|
// 返回主菜单
|
||||||
function backToMainMenu() {
|
function backToMainMenu() {
|
||||||
const mainMenu = document.getElementById('contextMenuMain');
|
const mainMenu = document.getElementById('contextMenuMain');
|
||||||
@ -478,9 +517,16 @@ export function getContextButtonScript(): string {
|
|||||||
const selected = currentListData.filter(item => selectedItems.has(item.path));
|
const selected = currentListData.filter(item => selectedItems.has(item.path));
|
||||||
|
|
||||||
if (selected.length > 0) {
|
if (selected.length > 0) {
|
||||||
selected.forEach(item => {
|
if (currentListType === 'documentSet') {
|
||||||
addContextItem(currentListType, item.path, item.relativePath || item.path);
|
vscode.postMessage({
|
||||||
});
|
command: 'saveDocumentSet',
|
||||||
|
documents: selected
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
selected.forEach(item => {
|
||||||
|
addContextItem(currentListType === 'documentSetItem' ? 'file' : currentListType, item.path, item.relativePath || item.path);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
const menu = document.getElementById('contextMenu');
|
const menu = document.getElementById('contextMenu');
|
||||||
@ -507,6 +553,11 @@ export function getContextButtonScript(): string {
|
|||||||
toggleContextMenu();
|
toggleContextMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加文档集
|
||||||
|
function handleAddDocumentSet() {
|
||||||
|
showDocumentSetList();
|
||||||
|
}
|
||||||
|
|
||||||
// 搜索功能
|
// 搜索功能
|
||||||
const searchInput = document.getElementById('contextMenuSearch');
|
const searchInput = document.getElementById('contextMenuSearch');
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
@ -527,6 +578,10 @@ export function getContextButtonScript(): string {
|
|||||||
switchToListView('选择文件', 'file', message.files);
|
switchToListView('选择文件', 'file', message.files);
|
||||||
} else if (message.command === 'showWorkspaceFolderList') {
|
} else if (message.command === 'showWorkspaceFolderList') {
|
||||||
switchToListView('选择文件夹', 'folder', message.folders);
|
switchToListView('选择文件夹', 'folder', message.folders);
|
||||||
|
} else if (message.command === 'showWorkspaceDocumentSetList') {
|
||||||
|
switchToListView('选择文档', 'documentSet', message.files);
|
||||||
|
} else if (message.command === 'showDocumentSetList') {
|
||||||
|
showDocumentSetView(message.documents || []);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
|
|||||||
119
src/views/contextSettingsComponent.ts
Normal file
119
src/views/contextSettingsComponent.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* 上下文设置组件
|
||||||
|
* 功能:管理文档集
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
getDocsetDialogContent,
|
||||||
|
getDocsetDialogStyles,
|
||||||
|
getDocsetDialogScript,
|
||||||
|
} from "./docsetDialog";
|
||||||
|
|
||||||
|
export function getContextSettingsComponentContent(): string {
|
||||||
|
return `
|
||||||
|
<div class="context-settings">
|
||||||
|
<div class="context-header">
|
||||||
|
<h3>上下文</h3>
|
||||||
|
</div>
|
||||||
|
<div class="context-section">
|
||||||
|
<div class="context-section-header">
|
||||||
|
<span>Docs</span>
|
||||||
|
<button class="context-add-btn" onclick="openAddDocumentSetDialog()">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M469.333333 469.333333V170.666667h85.333334v298.666666h298.666666v85.333334h-298.666666v298.666666h-85.333334v-298.666666H170.666667v-85.333334h298.666666z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
添加文档集
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="context-docs-list" id="contextDocsList">
|
||||||
|
<div class="context-empty">
|
||||||
|
<p>暂无文档集</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${getDocsetDialogContent()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContextSettingsComponentStyles(): string {
|
||||||
|
return `
|
||||||
|
.context-settings {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-header h3 {
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-section {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid var(--vscode-panel-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-section-header span {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-add-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--vscode-textLink-foreground);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-add-btn:hover {
|
||||||
|
background: var(--vscode-toolbar-hoverBackground);
|
||||||
|
color: var(--vscode-textLink-activeForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-add-btn svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-docs-list {
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-empty p {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${getDocsetDialogStyles()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContextSettingsComponentScript(): string {
|
||||||
|
return getDocsetDialogScript();
|
||||||
|
}
|
||||||
300
src/views/docsetDialog.ts
Normal file
300
src/views/docsetDialog.ts
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
/**
|
||||||
|
* 文档集对话框组件
|
||||||
|
* 功能:添加文档集的对话框
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getDocsetDialogContent(): string {
|
||||||
|
return `
|
||||||
|
<div class="docset-dialog" id="docsetDialog">
|
||||||
|
<div class="docset-dialog-overlay" onclick="closeDocsetDialog()"></div>
|
||||||
|
<div class="docset-dialog-content">
|
||||||
|
<div class="docset-dialog-header">
|
||||||
|
<h3>添加文档集</h3>
|
||||||
|
<button onclick="closeDocsetDialog()">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="docset-dialog-body">
|
||||||
|
<div class="docset-form-group">
|
||||||
|
<label>名称</label>
|
||||||
|
<input type="text" id="docsetName" placeholder="输入文档集名称" />
|
||||||
|
</div>
|
||||||
|
<div class="docset-form-group">
|
||||||
|
<label>文件</label>
|
||||||
|
<div class="docset-hint">支持 .md/.txt/.v/.sv/.pdf,单个文件最大 10 MB,文档集最大 50 MB,最多 1000 个文件</div>
|
||||||
|
<button class="docset-add-file-btn" id="addFileBtn" onclick="addFileToDocset()">
|
||||||
|
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M469.333333 469.333333V170.666667h85.333334v298.666666h298.666666v85.333334h-298.666666v298.666666h-85.333334v-298.666666H170.666667v-85.333334h298.666666z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
添加文件
|
||||||
|
</button>
|
||||||
|
<div id="docsetFilesDisplay" style="display: none; margin-top: 8px;">
|
||||||
|
<div id="docsetFilesList" style="max-height: 200px; overflow-y: auto; border: 1px solid var(--vscode-input-border); border-radius: 4px; padding: 8px;"></div>
|
||||||
|
<div id="docsetFilesSummary" style="margin-top: 8px; font-size: 12px; color: var(--vscode-descriptionForeground);"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="docset-dialog-footer">
|
||||||
|
<button class="docset-btn-cancel" onclick="closeDocsetDialog()">取消</button>
|
||||||
|
<button class="docset-btn-confirm" onclick="confirmDocset()">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDocsetDialogStyles(): string {
|
||||||
|
return `
|
||||||
|
.docset-dialog {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog.active {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog-content {
|
||||||
|
position: relative;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
border: 1px solid var(--vscode-panel-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid var(--vscode-panel-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog-header button {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog-header button:hover {
|
||||||
|
background: var(--vscode-toolbar-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog-body {
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-hint {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--vscode-input-background);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--vscode-input-foreground);
|
||||||
|
font-size: 13px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-add-file-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--vscode-textLink-foreground);
|
||||||
|
border: 1px solid var(--vscode-textLink-foreground);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-add-file-btn:hover {
|
||||||
|
background: var(--vscode-toolbar-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-add-file-btn svg {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid var(--vscode-panel-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-dialog-footer button {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-btn-cancel {
|
||||||
|
background: var(--vscode-button-secondaryBackground);
|
||||||
|
color: var(--vscode-button-secondaryForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-btn-cancel:hover {
|
||||||
|
background: var(--vscode-button-secondaryHoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-btn-confirm {
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-btn-confirm:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDocsetDialogScript(): string {
|
||||||
|
return `
|
||||||
|
let docsetFiles = [];
|
||||||
|
|
||||||
|
function openAddDocumentSetDialog() {
|
||||||
|
const dialog = document.getElementById('docsetDialog');
|
||||||
|
if (dialog) {
|
||||||
|
dialog.classList.add('active');
|
||||||
|
docsetFiles = [];
|
||||||
|
document.getElementById('docsetName').value = '';
|
||||||
|
document.getElementById('addFileBtn').style.display = 'flex';
|
||||||
|
document.getElementById('docsetFilesDisplay').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDocsetDialog() {
|
||||||
|
const dialog = document.getElementById('docsetDialog');
|
||||||
|
if (dialog) {
|
||||||
|
dialog.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFileToDocset() {
|
||||||
|
vscode.postMessage({ command: 'selectFilesForDocset' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatFileSize(bytes) {
|
||||||
|
if (bytes < 1024) return bytes + ' B';
|
||||||
|
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||||
|
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDocsetDisplay() {
|
||||||
|
if (docsetFiles.length === 0) {
|
||||||
|
document.getElementById('addFileBtn').style.display = 'flex';
|
||||||
|
document.getElementById('docsetFilesDisplay').style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('addFileBtn').style.display = 'none';
|
||||||
|
document.getElementById('docsetFilesDisplay').style.display = 'block';
|
||||||
|
|
||||||
|
const listEl = document.getElementById('docsetFilesList');
|
||||||
|
const summaryEl = document.getElementById('docsetFilesSummary');
|
||||||
|
|
||||||
|
listEl.innerHTML = docsetFiles.map((file, index) => \`
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; font-size: 12px; padding: 4px 0; color: var(--vscode-foreground);">
|
||||||
|
<span>\${file.relativePath || file.path}</span>
|
||||||
|
<button onclick="removeDocsetFile(\${index})" style="background: transparent; border: none; color: var(--vscode-foreground); cursor: pointer; padding: 0 4px; opacity: 0.7;">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<path d="M10 3h3v1h-1v9l-1 1H4l-1-1V4H2V3h3V2a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zM9 2H6v1h3V2zM4 13h7V4H4v9zm2-8H5v7h1V5zm1 0h1v7H7V5zm2 0h1v7H9V5z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
\`).join('');
|
||||||
|
|
||||||
|
const totalSize = docsetFiles.reduce((sum, f) => sum + (f.size || 0), 0);
|
||||||
|
summaryEl.textContent = \`已选择 \${docsetFiles.length} 个文件,总大小 \${formatFileSize(totalSize)}\`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDocsetFile(index) {
|
||||||
|
docsetFiles.splice(index, 1);
|
||||||
|
updateDocsetDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDocset() {
|
||||||
|
const name = document.getElementById('docsetName').value.trim();
|
||||||
|
if (!name) {
|
||||||
|
alert('请输入文档集名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (docsetFiles.length === 0) {
|
||||||
|
alert('请添加至少一个文件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'saveDocumentSet',
|
||||||
|
name: name,
|
||||||
|
documents: docsetFiles
|
||||||
|
});
|
||||||
|
|
||||||
|
closeDocsetDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', event => {
|
||||||
|
const message = event.data;
|
||||||
|
if (message.command === 'filesSelectedForDocset') {
|
||||||
|
if (message.errors && message.errors.length > 0) {
|
||||||
|
alert('部分文件添加失败:\\n' + message.errors.join('\\n'));
|
||||||
|
}
|
||||||
|
docsetFiles = [...docsetFiles, ...message.files];
|
||||||
|
updateDocsetDisplay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
}
|
||||||
@ -8,6 +8,11 @@ import {
|
|||||||
getRulesSettingsComponentStyles,
|
getRulesSettingsComponentStyles,
|
||||||
getRulesSettingsComponentScript,
|
getRulesSettingsComponentScript,
|
||||||
} from "./rulesSettingsComponent";
|
} from "./rulesSettingsComponent";
|
||||||
|
import {
|
||||||
|
getContextSettingsComponentContent,
|
||||||
|
getContextSettingsComponentStyles,
|
||||||
|
getContextSettingsComponentScript,
|
||||||
|
} from "./contextSettingsComponent";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取设置面板的 HTML 内容
|
* 获取设置面板的 HTML 内容
|
||||||
@ -34,6 +39,9 @@ export function getSettingsComponentContent(): string {
|
|||||||
<button class="settings-nav-item" data-tab="rules" onclick="switchSettingsTab('rules')">
|
<button class="settings-nav-item" data-tab="rules" onclick="switchSettingsTab('rules')">
|
||||||
规则
|
规则
|
||||||
</button>
|
</button>
|
||||||
|
<button class="settings-nav-item" data-tab="context" onclick="switchSettingsTab('context')">
|
||||||
|
上下文
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
@ -43,6 +51,9 @@ export function getSettingsComponentContent(): string {
|
|||||||
<div class="settings-tab-content" id="rulesSettings">
|
<div class="settings-tab-content" id="rulesSettings">
|
||||||
${getRulesSettingsComponentContent()}
|
${getRulesSettingsComponentContent()}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-tab-content" id="contextSettings">
|
||||||
|
${getContextSettingsComponentContent()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -187,6 +198,7 @@ export function getSettingsComponentStyles(): string {
|
|||||||
|
|
||||||
${getGeneralSettingsComponentStyles()}
|
${getGeneralSettingsComponentStyles()}
|
||||||
${getRulesSettingsComponentStyles()}
|
${getRulesSettingsComponentStyles()}
|
||||||
|
${getContextSettingsComponentStyles()}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +209,7 @@ export function getSettingsComponentScript(): string {
|
|||||||
return `
|
return `
|
||||||
${getGeneralSettingsComponentScript()}
|
${getGeneralSettingsComponentScript()}
|
||||||
${getRulesSettingsComponentScript()}
|
${getRulesSettingsComponentScript()}
|
||||||
|
${getContextSettingsComponentScript()}
|
||||||
|
|
||||||
// 打开设置面板
|
// 打开设置面板
|
||||||
function openSettingsModal() {
|
function openSettingsModal() {
|
||||||
@ -237,6 +250,15 @@ export function getSettingsComponentScript(): string {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听打开设置标签页的消息
|
||||||
|
window.addEventListener('message', event => {
|
||||||
|
const message = event.data;
|
||||||
|
if (message.command === 'openSettingsTab') {
|
||||||
|
openSettingsModal();
|
||||||
|
switchSettingsTab(message.tab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 阻止点击模态框内容时关闭
|
// 阻止点击模态框内容时关闭
|
||||||
document.addEventListener('click', (event) => {
|
document.addEventListener('click', (event) => {
|
||||||
const modalContent = document.querySelector('.settings-modal-content');
|
const modalContent = document.querySelector('.settings-modal-content');
|
||||||
|
|||||||
Reference in New Issue
Block a user