diff --git a/src/panels/helpers/contextHelper.ts b/src/panels/helpers/contextHelper.ts
index 91e6ca2..dbe9100 100644
--- a/src/panels/helpers/contextHelper.ts
+++ b/src/panels/helpers/contextHelper.ts
@@ -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;
+}
diff --git a/src/panels/helpers/messageRouter.ts b/src/panels/helpers/messageRouter.ts
index 2f78904..ecc9adf 100644
--- a/src/panels/helpers/messageRouter.ts
+++ b/src/panels/helpers/messageRouter.ts
@@ -32,6 +32,8 @@ import {
handleAddContextFolder,
handleAddContextImage,
handleAddContextDocument,
+ handleAddContextDocumentSet,
+ handleGetDocumentSetList,
} from "./contextHelper";
import { openFile, openFileWithSelection, openFilePathTag } from "./fileHelper";
@@ -356,6 +358,90 @@ export async function handleWebviewMessage(
await handleAddContextFolder(panel);
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":
await handleAddContextImage(panel);
break;
diff --git a/src/views/contextButton.ts b/src/views/contextButton.ts
index 3fae63c..a74aac1 100644
--- a/src/views/contextButton.ts
+++ b/src/views/contextButton.ts
@@ -41,18 +41,15 @@ export function getContextButtonContent(): string {
-
-
+ 文档集
+
+
@@ -382,6 +379,48 @@ export function getContextButtonScript(): string {
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 = \`
+
+ \`;
+ } else {
+ currentListType = 'documentSetItem';
+ currentListData = documents;
+ filteredListData = documents;
+ selectedItems.clear();
+ renderList(documents);
+ }
+ }
+ }
+
+ // 添加文档到文档集
+ function addDocumentToSet() {
+ vscode.postMessage({ command: 'openContextSettings' });
+ toggleContextMenu();
+ }
+
+ // 添加文档集项到上下文(已删除,使用统一的确认选择)
+
// 返回主菜单
function backToMainMenu() {
const mainMenu = document.getElementById('contextMenuMain');
@@ -478,9 +517,16 @@ export function getContextButtonScript(): string {
const selected = currentListData.filter(item => selectedItems.has(item.path));
if (selected.length > 0) {
- selected.forEach(item => {
- addContextItem(currentListType, item.path, item.relativePath || item.path);
- });
+ if (currentListType === 'documentSet') {
+ vscode.postMessage({
+ command: 'saveDocumentSet',
+ documents: selected
+ });
+ } else {
+ selected.forEach(item => {
+ addContextItem(currentListType === 'documentSetItem' ? 'file' : currentListType, item.path, item.relativePath || item.path);
+ });
+ }
}
} finally {
const menu = document.getElementById('contextMenu');
@@ -507,6 +553,11 @@ export function getContextButtonScript(): string {
toggleContextMenu();
}
+ // 添加文档集
+ function handleAddDocumentSet() {
+ showDocumentSetList();
+ }
+
// 搜索功能
const searchInput = document.getElementById('contextMenuSearch');
if (searchInput) {
@@ -527,6 +578,10 @@ export function getContextButtonScript(): string {
switchToListView('选择文件', 'file', message.files);
} else if (message.command === 'showWorkspaceFolderList') {
switchToListView('选择文件夹', 'folder', message.folders);
+ } else if (message.command === 'showWorkspaceDocumentSetList') {
+ switchToListView('选择文档', 'documentSet', message.files);
+ } else if (message.command === 'showDocumentSetList') {
+ showDocumentSetView(message.documents || []);
}
});
`;
diff --git a/src/views/contextSettingsComponent.ts b/src/views/contextSettingsComponent.ts
new file mode 100644
index 0000000..8bf683d
--- /dev/null
+++ b/src/views/contextSettingsComponent.ts
@@ -0,0 +1,119 @@
+/**
+ * 上下文设置组件
+ * 功能:管理文档集
+ */
+import {
+ getDocsetDialogContent,
+ getDocsetDialogStyles,
+ getDocsetDialogScript,
+} from "./docsetDialog";
+
+export function getContextSettingsComponentContent(): string {
+ return `
+
+
+ ${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();
+}
diff --git a/src/views/docsetDialog.ts b/src/views/docsetDialog.ts
new file mode 100644
index 0000000..c642a4a
--- /dev/null
+++ b/src/views/docsetDialog.ts
@@ -0,0 +1,300 @@
+/**
+ * 文档集对话框组件
+ * 功能:添加文档集的对话框
+ */
+
+export function getDocsetDialogContent(): string {
+ return `
+
+ `;
+}
+
+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) => \`
+
+
\${file.relativePath || file.path}
+
+
+ \`).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();
+ }
+ });
+ `;
+}
diff --git a/src/views/settingsComponent.ts b/src/views/settingsComponent.ts
index db24906..fd6513e 100644
--- a/src/views/settingsComponent.ts
+++ b/src/views/settingsComponent.ts
@@ -8,6 +8,11 @@ import {
getRulesSettingsComponentStyles,
getRulesSettingsComponentScript,
} from "./rulesSettingsComponent";
+import {
+ getContextSettingsComponentContent,
+ getContextSettingsComponentStyles,
+ getContextSettingsComponentScript,
+} from "./contextSettingsComponent";
/**
* 获取设置面板的 HTML 内容
@@ -34,6 +39,9 @@ export function getSettingsComponentContent(): string {
+
@@ -43,6 +51,9 @@ export function getSettingsComponentContent(): string {
${getRulesSettingsComponentContent()}
+
+ ${getContextSettingsComponentContent()}
+
@@ -187,6 +198,7 @@ export function getSettingsComponentStyles(): string {
${getGeneralSettingsComponentStyles()}
${getRulesSettingsComponentStyles()}
+ ${getContextSettingsComponentStyles()}
`;
}
@@ -197,6 +209,7 @@ export function getSettingsComponentScript(): string {
return `
${getGeneralSettingsComponentScript()}
${getRulesSettingsComponentScript()}
+ ${getContextSettingsComponentScript()}
// 打开设置面板
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) => {
const modalContent = document.querySelector('.settings-modal-content');