Compare commits
12 Commits
master
...
feat/Knowl
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a91513031 | |||
| 69f86dbc0d | |||
| 1207d2b91a | |||
| 4ba096d898 | |||
| d2cd7b0bc8 | |||
| 686aaebc26 | |||
| 894479e252 | |||
| cab8960159 | |||
| 46233d2ac3 | |||
| 79a6ff4c99 | |||
| ada3a3bffd | |||
| 64cce80a70 |
87
docs/文档集功能需求文档.md
Normal file
87
docs/文档集功能需求文档.md
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# 文档集功能需求文档
|
||||||
|
|
||||||
|
## 1. 功能概述
|
||||||
|
|
||||||
|
文档集功能允许用户管理文档,在添加上下文时可以选择文档加载到输入框中,发送给 AI 作为对话上下文。
|
||||||
|
|
||||||
|
## 2. 核心流程
|
||||||
|
|
||||||
|
### 2.1 添加文档集入口
|
||||||
|
1. 用户点击"添加上下文"中的"文档"按钮
|
||||||
|
2. 如果文档集为空,显示"添加文档集"按钮
|
||||||
|
3. 点击按钮跳转到"设置 → 上下文"页面
|
||||||
|
|
||||||
|
### 2.2 创建文档集
|
||||||
|
1. 在设置的上下文页面,点击"添加文档集"按钮
|
||||||
|
2. 弹出文档集创建对话框
|
||||||
|
3. 用户输入文档集名称
|
||||||
|
4. 用户点击"添加文件"按钮,选择文件
|
||||||
|
5. 系统验证文件(格式、大小、数量)
|
||||||
|
6. 显示已选文件列表和统计信息
|
||||||
|
7. 用户点击"确定",保存文档集
|
||||||
|
8. 系统持久化文档集信息到 `globalState`
|
||||||
|
|
||||||
|
### 2.3 使用文档
|
||||||
|
1. 用户点击"添加上下文"中的"文档"按钮
|
||||||
|
2. 显示所有文档列表(自动同步设置中的文档集)
|
||||||
|
3. 用户点击选择一个或多个文档
|
||||||
|
4. 文档路径加载到输入框中
|
||||||
|
5. 用户发送消息,后端读取文档内容作为上下文
|
||||||
|
|
||||||
|
### 2.4 管理文档集
|
||||||
|
1. 在设置的上下文页面查看文档列表
|
||||||
|
2. 显示每个文档的更新时间
|
||||||
|
3. 支持修改文档名称
|
||||||
|
4. 支持删除文档
|
||||||
|
|
||||||
|
## 3. 功能详细需求
|
||||||
|
|
||||||
|
### 3.1 前端需求
|
||||||
|
|
||||||
|
#### 3.1.1 添加上下文 - 文档按钮
|
||||||
|
**功能**:
|
||||||
|
- 点击"文档"按钮,显示文档列表弹窗
|
||||||
|
- 如果没有文档,显示"添加文档集"按钮
|
||||||
|
- 点击"添加文档集"按钮,跳转到设置的上下文页面
|
||||||
|
|
||||||
|
#### 3.1.2 文档列表弹窗
|
||||||
|
**UI 元素**:
|
||||||
|
- 文档列表(显示所有文档集中的文档)
|
||||||
|
- 每个文档显示:名称
|
||||||
|
- 支持多选
|
||||||
|
- 确定/取消按钮
|
||||||
|
|
||||||
|
**交互逻辑**:
|
||||||
|
- 自动同步设置中的文档集
|
||||||
|
- 点击文档选中/取消选中
|
||||||
|
- 点击确定,将选中文档路径加载到输入框
|
||||||
|
|
||||||
|
#### 3.1.3 设置 - 上下文页面
|
||||||
|
**UI 元素**:
|
||||||
|
- "添加文档集"按钮
|
||||||
|
- 文档列表
|
||||||
|
- 每个文档显示:名称、更新时间、修改名称按钮、删除按钮
|
||||||
|
|
||||||
|
**交互逻辑**:
|
||||||
|
- 点击"添加文档集"打开创建对话框
|
||||||
|
- 点击修改名称,弹出输入框修改
|
||||||
|
- 点击删除,删除二次确认弹窗 确认删除文档
|
||||||
|
|
||||||
|
#### 3.1.4 文档集创建对话框
|
||||||
|
**UI 元素**:
|
||||||
|
|
||||||
|
- 文档集名称输入框
|
||||||
|
- 添加文件按钮
|
||||||
|
- 文件列表显示区域
|
||||||
|
- 确定/取消按钮
|
||||||
|
|
||||||
|
**交互逻辑**:
|
||||||
|
- 点击"添加文件"触发文件选择器
|
||||||
|
- 显示已选文件的相对路径和大小
|
||||||
|
- 支持删除单个文件
|
||||||
|
- 实时更新统计信息
|
||||||
|
|
||||||
|
### 3.2 后端需求
|
||||||
|
|
||||||
|
后端只需要支持读取pdf,txt,.v,.sv,.md这些类型的文档
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ import * as vscode from "vscode";
|
|||||||
type Environment = "dev" | "test" | "prod";
|
type Environment = "dev" | "test" | "prod";
|
||||||
|
|
||||||
/** 当前环境 - 修改这里切换环境 */
|
/** 当前环境 - 修改这里切换环境 */
|
||||||
const CURRENT_ENV: Environment = "prod";
|
const CURRENT_ENV: Environment = "test";
|
||||||
|
|
||||||
/** 服务等级类型 */
|
/** 服务等级类型 */
|
||||||
export type ServiceTier = "lite" | "syntaxic" | "max" | "auto";
|
export type ServiceTier = "lite" | "syntaxic" | "max" | "auto";
|
||||||
@ -42,9 +42,9 @@ const ENV_CONFIG: Record<Environment, IccoderConfig> = {
|
|||||||
},
|
},
|
||||||
/** 测试服务器环境 - 通过 Gateway 路由 */
|
/** 测试服务器环境 - 通过 Gateway 路由 */
|
||||||
test: {
|
test: {
|
||||||
backendUrl: "http://192.168.1.108:2029/iccoder",
|
backendUrl: "http://192.168.1.134:2233",
|
||||||
backendUrlStrongeLoop: "http://192.168.1.108:2029",
|
backendUrlStrongeLoop: "http://192.168.1.134:2233",
|
||||||
loginUrl: "http://192.168.1.108:2005/login",
|
loginUrl: "http://192.168.1.134/login",
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
userId: "default-user",
|
userId: "default-user",
|
||||||
serviceTier: "max",
|
serviceTier: "max",
|
||||||
|
|||||||
@ -11,10 +11,13 @@ import { isTokenExpired } from "./utils/jwtUtils";
|
|||||||
import { NotificationService } from "./services/notificationService";
|
import { NotificationService } from "./services/notificationService";
|
||||||
import { InvitationService } from "./services/invitationService";
|
import { InvitationService } from "./services/invitationService";
|
||||||
import { ICCoderCodeActionProvider } from "./providers/codeActionProvider";
|
import { ICCoderCodeActionProvider } from "./providers/codeActionProvider";
|
||||||
|
import { initializeContextHelper } from "./panels/helpers/contextHelper";
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext) {
|
export async function activate(context: vscode.ExtensionContext) {
|
||||||
console.log("🎉 IC Coder 插件已激活!");
|
console.log("🎉 IC Coder 插件已激活!");
|
||||||
|
|
||||||
|
initializeContextHelper(context);
|
||||||
|
|
||||||
// 创建装饰类型(代码旁边的提示)
|
// 创建装饰类型(代码旁边的提示)
|
||||||
const decorationType = vscode.window.createTextEditorDecorationType({
|
const decorationType = vscode.window.createTextEditorDecorationType({
|
||||||
after: {
|
after: {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
setupBalanceUpdateCallback,
|
setupBalanceUpdateCallback,
|
||||||
} from "./helpers/userInfoHelper";
|
} from "./helpers/userInfoHelper";
|
||||||
import { handleWebviewMessage } from "./helpers/messageRouter";
|
import { handleWebviewMessage } from "./helpers/messageRouter";
|
||||||
|
import { getDocumentSets } from "./helpers/contextHelper";
|
||||||
|
|
||||||
function getIconUris(
|
function getIconUris(
|
||||||
webview: vscode.Webview,
|
webview: vscode.Webview,
|
||||||
@ -123,6 +124,13 @@ export async function showICHelperPanel(
|
|||||||
await sendUserInfoToWebview(panel, context);
|
await sendUserInfoToWebview(panel, context);
|
||||||
setupBalanceUpdateCallback(panel, context);
|
setupBalanceUpdateCallback(panel, context);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "documentSetSaved",
|
||||||
|
documentSets: getDocumentSets(),
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
|
||||||
const pendingMessage = context.globalState.get("pendingMessage") as any;
|
const pendingMessage = context.globalState.get("pendingMessage") as any;
|
||||||
if (pendingMessage) {
|
if (pendingMessage) {
|
||||||
await context.globalState.update("pendingMessage", undefined);
|
await context.globalState.update("pendingMessage", undefined);
|
||||||
|
|||||||
@ -6,6 +6,27 @@
|
|||||||
*/
|
*/
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
|
|
||||||
|
let globalContext: vscode.ExtensionContext;
|
||||||
|
const DOCUMENT_SETS_KEY = "iccoder.documentSets";
|
||||||
|
|
||||||
|
export interface DocumentFile {
|
||||||
|
name: string;
|
||||||
|
absolutePath: string;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentSet {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
files: DocumentFile[];
|
||||||
|
updatedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializeContextHelper(context: vscode.ExtensionContext) {
|
||||||
|
globalContext = context;
|
||||||
|
loadDocumentSets();
|
||||||
|
}
|
||||||
|
|
||||||
export async function handleAddContextFile(panel: vscode.WebviewPanel) {
|
export async function handleAddContextFile(panel: vscode.WebviewPanel) {
|
||||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||||
if (!workspaceFolder) {
|
if (!workspaceFolder) {
|
||||||
@ -102,3 +123,126 @@ 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) {
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "showDocumentSetList",
|
||||||
|
documents: documentSets,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let documentSet: DocumentFile[] = [];
|
||||||
|
|
||||||
|
export function getDocumentSet() {
|
||||||
|
return documentSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addToDocumentSet(docs: DocumentFile[]) {
|
||||||
|
documentSet = [...documentSet, ...docs];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveDocumentSet(
|
||||||
|
docs: DocumentFile[],
|
||||||
|
name: string,
|
||||||
|
panel: vscode.WebviewPanel
|
||||||
|
) {
|
||||||
|
const newDocSet: DocumentSet = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
name,
|
||||||
|
files: docs,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
};
|
||||||
|
documentSets.push(newDocSet);
|
||||||
|
persistDocumentSets();
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "documentSetSaved",
|
||||||
|
documentSets: documentSets,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let documentSets: DocumentSet[] = [];
|
||||||
|
|
||||||
|
function loadDocumentSets() {
|
||||||
|
const saved =
|
||||||
|
globalContext.globalState.get<Array<any>>(DOCUMENT_SETS_KEY) ||
|
||||||
|
globalContext.globalState.get<Array<any>>("documentSets", []);
|
||||||
|
if (saved) {
|
||||||
|
documentSets = saved.map((item: any) => ({
|
||||||
|
id: String(item.id),
|
||||||
|
name: String(item.name || ""),
|
||||||
|
files: Array.isArray(item.files)
|
||||||
|
? item.files.map((file: any) => ({
|
||||||
|
name: String(file.name || ""),
|
||||||
|
absolutePath: String(file.absolutePath || file.path || ""),
|
||||||
|
size: Number(file.size || 0),
|
||||||
|
}))
|
||||||
|
: Array.isArray(item.documents)
|
||||||
|
? item.documents.map((file: any) => ({
|
||||||
|
name: String(
|
||||||
|
file.name ||
|
||||||
|
file.relativePath?.split(/[\\/]/).pop() ||
|
||||||
|
file.path?.split(/[\\/]/).pop() ||
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
absolutePath: String(file.absolutePath || file.path || ""),
|
||||||
|
size: Number(file.size || 0),
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
updatedAt:
|
||||||
|
typeof item.updatedAt === "number"
|
||||||
|
? item.updatedAt
|
||||||
|
: new Date(item.updatedAt || Date.now()).getTime(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistDocumentSets() {
|
||||||
|
globalContext.globalState.update(DOCUMENT_SETS_KEY, documentSets);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDocumentSets() {
|
||||||
|
return documentSets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteDocumentSet(id: string, panel: vscode.WebviewPanel) {
|
||||||
|
documentSets = documentSets.filter(ds => ds.id !== id);
|
||||||
|
persistDocumentSets();
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "documentSetSaved",
|
||||||
|
documentSets: documentSets,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeDocumentSetName(id: string, newName: string, panel: vscode.WebviewPanel) {
|
||||||
|
const docSet = documentSets.find(ds => ds.id === id);
|
||||||
|
if (docSet) {
|
||||||
|
docSet.name = newName;
|
||||||
|
docSet.updatedAt = Date.now();
|
||||||
|
persistDocumentSets();
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "documentSetSaved",
|
||||||
|
documentSets: documentSets,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -27,17 +27,23 @@ import {
|
|||||||
savePersonalRule,
|
savePersonalRule,
|
||||||
updatePersonalRule,
|
updatePersonalRule,
|
||||||
deletePersonalRule,
|
deletePersonalRule,
|
||||||
|
updatePersonalRulesEnabled,
|
||||||
} from "../../utils/personalRulesManager";
|
} from "../../utils/personalRulesManager";
|
||||||
import { compactDialog } from "../../services/apiClient";
|
import { compactDialog } from "../../services/apiClient";
|
||||||
import { ChatHistoryManager } from "../../utils/chatHistoryManager";
|
import { ChatHistoryManager } from "../../utils/chatHistoryManager";
|
||||||
import { getCachedUserInfo } from "../../services/userService";
|
import { getCachedUserInfo } from "../../services/userService";
|
||||||
import { loadConversationHistory, selectConversation } from "./conversationHelper";
|
import {
|
||||||
|
loadConversationHistory,
|
||||||
|
selectConversation,
|
||||||
|
} from "./conversationHelper";
|
||||||
import { getVCDFileInfo } from "./vcdHelper";
|
import { getVCDFileInfo } from "./vcdHelper";
|
||||||
import {
|
import {
|
||||||
handleAddContextFile,
|
handleAddContextFile,
|
||||||
handleAddContextFolder,
|
handleAddContextFolder,
|
||||||
handleAddContextImage,
|
handleAddContextImage,
|
||||||
handleAddContextDocument,
|
handleAddContextDocument,
|
||||||
|
handleAddContextDocumentSet,
|
||||||
|
handleGetDocumentSetList,
|
||||||
} from "./contextHelper";
|
} from "./contextHelper";
|
||||||
import { openFile, openFileWithSelection, openFilePathTag } from "./fileHelper";
|
import { openFile, openFileWithSelection, openFilePathTag } from "./fileHelper";
|
||||||
|
|
||||||
@ -136,16 +142,16 @@ export async function handleWebviewMessage(
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "loadConversationHistory":
|
case "loadConversationHistory":
|
||||||
loadConversationHistory(
|
loadConversationHistory(panel, message.offset || 0, message.limit || 10);
|
||||||
panel,
|
|
||||||
message.offset || 0,
|
|
||||||
message.limit || 10,
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "selectConversation":
|
case "selectConversation":
|
||||||
if (message.conversationId) {
|
if (message.conversationId) {
|
||||||
selectConversation(panel, message.conversationId, context.extensionPath);
|
selectConversation(
|
||||||
|
panel,
|
||||||
|
message.conversationId,
|
||||||
|
context.extensionPath,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -259,7 +265,9 @@ export async function handleWebviewMessage(
|
|||||||
verified: true,
|
verified: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const { InvitationService } = require("../../services/invitationService");
|
const {
|
||||||
|
InvitationService,
|
||||||
|
} = require("../../services/invitationService");
|
||||||
const isVerified = await InvitationService.isVerified(context);
|
const isVerified = await InvitationService.isVerified(context);
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "invitationCodeStatus",
|
command: "invitationCodeStatus",
|
||||||
@ -290,7 +298,9 @@ export async function handleWebviewMessage(
|
|||||||
|
|
||||||
case "checkTrialExpiration":
|
case "checkTrialExpiration":
|
||||||
{
|
{
|
||||||
const { TrialExpirationService } = require("../../services/trialExpirationService");
|
const {
|
||||||
|
TrialExpirationService,
|
||||||
|
} = require("../../services/trialExpirationService");
|
||||||
const trialService = new TrialExpirationService(context, panel);
|
const trialService = new TrialExpirationService(context, panel);
|
||||||
await trialService.checkExpiration();
|
await trialService.checkExpiration();
|
||||||
}
|
}
|
||||||
@ -298,7 +308,9 @@ export async function handleWebviewMessage(
|
|||||||
|
|
||||||
case "verifyInvitationCode":
|
case "verifyInvitationCode":
|
||||||
{
|
{
|
||||||
const { InvitationService } = require("../../services/invitationService");
|
const {
|
||||||
|
InvitationService,
|
||||||
|
} = require("../../services/invitationService");
|
||||||
const result = await InvitationService.verifyCode(message.code);
|
const result = await InvitationService.verifyCode(message.code);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@ -362,6 +374,104 @@ 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 || [], message.name || "", panel);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "deleteDocumentSet":
|
||||||
|
const { deleteDocumentSet } = await import("./contextHelper");
|
||||||
|
deleteDocumentSet(message.id, panel);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "changeDocumentSetName":
|
||||||
|
const { changeDocumentSetName } = await import("./contextHelper");
|
||||||
|
changeDocumentSetName(message.id, message.newName, panel);
|
||||||
|
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<{
|
||||||
|
name: string;
|
||||||
|
absolutePath: 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({
|
||||||
|
name: path.basename(filePath),
|
||||||
|
absolutePath: 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;
|
||||||
@ -456,5 +566,26 @@ export async function handleWebviewMessage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "updatePersonalRulesEnabled":
|
||||||
|
{
|
||||||
|
const success = await updatePersonalRulesEnabled(message.enabled);
|
||||||
|
if (success) {
|
||||||
|
const data = loadPersonalRules();
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "personalRulesLoaded",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "loadDocumentSets":
|
||||||
|
const { getDocumentSets } = await import("./contextHelper");
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "documentSetSaved",
|
||||||
|
documentSets: getDocumentSets(),
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,6 +162,8 @@ export async function startStreamDialog(
|
|||||||
const body = JSON.stringify(request);
|
const body = JSON.stringify(request);
|
||||||
|
|
||||||
console.log(`[SSE] 开始流式对话: taskId=${request.taskId}, mode=${request.mode}, url=${urlString}`);
|
console.log(`[SSE] 开始流式对话: taskId=${request.taskId}, mode=${request.mode}, url=${urlString}`);
|
||||||
|
console.log("[SSE] 完整请求体:", request);
|
||||||
|
console.log("[SSE] 请求体 JSON:", body);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const options: http.RequestOptions = {
|
const options: http.RequestOptions = {
|
||||||
|
|||||||
@ -268,8 +268,19 @@ async function handleUserMessageWithBackend(
|
|||||||
let enhancedText = text;
|
let enhancedText = text;
|
||||||
if (contextItems && contextItems.length > 0) {
|
if (contextItems && contextItems.length > 0) {
|
||||||
console.log("[MessageHandler] 处理上下文项:", contextItems.length);
|
console.log("[MessageHandler] 处理上下文项:", contextItems.length);
|
||||||
const paths = contextItems.map((item) => item.path).join("\n");
|
const docTypes = new Set(["file", "document", "docset"]);
|
||||||
enhancedText = `${paths}\n\n${text}`;
|
const regularPaths = contextItems
|
||||||
|
.filter((item) => !docTypes.has(item.type))
|
||||||
|
.map((item) => item.path);
|
||||||
|
const docTags = contextItems
|
||||||
|
.filter((item) => docTypes.has(item.type))
|
||||||
|
.map((item) => `<doc>${item.path}</doc>`);
|
||||||
|
if (regularPaths.length > 0) {
|
||||||
|
enhancedText = `${regularPaths.join("\n")}\n\n${enhancedText}`;
|
||||||
|
}
|
||||||
|
if (docTags.length > 0) {
|
||||||
|
enhancedText = `${enhancedText}\n\n${docTags.join("\n")}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 historyManager 中的 taskId(由 ICHelperPanel 创建)
|
// 获取 historyManager 中的 taskId(由 ICHelperPanel 创建)
|
||||||
|
|||||||
@ -5,16 +5,16 @@
|
|||||||
* 使用场景:保存和加载用户的个人规则
|
* 使用场景:保存和加载用户的个人规则
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from "vscode";
|
||||||
import * as fs from 'fs';
|
import * as fs from "fs";
|
||||||
import * as path from 'path';
|
import * as path from "path";
|
||||||
import * as os from 'os';
|
import * as os from "os";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取规则目录路径
|
* 获取规则目录路径
|
||||||
*/
|
*/
|
||||||
function getRulesDir(): string {
|
function getRulesDir(): string {
|
||||||
return path.join(os.homedir(), '.iccoder', 'rules');
|
return path.join(os.homedir(), ".iccoder", "rules");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,18 +31,22 @@ function ensureRulesDir(): void {
|
|||||||
* 从文件内容中提取规则名称
|
* 从文件内容中提取规则名称
|
||||||
*/
|
*/
|
||||||
function extractRuleName(content: string): string {
|
function extractRuleName(content: string): string {
|
||||||
const lines = content.split('\n');
|
const lines = content.split("\n");
|
||||||
const firstLine = lines[0]?.trim();
|
const firstLine = lines[0]?.trim();
|
||||||
if (firstLine && firstLine.startsWith('# ')) {
|
if (firstLine && firstLine.startsWith("# ")) {
|
||||||
return firstLine.substring(2).trim();
|
return firstLine.substring(2).trim();
|
||||||
}
|
}
|
||||||
return content.substring(0, 30) + (content.length > 30 ? '...' : '');
|
return content.substring(0, 30) + (content.length > 30 ? "..." : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存新规则
|
* 保存新规则
|
||||||
*/
|
*/
|
||||||
export async function savePersonalRule(name: string, content: string, enabled: boolean): Promise<boolean> {
|
export async function savePersonalRule(
|
||||||
|
name: string,
|
||||||
|
content: string,
|
||||||
|
enabled: boolean,
|
||||||
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
ensureRulesDir();
|
ensureRulesDir();
|
||||||
|
|
||||||
@ -51,11 +55,17 @@ export async function savePersonalRule(name: string, content: string, enabled: b
|
|||||||
const filePath = path.join(getRulesDir(), filename);
|
const filePath = path.join(getRulesDir(), filename);
|
||||||
|
|
||||||
const fileContent = `# ${name}\n\n${content}`;
|
const fileContent = `# ${name}\n\n${content}`;
|
||||||
fs.writeFileSync(filePath, fileContent, 'utf-8');
|
fs.writeFileSync(filePath, fileContent, "utf-8");
|
||||||
|
|
||||||
await vscode.workspace.getConfiguration('ic-coder').update('personalRulesEnabled', enabled, vscode.ConfigurationTarget.Global);
|
await vscode.workspace
|
||||||
|
.getConfiguration("ic-coder")
|
||||||
|
.update(
|
||||||
|
"personalRulesEnabled",
|
||||||
|
enabled,
|
||||||
|
vscode.ConfigurationTarget.Global,
|
||||||
|
);
|
||||||
|
|
||||||
vscode.window.showInformationMessage('规则已保存');
|
vscode.window.showInformationMessage("规则已保存");
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(`保存规则失败: ${error}`);
|
vscode.window.showErrorMessage(`保存规则失败: ${error}`);
|
||||||
@ -66,15 +76,26 @@ export async function savePersonalRule(name: string, content: string, enabled: b
|
|||||||
/**
|
/**
|
||||||
* 更新规则
|
* 更新规则
|
||||||
*/
|
*/
|
||||||
export async function updatePersonalRule(filename: string, name: string, content: string, enabled: boolean): Promise<boolean> {
|
export async function updatePersonalRule(
|
||||||
|
filename: string,
|
||||||
|
name: string,
|
||||||
|
content: string,
|
||||||
|
enabled: boolean,
|
||||||
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const filePath = path.join(getRulesDir(), filename);
|
const filePath = path.join(getRulesDir(), filename);
|
||||||
const fileContent = `# ${name}\n\n${content}`;
|
const fileContent = `# ${name}\n\n${content}`;
|
||||||
fs.writeFileSync(filePath, fileContent, 'utf-8');
|
fs.writeFileSync(filePath, fileContent, "utf-8");
|
||||||
|
|
||||||
await vscode.workspace.getConfiguration('ic-coder').update('personalRulesEnabled', enabled, vscode.ConfigurationTarget.Global);
|
await vscode.workspace
|
||||||
|
.getConfiguration("ic-coder")
|
||||||
|
.update(
|
||||||
|
"personalRulesEnabled",
|
||||||
|
enabled,
|
||||||
|
vscode.ConfigurationTarget.Global,
|
||||||
|
);
|
||||||
|
|
||||||
vscode.window.showInformationMessage('规则已更新');
|
vscode.window.showInformationMessage("规则已更新");
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(`更新规则失败: ${error}`);
|
vscode.window.showErrorMessage(`更新规则失败: ${error}`);
|
||||||
@ -90,7 +111,7 @@ export async function deletePersonalRule(filename: string): Promise<boolean> {
|
|||||||
const filePath = path.join(getRulesDir(), filename);
|
const filePath = path.join(getRulesDir(), filename);
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
vscode.window.showInformationMessage('规则已删除');
|
vscode.window.showInformationMessage("规则已删除");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -103,8 +124,13 @@ export async function deletePersonalRule(filename: string): Promise<boolean> {
|
|||||||
/**
|
/**
|
||||||
* 加载所有规则
|
* 加载所有规则
|
||||||
*/
|
*/
|
||||||
export function loadPersonalRules(): { rules: Array<{ filename: string; name: string; content: string }>; enabled: boolean } {
|
export function loadPersonalRules(): {
|
||||||
const enabled = vscode.workspace.getConfiguration('ic-coder').get<boolean>('personalRulesEnabled', true);
|
rules: Array<{ filename: string; name: string; content: string }>;
|
||||||
|
enabled: boolean;
|
||||||
|
} {
|
||||||
|
const enabled = vscode.workspace
|
||||||
|
.getConfiguration("ic-coder")
|
||||||
|
.get<boolean>("personalRulesEnabled", true);
|
||||||
const dir = getRulesDir();
|
const dir = getRulesDir();
|
||||||
|
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
@ -112,16 +138,16 @@ export function loadPersonalRules(): { rules: Array<{ filename: string; name: st
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
|
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
||||||
const rules = files.map(filename => {
|
const rules = files.map((filename) => {
|
||||||
const content = fs.readFileSync(path.join(dir, filename), 'utf-8');
|
const content = fs.readFileSync(path.join(dir, filename), "utf-8");
|
||||||
const lines = content.split('\n');
|
const lines = content.split("\n");
|
||||||
let name = '';
|
let name = "";
|
||||||
let actualContent = content;
|
let actualContent = content;
|
||||||
|
|
||||||
if (lines[0]?.trim().startsWith('# ')) {
|
if (lines[0]?.trim().startsWith("# ")) {
|
||||||
name = lines[0].substring(2).trim();
|
name = lines[0].substring(2).trim();
|
||||||
actualContent = lines.slice(2).join('\n').trim();
|
actualContent = lines.slice(2).join("\n").trim();
|
||||||
} else {
|
} else {
|
||||||
name = extractRuleName(content);
|
name = extractRuleName(content);
|
||||||
}
|
}
|
||||||
@ -130,11 +156,32 @@ export function loadPersonalRules(): { rules: Array<{ filename: string; name: st
|
|||||||
});
|
});
|
||||||
return { rules, enabled };
|
return { rules, enabled };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('读取规则失败:', error);
|
console.error("读取规则失败:", error);
|
||||||
return { rules: [], enabled };
|
return { rules: [], enabled };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新个人规则启用状态
|
||||||
|
*/
|
||||||
|
export async function updatePersonalRulesEnabled(
|
||||||
|
enabled: boolean,
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await vscode.workspace
|
||||||
|
.getConfiguration("ic-coder")
|
||||||
|
.update(
|
||||||
|
"personalRulesEnabled",
|
||||||
|
enabled,
|
||||||
|
vscode.ConfigurationTarget.Global,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("更新规则启用状态失败:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前生效的所有规则内容
|
* 获取当前生效的所有规则内容
|
||||||
*/
|
*/
|
||||||
@ -143,5 +190,5 @@ export function getActiveRules(): string | null {
|
|||||||
if (!enabled || rules.length === 0) {
|
if (!enabled || rules.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return rules.map(r => r.content).join('\n\n');
|
return rules.map((r) => r.content).join("\n\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
renderDocumentSetList(documents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文档到文档集
|
||||||
|
function addDocumentToSet() {
|
||||||
|
vscode.postMessage({ command: 'openContextSettings' });
|
||||||
|
toggleContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文档集项到上下文(已删除,使用统一的确认选择)
|
||||||
|
|
||||||
// 返回主菜单
|
// 返回主菜单
|
||||||
function backToMainMenu() {
|
function backToMainMenu() {
|
||||||
const mainMenu = document.getElementById('contextMenuMain');
|
const mainMenu = document.getElementById('contextMenuMain');
|
||||||
@ -442,6 +481,43 @@ export function getContextButtonScript(): string {
|
|||||||
\`).join('');
|
\`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 渲染文档集列表
|
||||||
|
function renderDocumentSetList(data) {
|
||||||
|
const body = document.getElementById('contextMenuListBody');
|
||||||
|
if (!body) return;
|
||||||
|
|
||||||
|
filteredListData = data || [];
|
||||||
|
|
||||||
|
body.innerHTML = filteredListData.map((item, index) => \`
|
||||||
|
<div class="context-menu-list-item \${selectedItems.has(item.id) ? 'selected' : ''}" onclick="toggleDocumentSetSelection(\${index})">
|
||||||
|
<input type="checkbox" id="item-\${index}" \${selectedItems.has(item.id) ? 'checked' : ''} />
|
||||||
|
<label>\${item.name}</label>
|
||||||
|
</div>
|
||||||
|
\`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换文档集选择
|
||||||
|
function toggleDocumentSetSelection(index) {
|
||||||
|
const selectedItem = filteredListData[index];
|
||||||
|
if (!selectedItem) return;
|
||||||
|
|
||||||
|
const selectedId = selectedItem.id;
|
||||||
|
const checkbox = document.getElementById('item-' + index);
|
||||||
|
const item = document.querySelectorAll('.context-menu-list-item')[index];
|
||||||
|
|
||||||
|
if (selectedItems.has(selectedId)) {
|
||||||
|
selectedItems.delete(selectedId);
|
||||||
|
if (checkbox) checkbox.checked = false;
|
||||||
|
if (item) item.classList.remove('selected');
|
||||||
|
} else {
|
||||||
|
selectedItems.add(selectedId);
|
||||||
|
if (checkbox) checkbox.checked = true;
|
||||||
|
if (item) item.classList.add('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelectedCount();
|
||||||
|
}
|
||||||
|
|
||||||
// 切换项选择
|
// 切换项选择
|
||||||
function toggleItemSelection(index) {
|
function toggleItemSelection(index) {
|
||||||
const selectedItem = filteredListData[index];
|
const selectedItem = filteredListData[index];
|
||||||
@ -475,13 +551,28 @@ export function getContextButtonScript(): string {
|
|||||||
// 确认选择
|
// 确认选择
|
||||||
function confirmSelection() {
|
function confirmSelection() {
|
||||||
try {
|
try {
|
||||||
|
if (currentListType === 'documentSetItem') {
|
||||||
|
const selected = currentListData.filter(item => selectedItems.has(item.id));
|
||||||
|
selected.forEach(docSet => {
|
||||||
|
(docSet.files || []).forEach(doc => {
|
||||||
|
addContextItem('docset', doc.absolutePath, doc.name || doc.absolutePath);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
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) {
|
||||||
|
if (currentListType === 'documentSet') {
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'saveDocumentSet',
|
||||||
|
documents: selected
|
||||||
|
});
|
||||||
|
} else {
|
||||||
selected.forEach(item => {
|
selected.forEach(item => {
|
||||||
addContextItem(currentListType, item.path, item.relativePath || item.path);
|
addContextItem(currentListType, item.path, item.relativePath || item.path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
const menu = document.getElementById('contextMenu');
|
const menu = document.getElementById('contextMenu');
|
||||||
const button = document.querySelector('.add-context-button');
|
const button = document.querySelector('.add-context-button');
|
||||||
@ -507,15 +598,24 @@ export function getContextButtonScript(): string {
|
|||||||
toggleContextMenu();
|
toggleContextMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加文档集
|
||||||
|
function handleAddDocumentSet() {
|
||||||
|
showDocumentSetList();
|
||||||
|
}
|
||||||
|
|
||||||
// 搜索功能
|
// 搜索功能
|
||||||
const searchInput = document.getElementById('contextMenuSearch');
|
const searchInput = document.getElementById('contextMenuSearch');
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
searchInput.addEventListener('input', function(e) {
|
searchInput.addEventListener('input', function(e) {
|
||||||
const keyword = (e.target.value || '').toLowerCase().trim();
|
const keyword = (e.target.value || '').toLowerCase().trim();
|
||||||
const filtered = currentListData.filter(item =>
|
const filtered = currentListData.filter(item =>
|
||||||
(item.relativePath || item.path || '').toLowerCase().includes(keyword)
|
(item.name || item.relativePath || item.path || '').toLowerCase().includes(keyword)
|
||||||
);
|
);
|
||||||
|
if (currentListType === 'documentSetItem') {
|
||||||
|
renderDocumentSetList(filtered);
|
||||||
|
} else {
|
||||||
renderList(filtered);
|
renderList(filtered);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,6 +627,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 || []);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -181,6 +181,7 @@ export function getContextDisplayScript(): string {
|
|||||||
case 'folder': icon = getFolderIcon(); break;
|
case 'folder': icon = getFolderIcon(); break;
|
||||||
case 'image': icon = getImageIcon(); break;
|
case 'image': icon = getImageIcon(); break;
|
||||||
case 'document': icon = getDocumentIcon(); break;
|
case 'document': icon = getDocumentIcon(); break;
|
||||||
|
case 'docset': icon = getDocumentIcon(); break;
|
||||||
case 'code': icon = getCodeIcon(); break;
|
case 'code': icon = getCodeIcon(); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
160
src/views/contextSettingsComponent.ts
Normal file
160
src/views/contextSettingsComponent.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/**
|
||||||
|
* 上下文设置组件
|
||||||
|
* 功能:管理文档集
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--vscode-panel-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-item:hover {
|
||||||
|
background: var(--vscode-list-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-name {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-meta {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-delete-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
opacity: 0.6;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-delete-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background: var(--vscode-toolbar-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
${getDocsetDialogStyles()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContextSettingsComponentScript(): string {
|
||||||
|
return getDocsetDialogScript();
|
||||||
|
}
|
||||||
571
src/views/docsetDialog.ts
Normal file
571
src/views/docsetDialog.ts
Normal file
@ -0,0 +1,571 @@
|
|||||||
|
/**
|
||||||
|
* 文档集对话框组件
|
||||||
|
* 功能:添加文档集的对话框
|
||||||
|
*/
|
||||||
|
|
||||||
|
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>
|
||||||
|
<div class="delete-confirm-dialog" id="deleteConfirmDialog">
|
||||||
|
<div class="delete-confirm-content">
|
||||||
|
<div class="delete-confirm-title">确认删除</div>
|
||||||
|
<div class="delete-confirm-message" id="deleteConfirmMessage"></div>
|
||||||
|
<div class="delete-confirm-actions">
|
||||||
|
<button class="docset-btn-cancel" onclick="closeDeleteConfirm()">取消</button>
|
||||||
|
<button class="docset-btn-confirm" onclick="confirmDelete()">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="rename-dialog" id="renameDialog">
|
||||||
|
<div class="rename-content">
|
||||||
|
<div class="rename-title">修改名称</div>
|
||||||
|
<input type="text" id="renameInput" class="rename-input" placeholder="输入新名称" />
|
||||||
|
<div class="rename-actions">
|
||||||
|
<button class="docset-btn-cancel" onclick="closeRenameDialog()">取消</button>
|
||||||
|
<button class="docset-btn-confirm" onclick="confirmRename()">确定</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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-delete-btn, .docset-change-btn {
|
||||||
|
position: relative;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-delete-btn:hover, .docset-change-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docset-delete-btn:hover::after, .docset-change-btn:hover::after {
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: var(--vscode-editorHoverWidget-background);
|
||||||
|
color: var(--vscode-editorHoverWidget-foreground);
|
||||||
|
border: 1px solid var(--vscode-editorHoverWidget-border);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-confirm-dialog {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 10000;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-confirm-dialog.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-confirm-content {
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
border: 1px solid var(--vscode-panel-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-confirm-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-confirm-message {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-confirm-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-confirm-actions button {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rename-dialog {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 10000;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rename-dialog.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rename-content {
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
border: 1px solid var(--vscode-panel-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rename-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rename-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;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rename-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rename-actions button {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.name || file.absolutePath}</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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDocumentSets(documentSets) {
|
||||||
|
const listEl = document.getElementById('contextDocsList');
|
||||||
|
if (!listEl) return;
|
||||||
|
|
||||||
|
if (!documentSets || documentSets.length === 0) {
|
||||||
|
listEl.innerHTML = '<div class="context-empty"><p>暂无文档集</p></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listEl.innerHTML = documentSets.map(ds => \`
|
||||||
|
<div class="docset-item">
|
||||||
|
<div class="docset-name">\${ds.name}</div>
|
||||||
|
<div class="docset-meta">更新于 \${new Date(ds.updatedAt).toLocaleString('zh-CN')}</div>
|
||||||
|
<button class="docset-change-btn" data-tooltip="修改名称" onclick="changeDocsetName('\${ds.id}', '\${ds.name}')">
|
||||||
|
<svg t="1773883957219" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7170" width="14" height="14">
|
||||||
|
<path d="M745.76 369.86l-451 537.48a18.693 18.693 0 0 1-8.46 5.74l-136.58 45.27c-13.24 4.39-26.46-6.71-24.43-20.5l20.86-142.36c0.5-3.44 1.95-6.67 4.19-9.33l451-537.48c6.65-7.93 18.47-8.96 26.4-2.31l115.71 97.1c7.92 6.64 8.96 18.46 2.31 26.39zM894.53 192.56l-65.9 78.53c-6.65 7.93-18.47 8.96-26.4 2.31l-115.71-97.1c-7.93-6.65-8.96-18.47-2.31-26.4l65.9-78.53c6.65-7.93 18.47-8.96 26.4-2.31l115.71 97.1c7.93 6.65 8.96 18.47 2.31 26.4z" fill="currentColor" p-id="7171"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="docset-delete-btn" data-tooltip="删除" onclick="showDeleteConfirm('\${ds.id}', '\${ds.name}')">
|
||||||
|
<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('');
|
||||||
|
}
|
||||||
|
|
||||||
|
let deleteTargetId = null;
|
||||||
|
let renameTargetId = null;
|
||||||
|
let renameOriginalName = null;
|
||||||
|
|
||||||
|
window.showDeleteConfirm = function(id, name) {
|
||||||
|
deleteTargetId = id;
|
||||||
|
document.getElementById('deleteConfirmMessage').textContent = \`确定要删除文档集 "\${name}" 吗?此操作不可恢复。\`;
|
||||||
|
document.getElementById('deleteConfirmDialog').classList.add('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.changeDocsetName = function(id, name) {
|
||||||
|
renameTargetId = id;
|
||||||
|
renameOriginalName = name;
|
||||||
|
document.getElementById('renameInput').value = name;
|
||||||
|
document.getElementById('renameDialog').classList.add('active');
|
||||||
|
setTimeout(() => document.getElementById('renameInput').focus(), 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.closeDeleteConfirm = function() {
|
||||||
|
document.getElementById('deleteConfirmDialog').classList.remove('active');
|
||||||
|
deleteTargetId = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.closeRenameDialog = function() {
|
||||||
|
document.getElementById('renameDialog').classList.remove('active');
|
||||||
|
renameTargetId = null;
|
||||||
|
renameOriginalName = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.confirmDelete = function() {
|
||||||
|
if (deleteTargetId) {
|
||||||
|
vscode.postMessage({ command: 'deleteDocumentSet', id: deleteTargetId });
|
||||||
|
closeDeleteConfirm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.confirmRename = function() {
|
||||||
|
const newName = document.getElementById('renameInput').value.trim();
|
||||||
|
if (!newName) {
|
||||||
|
alert('请输入名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newName !== renameOriginalName) {
|
||||||
|
vscode.postMessage({ command: 'changeDocumentSetName', id: renameTargetId, newName: newName });
|
||||||
|
}
|
||||||
|
closeRenameDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
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'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
||||||
|
const MAX_TOTAL_SIZE = 50 * 1024 * 1024;
|
||||||
|
const MAX_FILE_COUNT = 1000;
|
||||||
|
|
||||||
|
const vaildFiles = [];
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
for (const file of message.files) {
|
||||||
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
|
errors.push(\`\${file.name || file.absolutePath} 超过单个文件大小限制(\${formatFileSize(MAX_FILE_SIZE)})\`);
|
||||||
|
} else {
|
||||||
|
vaildFiles.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFiles = [...docsetFiles, ...vaildFiles];
|
||||||
|
const totalSize = newFiles.reduce((sum, f) => sum + (f.size || 0), 0);
|
||||||
|
|
||||||
|
if (newFiles.length > MAX_FILE_COUNT) {
|
||||||
|
errors.push(\`文档数量超过限制(最多1000个),当前数量(\${newFiles.length} 个)\`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalSize > MAX_TOTAL_SIZE) {
|
||||||
|
errors.push(\`文档集总大小超过 50MB 限制,当前大小为(\${formatFileSize(MAX_TOTAL_SIZE)})\`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(errors.length > 0) {
|
||||||
|
alert('以下文件被跳过:\\n' + errors.join('\\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
docsetFiles = newFiles;
|
||||||
|
updateDocsetDisplay();
|
||||||
|
} else if (message.command === 'documentSetSaved') {
|
||||||
|
renderDocumentSets(message.documentSets);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
}
|
||||||
@ -400,6 +400,12 @@ export function getRulesSettingsComponentScript(): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//监听启用个人规则开关变化
|
||||||
|
document.getElementById('enablePersonalRulesCheckbox').addEventListener('change', function() {
|
||||||
|
const enabled = this.checked;
|
||||||
|
vscode.postMessage({ command: 'updatePersonalRulesEnabled', enabled: enabled });
|
||||||
|
});
|
||||||
|
|
||||||
// 页面加载时请求规则数据
|
// 页面加载时请求规则数据
|
||||||
vscode.postMessage({ command: 'loadPersonalRules' });
|
vscode.postMessage({ command: 'loadPersonalRules' });
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -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() {
|
||||||
@ -235,7 +248,21 @@ export function getSettingsComponentScript(): string {
|
|||||||
content.classList.remove('active');
|
content.classList.remove('active');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 切换到上下文标签页时加载文档集列表
|
||||||
|
if (tabName === 'context') {
|
||||||
|
vscode.postMessage({ command: 'loadDocumentSets' });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听打开设置标签页的消息
|
||||||
|
window.addEventListener('message', event => {
|
||||||
|
const message = event.data;
|
||||||
|
if (message.command === 'openSettingsTab') {
|
||||||
|
openSettingsModal();
|
||||||
|
switchSettingsTab(message.tab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 阻止点击模态框内容时关闭
|
// 阻止点击模态框内容时关闭
|
||||||
document.addEventListener('click', (event) => {
|
document.addEventListener('click', (event) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user