From da027a93e466e324bc903cdb4b92256f6f16c781 Mon Sep 17 00:00:00 2001 From: adnaan <1662877157@qq.com> Date: Fri, 19 Dec 2025 19:31:53 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=B6=88=E9=99=A4=E7=A1=AC?= =?UTF-8?q?=E7=BC=96=E7=A0=81=EF=BC=8C=E6=89=A9=E5=B1=95=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 11 + src/main/main.ts | 125 ++++----- src/renderer/App.tsx | 25 +- src/renderer/agent/prompts.ts | 15 +- src/renderer/config/editorConfig.ts | 18 +- src/renderer/services/completionService.ts | 31 +-- src/renderer/services/lspService.ts | 24 +- src/renderer/store/slices/settingsSlice.ts | 4 +- src/shared/constants.ts | 116 ++++++++ src/shared/languages.ts | 299 +++++++++++++++++++++ vite.config.ts | 4 + 11 files changed, 532 insertions(+), 140 deletions(-) create mode 100644 .gitattributes create mode 100644 src/shared/constants.ts create mode 100644 src/shared/languages.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..81339597 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.jsx text eol=lf +*.json text eol=lf +*.css text eol=lf +*.html text eol=lf +*.md text eol=lf +*.yml text eol=lf +*.yaml text eol=lf diff --git a/src/main/main.ts b/src/main/main.ts index d1feaf07..92c3b39e 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -11,6 +11,31 @@ import { registerAllHandlers, cleanupAllHandlers, updateLLMServiceWindow } from import { lspManager } from './lspManager' import { securityManager, updateWhitelist } from './security' +// 共享安全常量(与 renderer 保持一致) +const SECURITY_DEFAULTS = { + SHELL_COMMANDS: [ + // 包管理器 + 'npm', 'yarn', 'pnpm', 'bun', + // 运行时 + 'node', 'npx', 'deno', + // 版本控制 + 'git', + // 编程语言 + 'python', 'python3', 'pip', 'pip3', + 'java', 'javac', 'mvn', 'gradle', + 'go', 'rust', 'cargo', + // 构建工具 + 'make', 'gcc', 'clang', 'cmake', + // 常用命令 + 'pwd', 'ls', 'dir', 'cat', 'type', 'echo', 'mkdir', 'touch', 'rm', 'mv', 'cp', 'cd', + ], + GIT_SUBCOMMANDS: [ + 'status', 'log', 'diff', 'add', 'commit', 'push', 'pull', + 'branch', 'checkout', 'merge', 'rebase', 'clone', 'remote', + 'fetch', 'show', 'rev-parse', 'init', 'stash', 'tag', + ], +} as const + // ========================================== // Store 初始化 // ========================================== @@ -29,41 +54,37 @@ function initStore() { } } +// 初始化 store initStore() // ========================================== -// 全局状态 +// 窗口管理 // ========================================== -const windows = new Map() -let lastActiveWindow: BrowserWindow | null = null -let isQuitting = false +const windows = new Set() +let mainWindow: BrowserWindow | null = null -function getMainWindow() { - return lastActiveWindow || Array.from(windows.values())[0] || null +function getMainWindow(): BrowserWindow | null { + return mainWindow } -// ========================================== -// 窗口创建 -// ========================================== - -function createWindow(isEmpty: boolean = false) { - // 图标路径:开发环境用 public,生产环境用 resources - const iconPath = app.isPackaged - ? path.join(process.resourcesPath, 'icon.png') - : path.join(__dirname, '../../public/icon.png') +// 单例锁定 +const gotTheLock = app.requestSingleInstanceLock() +if (!gotTheLock) { + app.quit() +} +function createWindow(isEmpty = true): BrowserWindow { const win = new BrowserWindow({ width: 1600, height: 1000, minWidth: 1200, minHeight: 700, - frame: false, - titleBarStyle: 'hidden', - icon: iconPath, - trafficLightPosition: { x: 15, y: 15 }, backgroundColor: '#09090b', - show: false, + show: false, // 等待渲染完成后显示 + titleBarStyle: 'hidden', + titleBarOverlay: false, + autoHideMenuBar: true, webPreferences: { preload: path.join(__dirname, '../preload/preload.js'), contextIsolation: true, @@ -71,51 +92,30 @@ function createWindow(isEmpty: boolean = false) { }, }) - const windowId = win.id - windows.set(windowId, win) - lastActiveWindow = win + windows.add(win) + if (!mainWindow) { + mainWindow = win + } - win.on('focus', () => { - lastActiveWindow = win - updateLLMServiceWindow(win) - }) + // 每个窗口都需要更新 LLM service 的引用 + updateLLMServiceWindow(win) - win.once('ready-to-show', () => { - win.show() - console.log(`[Main] Window ${windowId} shown`) - if (!app.isPackaged) { - win.webContents.openDevTools({ mode: 'detach' }) - } - }) - - win.on('close', async (e) => { - if (windows.size === 1 && !isQuitting) { - // 最后一个窗口关闭时,执行全局清理 - isQuitting = true - e.preventDefault() - console.log('[Main] Last window closing, starting cleanup...') - try { - cleanupAllHandlers() - await lspManager.stopAllServers() - console.log('[Main] Cleanup completed') - } catch (err) { - console.error('[Main] Cleanup error:', err) - } - win.destroy() - app.quit() + win.on('closed', () => { + windows.delete(win) + if (windows.size === 0) { + mainWindow = null + cleanupAllHandlers() + lspManager.stopAllServers() } else { - // 非最后一个窗口,直接移除引用 - windows.delete(windowId) - if (lastActiveWindow === win) { - lastActiveWindow = Array.from(windows.values())[0] || null + // 如果关闭的是 mainWindow,选择一个新的 + if (mainWindow === win) { + mainWindow = windows.values().next().value ?? null } } }) - // 加载页面 - const query = isEmpty ? '?empty=1' : '' - if (!app.isPackaged) { - win.loadURL(`http://localhost:5173${query}`) + if (process.env.NODE_ENV === 'development' || !app.isPackaged) { + win.loadURL(`http://localhost:5173${isEmpty ? '?empty=1' : ''}`) } else { win.loadFile(path.join(__dirname, '../renderer/index.html'), { query: isEmpty ? { empty: '1' } : undefined }) } @@ -130,19 +130,20 @@ function createWindow(isEmpty: boolean = false) { app.whenReady().then(() => { console.log('[Security] 🔒 初始化安全模块...') + // 使用共享常量作为默认值 const securityConfig = mainStore.get('securitySettings', { enablePermissionConfirm: true, enableAuditLog: true, strictWorkspaceMode: true, - allowedShellCommands: ['npm', 'yarn', 'pnpm', 'node', 'npx', 'git', 'python', 'python3', 'java', 'go', 'rust', 'cargo', 'make', 'gcc', 'clang', 'pwd', 'ls', 'cat', 'echo', 'mkdir', 'touch', 'rm', 'mv', 'cd'], - allowedGitSubcommands: ['status', 'log', 'diff', 'add', 'commit', 'push', 'pull', 'branch', 'checkout', 'merge', 'rebase', 'clone', 'remote', 'fetch', 'show', 'rev-parse', 'init'], + allowedShellCommands: [...SECURITY_DEFAULTS.SHELL_COMMANDS], + allowedGitSubcommands: [...SECURITY_DEFAULTS.GIT_SUBCOMMANDS], }) as any securityManager.updateConfig(securityConfig) // 初始化白名单 - const shellCommands = securityConfig.allowedShellCommands || ['npm', 'yarn', 'pnpm', 'node', 'npx', 'git'] - const gitCommands = securityConfig.allowedGitSubcommands || ['status', 'log', 'diff', 'add', 'commit', 'push', 'pull', 'branch', 'checkout', 'merge', 'rebase', 'clone', 'remote', 'fetch', 'show', 'rev-parse', 'init'] + const shellCommands = securityConfig.allowedShellCommands || [...SECURITY_DEFAULTS.SHELL_COMMANDS] + const gitCommands = securityConfig.allowedGitSubcommands || [...SECURITY_DEFAULTS.GIT_SUBCOMMANDS] updateWhitelist(shellCommands, gitCommands) console.log('[Security] ✅ 安全模块已初始化') diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 7a450fae..d8840c23 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useCallback } from 'react' +import { useEffect, useState, useCallback, lazy, Suspense } from 'react' import { useStore } from './store' import TitleBar from './components/TitleBar' import Sidebar from './components/Sidebar' @@ -11,8 +11,6 @@ import KeyboardShortcuts from './components/KeyboardShortcuts' import QuickOpen from './components/QuickOpen' import ActivityBar from './components/ActivityBar' import StatusBar from './components/StatusBar' -import ComposerPanel from './components/ComposerPanel' -import OnboardingWizard from './components/OnboardingWizard' import { ToastProvider, useToast, setGlobalToast } from './components/Toast' import { GlobalConfirmDialog } from './components/ConfirmDialog' import { ErrorBoundary } from './components/ErrorBoundary' @@ -26,8 +24,13 @@ import { checkpointService } from './agent/checkpointService' import { useAgentStore } from './agent/core/AgentStore' import { keybindingService } from './services/keybindingService' import { registerCoreCommands } from './config/commands' +import { LAYOUT_LIMITS } from '../shared/constants' import noiseSvg from './assets/images/noise.svg' +// 懒加载大组件以优化首屏性能 +const ComposerPanel = lazy(() => import('./components/ComposerPanel')) +const OnboardingWizard = lazy(() => import('./components/OnboardingWizard')) + // 暴露 store 给插件系统 ; (window as any).__ADNIFY_STORE__ = { getState: () => useStore.getState() } @@ -195,20 +198,20 @@ function AppContent() { return cleanup }, []) - // Resize Logic + // Resize Logic - 使用共享常量 useEffect(() => { if (!isResizingSidebar && !isResizingChat) return const handleMouseMove = (e: MouseEvent) => { if (isResizingSidebar) { - const newWidth = e.clientX - 48 // 48 is ActivityBar width - if (newWidth > 150 && newWidth < 600) { + const newWidth = e.clientX - LAYOUT_LIMITS.ACTIVITY_BAR_WIDTH + if (newWidth > LAYOUT_LIMITS.SIDEBAR_MIN_WIDTH && newWidth < LAYOUT_LIMITS.SIDEBAR_MAX_WIDTH) { setSidebarWidth(newWidth) } } if (isResizingChat) { const newWidth = window.innerWidth - e.clientX - if (newWidth > 300 && newWidth < 800) { + if (newWidth > LAYOUT_LIMITS.CHAT_MIN_WIDTH && newWidth < LAYOUT_LIMITS.CHAT_MAX_WIDTH) { setChatWidth(newWidth) } } @@ -355,10 +358,14 @@ function AppContent() { setShowQuickOpen(false)} /> )} {showComposer && ( - setShowComposer(false)} /> + + setShowComposer(false)} /> + )} {showOnboarding && isInitialized && ( - setShowOnboarding(false)} /> + + setShowOnboarding(false)} /> + )} {showAbout && setShowAbout(false)} />} diff --git a/src/renderer/agent/prompts.ts b/src/renderer/agent/prompts.ts index 5e9d1c5d..4c424f1c 100644 --- a/src/renderer/agent/prompts.ts +++ b/src/renderer/agent/prompts.ts @@ -5,18 +5,19 @@ import { ChatMode } from '../store' import { rulesService } from './rulesService' +import { FILE_LIMITS } from '../../shared/constants' // Search/Replace 块格式 (Git 风格,LLM 更熟悉) export const ORIGINAL = '<<<<<<< SEARCH' export const DIVIDER = '=======' export const FINAL = '>>>>>>> REPLACE' -// 限制常量(与 editorConfig.ts 保持一致) -export const MAX_FILE_CHARS = 60000 -export const MAX_DIR_ITEMS = 150 -export const MAX_SEARCH_RESULTS = 30 -export const MAX_TERMINAL_OUTPUT = 3000 -export const MAX_CONTEXT_CHARS = 30000 +// 限制常量(从共享配置导入) +export const MAX_FILE_CHARS = FILE_LIMITS.MAX_FILE_CHARS +export const MAX_DIR_ITEMS = FILE_LIMITS.MAX_DIR_ITEMS +export const MAX_SEARCH_RESULTS = FILE_LIMITS.MAX_SEARCH_RESULTS +export const MAX_TERMINAL_OUTPUT = FILE_LIMITS.MAX_TERMINAL_OUTPUT +export const MAX_CONTEXT_CHARS = FILE_LIMITS.MAX_CONTEXT_CHARS // Search/Replace 块模板 const searchReplaceBlockTemplate = `\ @@ -161,7 +162,7 @@ export async function buildSystemPrompt( // 获取提示词模板 const { getPromptTemplateById, getDefaultPromptTemplate } = await import('./promptTemplates') - const template = promptTemplateId + const template = promptTemplateId ? getPromptTemplateById(promptTemplateId) || getDefaultPromptTemplate() : getDefaultPromptTemplate() diff --git a/src/renderer/config/editorConfig.ts b/src/renderer/config/editorConfig.ts index 99f2395a..045f47e4 100644 --- a/src/renderer/config/editorConfig.ts +++ b/src/renderer/config/editorConfig.ts @@ -4,6 +4,8 @@ * 双重存储:localStorage(快速读取)+ 文件(持久化备份) */ +import { IGNORED_DIRECTORIES } from '../../shared/languages' + export interface EditorConfig { // 编辑器外观 fontSize: number @@ -118,20 +120,8 @@ export const defaultEditorConfig: EditorConfig = { maxSingleFileChars: 6000, // 单文件最多 6000 字符 }, - // 忽略的目录 - ignoredDirectories: [ - 'node_modules', - 'dist', - 'build', - '.git', - '.next', - 'coverage', - '__pycache__', - '.cache', - 'out', - '.vscode', - '.idea', - ], + // 忽略的目录(使用共享常量) + ignoredDirectories: [...IGNORED_DIRECTORIES], } // 存储 key diff --git a/src/renderer/services/completionService.ts b/src/renderer/services/completionService.ts index 9edf7455..c0286e01 100644 --- a/src/renderer/services/completionService.ts +++ b/src/renderer/services/completionService.ts @@ -12,6 +12,7 @@ import { useStore } from '../store' import { getEditorConfig } from '../config/editorConfig' +import { FIM_CAPABLE_MODELS, getLanguageFromPath as sharedGetLanguageFromPath } from '../../shared/languages' // ============ Interfaces ============ @@ -69,16 +70,8 @@ export interface CompletionOptions { maxCandidates: number // Max candidates to generate } -// FIM-capable models -const FIM_MODELS = [ - 'deepseek-coder', - 'codellama', - 'starcoder', - 'code-llama', - 'deepseek', - 'qwen-coder', - 'yi-coder', -] +// FIM-capable models (from shared config) +const FIM_MODELS = FIM_CAPABLE_MODELS // 从配置获取默认选项 @@ -221,22 +214,8 @@ function debounce) => ReturnType>( // ============ Language Detection ============ - -const LANGUAGE_MAP: Record = { - ts: 'typescript', tsx: 'typescript', - js: 'javascript', jsx: 'javascript', mjs: 'javascript', cjs: 'javascript', - py: 'python', rs: 'rust', go: 'go', java: 'java', - cpp: 'cpp', c: 'c', h: 'c', hpp: 'cpp', - css: 'css', scss: 'scss', less: 'less', - html: 'html', htm: 'html', vue: 'html', svelte: 'html', - json: 'json', yaml: 'yaml', yml: 'yaml', - md: 'markdown', sql: 'sql', sh: 'shell', bash: 'shell', -} - -function getLanguageFromPath(path: string): string { - const ext = path.split('.').pop()?.toLowerCase() || '' - return LANGUAGE_MAP[ext] || 'plaintext' -} +// Use shared language configuration +const getLanguageFromPath = sharedGetLanguageFromPath // ============ Import Analysis ============ diff --git a/src/renderer/services/lspService.ts b/src/renderer/services/lspService.ts index a71e8225..aa2d0d6e 100644 --- a/src/renderer/services/lspService.ts +++ b/src/renderer/services/lspService.ts @@ -4,6 +4,7 @@ */ import { useStore } from '../store' +import { EXTENSION_TO_LANGUAGE, LSP_SUPPORTED_LANGUAGES } from '../../shared/languages' // 文档版本追踪 const documentVersions = new Map() @@ -37,33 +38,14 @@ export function getFileWorkspaceRoot(filePath: string): string | null { */ export function getLanguageId(filePath: string): string { const ext = filePath.split('.').pop()?.toLowerCase() || '' - const languageMap: Record = { - ts: 'typescript', - tsx: 'typescriptreact', - js: 'javascript', - jsx: 'javascriptreact', - mjs: 'javascript', - cjs: 'javascript', - html: 'html', - htm: 'html', - css: 'css', - scss: 'scss', - less: 'less', - json: 'json', - jsonc: 'jsonc', - } - return languageMap[ext] || 'plaintext' + return EXTENSION_TO_LANGUAGE[ext] || 'plaintext' } /** * 检查语言是否支持 LSP */ export function isLanguageSupported(languageId: string): boolean { - const supported = [ - 'typescript', 'typescriptreact', 'javascript', 'javascriptreact', - 'html', 'css', 'scss', 'less', 'json', 'jsonc', - ] - return supported.includes(languageId) + return (LSP_SUPPORTED_LANGUAGES as readonly string[]).includes(languageId) } /** diff --git a/src/renderer/store/slices/settingsSlice.ts b/src/renderer/store/slices/settingsSlice.ts index 18933be3..005fa7bd 100644 --- a/src/renderer/store/slices/settingsSlice.ts +++ b/src/renderer/store/slices/settingsSlice.ts @@ -2,6 +2,7 @@ * 设置相关状态切片 */ import { StateCreator } from 'zustand' +import { SECURITY_DEFAULTS } from '../../../shared/constants' export type ProviderType = 'openai' | 'anthropic' | 'gemini' | 'deepseek' | 'groq' | 'mistral' | 'ollama' | 'custom' @@ -77,11 +78,12 @@ const defaultProviderConfigs: Record = { custom: { customModels: [] }, } +// 使用共享常量作为默认安全设置 const defaultSecuritySettings: SecuritySettings = { enablePermissionConfirm: true, enableAuditLog: true, strictWorkspaceMode: true, - allowedShellCommands: ['npm', 'yarn', 'pnpm', 'node', 'npx', 'git', 'ls', 'cat', 'echo', 'pwd'], + allowedShellCommands: [...SECURITY_DEFAULTS.SHELL_COMMANDS], showSecurityWarnings: true, } diff --git a/src/shared/constants.ts b/src/shared/constants.ts new file mode 100644 index 00000000..79787ebf --- /dev/null +++ b/src/shared/constants.ts @@ -0,0 +1,116 @@ +/** + * 共享常量配置 + * 集中管理所有硬编码值,实现定制化 + */ + +// ========================================== +// 文件和搜索限制(原 prompts.ts 硬编码) +// ========================================== + +export const FILE_LIMITS = { + /** 单个文件最大字符数 */ + MAX_FILE_CHARS: 60000, + /** 目录列表最大条目数 */ + MAX_DIR_ITEMS: 150, + /** 搜索结果最大数量 */ + MAX_SEARCH_RESULTS: 30, + /** 终端输出最大字符数 */ + MAX_TERMINAL_OUTPUT: 3000, + /** AI 上下文最大字符数 */ + MAX_CONTEXT_CHARS: 30000, +} as const + +// ========================================== +// 布局限制(原 App.tsx 硬编码) +// ========================================== + +export const LAYOUT_LIMITS = { + /** ActivityBar 宽度 */ + ACTIVITY_BAR_WIDTH: 48, + /** 侧边栏最小宽度 */ + SIDEBAR_MIN_WIDTH: 150, + /** 侧边栏最大宽度 */ + SIDEBAR_MAX_WIDTH: 600, + /** 聊天面板最小宽度 */ + CHAT_MIN_WIDTH: 300, + /** 聊天面板最大宽度 */ + CHAT_MAX_WIDTH: 800, +} as const + +// ========================================== +// 窗口默认值(原 main.ts 硬编码) +// ========================================== + +export const WINDOW_DEFAULTS = { + WIDTH: 1600, + HEIGHT: 1000, + MIN_WIDTH: 1200, + MIN_HEIGHT: 700, + BACKGROUND_COLOR: '#09090b', +} as const + +// ========================================== +// 安全设置默认值(统一 main.ts 和 settingsSlice.ts) +// ========================================== + +export const SECURITY_DEFAULTS = { + /** 允许的 Shell 命令 */ + SHELL_COMMANDS: [ + // 包管理器 + 'npm', 'yarn', 'pnpm', 'bun', + // 运行时 + 'node', 'npx', 'deno', + // 版本控制 + 'git', + // 编程语言 + 'python', 'python3', 'pip', 'pip3', + 'java', 'javac', 'mvn', 'gradle', + 'go', 'rust', 'cargo', + // 构建工具 + 'make', 'gcc', 'clang', 'cmake', + // 常用命令 + 'pwd', 'ls', 'dir', 'cat', 'type', 'echo', 'mkdir', 'touch', 'rm', 'mv', 'cp', 'cd', + ], + /** 允许的 Git 子命令 */ + GIT_SUBCOMMANDS: [ + 'status', 'log', 'diff', 'add', 'commit', 'push', 'pull', + 'branch', 'checkout', 'merge', 'rebase', 'clone', 'remote', + 'fetch', 'show', 'rev-parse', 'init', 'stash', 'tag', + ], +} as const + +// ========================================== +// AI 相关默认值(原 editorConfig.ts 和 settingsSlice.ts) +// ========================================== + +export const AI_DEFAULTS = { + /** 默认提供商 */ + DEFAULT_PROVIDER: 'openai' as const, + /** 默认模型 */ + DEFAULT_MODEL: 'gpt-4o', + /** 最大工具调用循环数 */ + MAX_TOOL_LOOPS: 15, + /** 补全最大 token 数 */ + COMPLETION_MAX_TOKENS: 256, + /** 补全温度 */ + COMPLETION_TEMPERATURE: 0.1, +} as const + +// ========================================== +// 性能相关默认值 +// ========================================== + +export const PERFORMANCE_DEFAULTS = { + /** 文件变化防抖延迟 (ms) */ + FILE_CHANGE_DEBOUNCE_MS: 300, + /** 代码补全防抖延迟 (ms) */ + COMPLETION_DEBOUNCE_MS: 300, + /** 搜索防抖延迟 (ms) */ + SEARCH_DEBOUNCE_MS: 200, + /** Git 状态刷新间隔 (ms) */ + GIT_STATUS_INTERVAL_MS: 5000, + /** API 请求超时 (ms) */ + REQUEST_TIMEOUT_MS: 120000, + /** 命令执行超时 (ms) */ + COMMAND_TIMEOUT_MS: 30000, +} as const diff --git a/src/shared/languages.ts b/src/shared/languages.ts new file mode 100644 index 00000000..bbe892ca --- /dev/null +++ b/src/shared/languages.ts @@ -0,0 +1,299 @@ +/** + * 语言配置 + * 统一管理文件扩展名到语言 ID 的映射,避免重复定义 + */ + +// ========================================== +// 文件扩展名 -> 语言 ID 映射 +// ========================================== + +export const EXTENSION_TO_LANGUAGE: Record = { + // JavaScript / TypeScript + ts: 'typescript', + tsx: 'typescriptreact', + js: 'javascript', + jsx: 'javascriptreact', + mjs: 'javascript', + cjs: 'javascript', + + // Python + py: 'python', + pyw: 'python', + pyi: 'python', + pyx: 'python', + + // Rust + rs: 'rust', + + // Go + go: 'go', + + // Java / Kotlin / Scala + java: 'java', + kt: 'kotlin', + kts: 'kotlin', + scala: 'scala', + + // C / C++ + c: 'c', + h: 'c', + cpp: 'cpp', + hpp: 'cpp', + cc: 'cpp', + cxx: 'cpp', + hxx: 'cpp', + + // C# + cs: 'csharp', + + // Web - Markup + html: 'html', + htm: 'html', + vue: 'vue', + svelte: 'svelte', + + // Web - Styles + css: 'css', + scss: 'scss', + sass: 'sass', + less: 'less', + styl: 'stylus', + + // Data formats + json: 'json', + jsonc: 'jsonc', + json5: 'json5', + yaml: 'yaml', + yml: 'yaml', + toml: 'toml', + xml: 'xml', + svg: 'xml', + + // Shell + sh: 'shell', + bash: 'shell', + zsh: 'shell', + fish: 'shell', + ps1: 'powershell', + psm1: 'powershell', + bat: 'batch', + cmd: 'batch', + + // Markdown / Documentation + md: 'markdown', + mdx: 'mdx', + rst: 'restructuredtext', + tex: 'latex', + + // Database + sql: 'sql', + mysql: 'sql', + pgsql: 'sql', + + // Other languages + rb: 'ruby', + php: 'php', + swift: 'swift', + dart: 'dart', + lua: 'lua', + r: 'r', + R: 'r', + jl: 'julia', + ex: 'elixir', + exs: 'elixir', + erl: 'erlang', + hrl: 'erlang', + hs: 'haskell', + lhs: 'haskell', + ml: 'ocaml', + mli: 'ocaml', + clj: 'clojure', + cljs: 'clojure', + fs: 'fsharp', + fsx: 'fsharp', + nim: 'nim', + zig: 'zig', + v: 'v', + sol: 'solidity', + + // Config files + dockerfile: 'dockerfile', + makefile: 'makefile', + cmake: 'cmake', + gradle: 'groovy', + groovy: 'groovy', + + // GraphQL + graphql: 'graphql', + gql: 'graphql', + + // Protocol Buffers + proto: 'protobuf', +} + +// ========================================== +// LSP 支持的语言 +// ========================================== + +export const LSP_SUPPORTED_LANGUAGES = [ + // 完全支持(内置 LSP) + 'typescript', + 'typescriptreact', + 'javascript', + 'javascriptreact', + 'html', + 'css', + 'scss', + 'less', + 'json', + 'jsonc', +] as const + +// 可扩展支持(需要额外 LSP 服务器) +export const LSP_EXTENSIBLE_LANGUAGES = [ + 'python', // pylsp / pyright + 'rust', // rust-analyzer + 'go', // gopls + 'java', // jdtls + 'csharp', // omnisharp +] as const + +// ========================================== +// 忽略目录(完整列表) +// ========================================== + +export const IGNORED_DIRECTORIES = [ + // Node.js / JavaScript + 'node_modules', + '.npm', + '.yarn', + '.pnpm-store', + 'bower_components', + + // Build outputs + 'dist', + 'build', + 'out', + '.next', + '.nuxt', + '.output', + '.svelte-kit', + '.parcel-cache', + '.turbo', + + // Caches + '.cache', + '__pycache__', + '.pytest_cache', + '.mypy_cache', + '.ruff_cache', + '.tox', + + // Version control + '.git', + '.svn', + '.hg', + '.bzr', + + // IDE / Editor + '.vscode', + '.idea', + '.vs', + '.fleet', + + // Language-specific + 'vendor', // Go, PHP, Ruby + 'target', // Rust, Java (Maven) + '.venv', // Python + 'venv', // Python + 'env', // Python + '.virtualenv', // Python + '__pypackages__', // Python (PDM) + '.gradle', // Java (Gradle) + '.maven', // Java (Maven) + 'Pods', // iOS (CocoaPods) + 'DerivedData', // iOS (Xcode) + '.dart_tool', // Dart + '.pub-cache', // Dart + 'zig-cache', // Zig + '_build', // Elixir + 'deps', // Elixir + + // Coverage / Testing + 'coverage', + '.nyc_output', + 'htmlcov', + '.coverage', + + // Misc + 'tmp', + 'temp', + 'logs', + '.DS_Store', + 'Thumbs.db', +] as const + +// ========================================== +// FIM (Fill-in-the-Middle) 支持的模型 +// ========================================== + +export const FIM_CAPABLE_MODELS = [ + 'deepseek-coder', + 'deepseek-coder-v2', + 'codellama', + 'code-llama', + 'starcoder', + 'starcoder2', + 'qwen-coder', + 'qwen2.5-coder', + 'yi-coder', + 'codestral', + 'codegemma', +] as const + +// ========================================== +// 语言 ID -> LSP 语言 ID 映射 (用于 LSP 通信) +// ========================================== + +export const LANGUAGE_TO_LSP_ID: Record = { + typescript: 'typescript', + typescriptreact: 'typescriptreact', + javascript: 'javascript', + javascriptreact: 'javascriptreact', + html: 'html', + css: 'css', + scss: 'scss', + less: 'less', + json: 'json', + jsonc: 'jsonc', + python: 'python', + rust: 'rust', + go: 'go', + java: 'java', +} + +// ========================================== +// 辅助函数 +// ========================================== + +/** + * 根据文件路径获取语言 ID + */ +export function getLanguageFromPath(filePath: string): string { + const ext = filePath.split('.').pop()?.toLowerCase() || '' + return EXTENSION_TO_LANGUAGE[ext] || 'plaintext' +} + +/** + * 检查语言是否支持 LSP + */ +export function isLspSupported(languageId: string): boolean { + return (LSP_SUPPORTED_LANGUAGES as readonly string[]).includes(languageId) +} + +/** + * 检查目录是否应被忽略 + */ +export function shouldIgnoreDirectory(dirName: string): boolean { + return IGNORED_DIRECTORIES.includes(dirName as any) +} diff --git a/vite.config.ts b/vite.config.ts index 991f532e..49334463 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -95,6 +95,10 @@ export default defineConfig({ 'state': ['zustand'], // UI 图标 'icons': ['lucide-react'], + // 终端相关 - 懒加载优化 + 'terminal': ['@xterm/xterm', '@xterm/addon-fit', '@xterm/addon-webgl', '@xterm/addon-web-links'], + // Markdown 渲染 - 懒加载优化 + 'markdown': ['react-markdown', 'react-syntax-highlighter'], }, }, },