feat: 实现文档集管理系统

新增功能:
   - 文档集创建:支持添加 .md/.txt/.v/.sv/.pdf 格式文件
   - 智能限制:单文件 10MB,总容量 50MB,最多 1000 个文件
   - 状态管理:实时显示索引状态(待索引/索引中/已索引)
   - 交互优化:支持文件预览、删除操作,带 loading 动画

   技术实现:
   - 新增 docsetDialog 组件处理文档集对话框
   - 新增 contextSettingsComponent 管理上下文设置
   - 扩展 contextHelper 支持文档集 CRUD 操作
   - 优化 messageRouter 处理文档集相关消息
This commit is contained in:
Roe-xin
2026-03-17 14:28:28 +08:00
parent 76c1af6e7e
commit 64cce80a70
6 changed files with 637 additions and 12 deletions

300
src/views/docsetDialog.ts Normal file
View 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();
}
});
`;
}