diff --git a/electron/main/ai/assistant/builtinTools.ts b/electron/main/ai/assistant/builtinTools.ts index edd3035e..acbe9e42 100644 --- a/electron/main/ai/assistant/builtinTools.ts +++ b/electron/main/ai/assistant/builtinTools.ts @@ -1,20 +1,14 @@ /** - * 内置工具目录查询 - * - * 基于 TOOL_REGISTRY 提供前端展示所需的工具目录(含分类信息)。 + * 内置工具目录查询 — 从 @openchatlab/core 重导出 */ -import { TOOL_REGISTRY } from '../tools/definitions' -import type { ToolCategory } from '../tools/types' +import { BUILTIN_TOOL_CATALOG as _CATALOG } from '@openchatlab/core' -export interface BuiltinToolCatalogEntry { - name: string - category: ToolCategory -} +export type { BuiltinToolCatalogEntry } from '@openchatlab/core' /** * 获取所有内置工具的目录(含分类),供前端展示 */ -export function getBuiltinToolCatalog(): BuiltinToolCatalogEntry[] { - return TOOL_REGISTRY.map((e) => ({ name: e.name, category: e.category })) +export function getBuiltinToolCatalog() { + return _CATALOG } diff --git a/electron/main/ai/assistant/parser.ts b/electron/main/ai/assistant/parser.ts index ffdc6058..96f7bbe4 100644 --- a/electron/main/ai/assistant/parser.ts +++ b/electron/main/ai/assistant/parser.ts @@ -1,68 +1,5 @@ /** - * 助手文件解析器 & 序列化器 - * 将 Markdown + YAML Frontmatter 格式的 .md 文件与 AssistantConfig 互相转换 + * 助手文件解析器 — 从 @openchatlab/node-runtime 重导出 */ -import * as path from 'path' -import matter from 'gray-matter' -import type { AssistantConfig } from './types' - -/** - * 解析助手 Markdown 文件内容为 AssistantConfig - * - * 解析失败时返回 null(不抛异常),调用方负责处理。 - * 单文件解析失败不能影响整个 AssistantManager 初始化。 - */ -export function parseAssistantFile(content: string, filePath: string): AssistantConfig | null { - try { - const { data: fm, content: body } = matter(content) - - const id = fm.id ?? path.basename(filePath, '.md') - const name = fm.name - if (!name) return null - - return { - id, - name, - systemPrompt: body.trim(), - presetQuestions: parseStringArray(fm.presetQuestions), - allowedBuiltinTools: parseStringArray(fm.allowedBuiltinTools), - builtinId: typeof fm.builtinId === 'string' ? fm.builtinId : undefined, - applicableChatTypes: parseChatTypes(fm.applicableChatTypes), - supportedLocales: parseStringArray(fm.supportedLocales), - } - } catch { - return null - } -} - -/** - * 将 AssistantConfig 序列化为 Markdown 文件内容(YAML frontmatter + body) - * - * frontmatter 中省略空数组和 undefined 值以保持简洁。 - */ -export function serializeAssistant(config: AssistantConfig): string { - const fm: Record = { - id: config.id, - name: config.name, - } - - if (config.builtinId) fm.builtinId = config.builtinId - if (config.applicableChatTypes?.length) fm.applicableChatTypes = config.applicableChatTypes - if (config.supportedLocales?.length) fm.supportedLocales = config.supportedLocales - if (config.allowedBuiltinTools?.length) fm.allowedBuiltinTools = config.allowedBuiltinTools - if (config.presetQuestions?.length) fm.presetQuestions = config.presetQuestions - - return matter.stringify(`\n${config.systemPrompt}\n`, fm) -} - -function parseStringArray(raw: unknown): string[] { - if (Array.isArray(raw)) return raw.map(String).filter(Boolean) - return [] -} - -function parseChatTypes(raw: unknown): ('group' | 'private')[] | undefined { - if (!Array.isArray(raw)) return undefined - const valid = raw.filter((v): v is 'group' | 'private' => v === 'group' || v === 'private') - return valid.length > 0 ? valid : undefined -} +export { parseAssistantFile, serializeAssistant } from '@openchatlab/node-runtime' diff --git a/electron/main/ai/llm/model-catalog.ts b/electron/main/ai/llm/model-catalog.ts index 2133aef1..2b633b0c 100644 --- a/electron/main/ai/llm/model-catalog.ts +++ b/electron/main/ai/llm/model-catalog.ts @@ -1,896 +1,5 @@ /** - * Model Catalog — 内置模型目录 + * Model Catalog — 从 @openchatlab/core 重导出 */ -import type { ModelDefinition } from './model-types' - -// ==================== Helper ==================== - -type ModelInput = Omit - -function builtin(m: ModelInput): ModelDefinition { - return { ...m, builtin: true, editable: false } -} - -// ==================== OpenAI ==================== - -const OPENAI_MODELS: ModelDefinition[] = [ - builtin({ - id: 'gpt-5.4-pro', - providerId: 'openai', - name: 'GPT-5.4 Pro', - description: 'Latest flagship, best reasoning & coding', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'gpt-5.4', - providerId: 'openai', - name: 'GPT-5.4', - description: 'GPT-5.4 standard', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'gpt-5.2-pro', - providerId: 'openai', - name: 'GPT-5.2 Pro', - description: 'GPT-5.2 Pro', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'gpt-5.2', - providerId: 'openai', - name: 'GPT-5.2', - description: 'GPT-5.2 standard', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'gpt-5.1', - providerId: 'openai', - name: 'GPT-5.1', - description: 'GPT-5.1', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'gpt-5-pro', - providerId: 'openai', - name: 'GPT-5 Pro', - description: 'GPT-5 Pro', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'gpt-5', - providerId: 'openai', - name: 'GPT-5', - description: 'GPT-5 standard', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'gpt-4.1', - providerId: 'openai', - name: 'GPT-4.1', - description: 'Best at coding & instruction following', - contextWindow: 1047576, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'gpt-4.1-mini', - providerId: 'openai', - name: 'GPT-4.1 Mini', - description: 'Cost-effective', - contextWindow: 1047576, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'gpt-4.1-nano', - providerId: 'openai', - name: 'GPT-4.1 Nano', - description: 'Fastest and most affordable', - contextWindow: 1047576, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'gpt-4o', - providerId: 'openai', - name: 'GPT-4o', - description: 'Multimodal flagship model', - contextWindow: 128000, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'gpt-4o-mini', - providerId: 'openai', - name: 'GPT-4o Mini', - description: 'Lightweight multimodal model', - contextWindow: 128000, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'o3', - providerId: 'openai', - name: 'o3', - description: 'Advanced reasoning model', - contextWindow: 200000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'o4-mini', - providerId: 'openai', - name: 'o4-mini', - description: 'Efficient reasoning model', - contextWindow: 200000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'text-embedding-3-small', - providerId: 'openai', - name: 'Text Embedding 3 Small', - description: 'Efficient embedding model', - contextWindow: 8191, - capabilities: ['embedding'], - recommendedFor: ['embedding'], - status: 'stable', - }), - builtin({ - id: 'text-embedding-3-large', - providerId: 'openai', - name: 'Text Embedding 3 Large', - description: 'High-precision embedding model', - contextWindow: 8191, - capabilities: ['embedding'], - recommendedFor: ['embedding'], - status: 'stable', - }), -] - -// ==================== Anthropic ==================== - -const ANTHROPIC_MODELS: ModelDefinition[] = [ - builtin({ - id: 'claude-opus-4-6', - providerId: 'anthropic', - name: 'Claude Opus 4.6', - description: 'Latest flagship, adaptive thinking', - contextWindow: 200000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'claude-sonnet-4-6', - providerId: 'anthropic', - name: 'Claude Sonnet 4.6', - description: 'Cost-effective, adaptive thinking', - contextWindow: 200000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'claude-opus-4-5', - providerId: 'anthropic', - name: 'Claude Opus 4.5', - description: 'Previous generation flagship', - contextWindow: 200000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'claude-sonnet-4-5', - providerId: 'anthropic', - name: 'Claude Sonnet 4.5', - description: 'Previous generation cost-effective', - contextWindow: 200000, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'claude-haiku-4-5', - providerId: 'anthropic', - name: 'Claude Haiku 4.5', - description: 'Fast and lightweight', - contextWindow: 200000, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'claude-sonnet-4-20250514', - providerId: 'anthropic', - name: 'Claude Sonnet 4', - description: 'Sonnet 4 snapshot', - contextWindow: 200000, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'claude-opus-4-20250514', - providerId: 'anthropic', - name: 'Claude Opus 4', - description: 'Opus 4 snapshot', - contextWindow: 200000, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'claude-haiku-3-5-20241022', - providerId: 'anthropic', - name: 'Claude 3.5 Haiku', - description: '3.5 Haiku snapshot', - contextWindow: 200000, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), -] - -// ==================== Gemini ==================== - -const GEMINI_MODELS: ModelDefinition[] = [ - builtin({ - id: 'gemini-3.1-pro-preview', - providerId: 'gemini', - name: 'Gemini 3.1 Pro', - description: 'Latest Gemini 3.1 Pro preview', - contextWindow: 1048576, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'preview', - }), - builtin({ - id: 'gemini-3-pro-preview', - providerId: 'gemini', - name: 'Gemini 3 Pro', - description: 'Gemini 3 Pro preview', - contextWindow: 1048576, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'preview', - }), - builtin({ - id: 'gemini-2.5-flash', - providerId: 'gemini', - name: 'Gemini 2.5 Flash', - description: 'Cost-effective, low latency, adaptive thinking', - contextWindow: 1048576, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'gemini-2.5-pro', - providerId: 'gemini', - name: 'Gemini 2.5 Pro', - description: 'Deep reasoning and complex tasks', - contextWindow: 1048576, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'gemini-2.0-flash', - providerId: 'gemini', - name: 'Gemini 2.0 Flash', - description: 'Previous generation fast model', - contextWindow: 1048576, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'text-embedding-004', - providerId: 'gemini', - name: 'Text Embedding 004', - description: 'Google embedding model', - contextWindow: 2048, - capabilities: ['embedding'], - recommendedFor: ['embedding'], - status: 'stable', - }), -] - -// ==================== DeepSeek ==================== - -const DEEPSEEK_MODELS: ModelDefinition[] = [ - builtin({ - id: 'deepseek-v4-pro', - providerId: 'deepseek', - name: 'DeepSeek V4 Pro', - description: 'Flagship model, 1M context, thinking & non-thinking', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'deepseek-v4-flash', - providerId: 'deepseek', - name: 'DeepSeek V4 Flash', - description: 'Fast model, 1M context, thinking & non-thinking', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), -] - -// ==================== 通义千问 ==================== - -const QWEN_MODELS: ModelDefinition[] = [ - builtin({ - id: 'qwen3.5-plus', - providerId: 'qwen', - name: 'Qwen3.5 Plus', - description: 'Latest Qwen3.5 flagship', - contextWindow: 262144, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'qwen3.5-flash', - providerId: 'qwen', - name: 'Qwen3.5 Flash', - description: 'Qwen3.5 fast version', - contextWindow: 262144, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'qwen3-max', - providerId: 'qwen', - name: 'Qwen3 Max', - description: 'Qwen3 massive-scale', - contextWindow: 262144, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'qwen-max', - providerId: 'qwen', - name: 'Qwen Max', - description: 'Massive-scale language model', - contextWindow: 262144, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'qwen-plus', - providerId: 'qwen', - name: 'Qwen Plus', - description: 'Good quality, suitable for most scenarios', - contextWindow: 262144, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'qwen-turbo', - providerId: 'qwen', - name: 'Qwen Turbo', - description: 'Fast and cost-effective', - contextWindow: 1000000, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'qwen-long', - providerId: 'qwen', - name: 'Qwen Long', - description: 'Ultra-long context model', - contextWindow: 262144, - capabilities: ['chat'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'text-embedding-v3', - providerId: 'qwen', - name: 'Text Embedding V3', - description: 'Qwen embedding model', - contextWindow: 8192, - capabilities: ['embedding'], - recommendedFor: ['embedding'], - status: 'stable', - }), -] - -// ==================== 智谱 GLM ==================== - -const GLM_MODELS: ModelDefinition[] = [ - builtin({ - id: 'glm-5', - providerId: 'glm', - name: 'GLM-5', - description: 'Latest flagship model', - contextWindow: 200000, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'glm-4.7', - providerId: 'glm', - name: 'GLM-4.7', - description: 'GLM-4.7 high performance', - contextWindow: 200000, - capabilities: ['chat', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'glm-4.6v', - providerId: 'glm', - name: 'GLM-4.6V', - description: '4.6 multimodal model', - contextWindow: 200000, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'glm-4.6v-flash', - providerId: 'glm', - name: 'GLM-4.6V Flash', - description: '4.6V free multimodal', - contextWindow: 200000, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'glm-4.5', - providerId: 'glm', - name: 'GLM-4.5', - description: 'GLM-4.5 standard', - contextWindow: 200000, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'glm-4.5-flash', - providerId: 'glm', - name: 'GLM-4.5 Flash', - description: '4.5 free model', - contextWindow: 200000, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'glm-4-plus', - providerId: 'glm', - name: 'GLM-4 Plus', - description: 'Previous generation flagship', - contextWindow: 128000, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'glm-4-flash', - providerId: 'glm', - name: 'GLM-4 Flash', - description: 'Fast model', - contextWindow: 128000, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'embedding-3', - providerId: 'glm', - name: 'Embedding-3', - description: 'Zhipu embedding model', - contextWindow: 8192, - capabilities: ['embedding'], - recommendedFor: ['embedding'], - status: 'stable', - }), -] - -// ==================== Kimi ==================== - -const KIMI_MODELS: ModelDefinition[] = [ - builtin({ - id: 'kimi-k2.5', - providerId: 'kimi', - name: 'Kimi K2.5', - description: 'Latest flagship with vision support', - contextWindow: 262144, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'kimi-k2-thinking', - providerId: 'kimi', - name: 'Kimi K2 Thinking', - description: 'K2 deep reasoning', - contextWindow: 131072, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'kimi-k2-thinking-turbo', - providerId: 'kimi', - name: 'Kimi K2 Thinking Turbo', - description: 'K2 fast reasoning', - contextWindow: 131072, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'kimi-k2-0905-Preview', - providerId: 'kimi', - name: 'Kimi K2 Preview', - description: 'K2 preview', - contextWindow: 131072, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'preview', - }), - builtin({ - id: 'moonshot-v1-auto', - providerId: 'kimi', - name: 'Moonshot V1 Auto', - description: 'Auto-select context length', - contextWindow: 131072, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), -] - -// ==================== 豆包 ==================== - -const DOUBAO_MODELS: ModelDefinition[] = [ - builtin({ - id: 'doubao-seed-2-0-pro-260215', - providerId: 'doubao', - name: 'Doubao Seed 2.0 Pro', - description: 'Latest flagship model', - contextWindow: 256000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'doubao-seed-2-0-lite-260215', - providerId: 'doubao', - name: 'Doubao Seed 2.0 Lite', - description: 'Seed 2.0 lightweight', - contextWindow: 256000, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'doubao-seed-2-0-mini-260215', - providerId: 'doubao', - name: 'Doubao Seed 2.0 Mini', - description: 'Seed 2.0 ultra-fast', - contextWindow: 256000, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'doubao-seed-1-8-251228', - providerId: 'doubao', - name: 'Doubao Seed 1.8', - description: 'Previous generation flagship', - contextWindow: 128000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'doubao-seed-1-6-251015', - providerId: 'doubao', - name: 'Doubao Seed 1.6', - description: 'High-performance general model', - contextWindow: 128000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'doubao-seed-1-6-lite-251015', - providerId: 'doubao', - name: 'Doubao Seed 1.6 Lite', - description: 'Lightweight and cost-effective', - contextWindow: 128000, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), -] - -// ==================== 硅基流动 ==================== - -const SILICONFLOW_MODELS: ModelDefinition[] = [ - builtin({ - id: 'deepseek-ai/DeepSeek-V3.2', - providerId: 'siliconflow', - name: 'DeepSeek V3.2', - description: 'DeepSeek V3.2 on SiliconFlow', - contextWindow: 128000, - capabilities: ['chat', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'deepseek-ai/DeepSeek-R1', - providerId: 'siliconflow', - name: 'DeepSeek R1', - description: 'DeepSeek R1 reasoning model', - contextWindow: 65536, - capabilities: ['chat', 'reasoning'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'Qwen/Qwen3-8B', - providerId: 'siliconflow', - name: 'Qwen3 8B', - description: 'Qwen3 8B lightweight model', - contextWindow: 32768, - capabilities: ['chat'], - recommendedFor: [], - status: 'stable', - }), -] - -// ==================== Groq ==================== - -const GROQ_MODELS: ModelDefinition[] = [ - builtin({ - id: 'llama3-70b-8192', - providerId: 'groq', - name: 'LLaMA3 70B', - description: 'Meta LLaMA3 70B ultra-fast inference', - contextWindow: 8192, - capabilities: ['chat', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'llama3-8b-8192', - providerId: 'groq', - name: 'LLaMA3 8B', - description: 'Meta LLaMA3 8B lightweight ultra-fast', - contextWindow: 8192, - capabilities: ['chat'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'mistral-saba-24b', - providerId: 'groq', - name: 'Mistral Saba 24B', - description: 'Mistral Saba 24B', - contextWindow: 32768, - capabilities: ['chat'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'gemma-9b-it', - providerId: 'groq', - name: 'Gemma 9B', - description: 'Google Gemma 9B', - contextWindow: 8192, - capabilities: ['chat'], - recommendedFor: [], - status: 'stable', - }), -] - -// ==================== OpenRouter ==================== - -const OPENROUTER_MODELS: ModelDefinition[] = [ - builtin({ - id: 'deepseek/deepseek-v4-pro', - providerId: 'openrouter', - name: 'DeepSeek V4 Pro', - description: 'DeepSeek V4 Pro via OpenRouter', - contextWindow: 1000000, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'google/gemini-2.5-flash-preview', - providerId: 'openrouter', - name: 'Gemini 2.5 Flash', - description: 'Google Gemini 2.5 Flash via OpenRouter', - contextWindow: 1048576, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'mistralai/mistral-7b-instruct:free', - providerId: 'openrouter', - name: 'Mistral 7B (Free)', - description: 'Mistral 7B free tier', - contextWindow: 32768, - capabilities: ['chat'], - recommendedFor: [], - status: 'stable', - }), -] - -// ==================== xAI ==================== - -const XAI_MODELS: ModelDefinition[] = [ - builtin({ - id: 'grok-4', - providerId: 'xai', - name: 'Grok 4', - description: 'Latest flagship model', - contextWindow: 256000, - capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'grok-3', - providerId: 'xai', - name: 'Grok 3', - description: 'Grok 3 standard', - contextWindow: 131072, - capabilities: ['chat', 'vision', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'grok-3-fast', - providerId: 'xai', - name: 'Grok 3 Fast', - description: 'Grok 3 fast version', - contextWindow: 131072, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'grok-3-mini', - providerId: 'xai', - name: 'Grok 3 Mini', - description: 'Grok 3 small reasoning model', - contextWindow: 131072, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'grok-3-mini-fast', - providerId: 'xai', - name: 'Grok 3 Mini Fast', - description: 'Grok 3 Mini fast version', - contextWindow: 131072, - capabilities: ['chat', 'reasoning', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), -] - -// ==================== MiniMax ==================== - -const MINIMAX_MODELS: ModelDefinition[] = [ - builtin({ - id: 'MiniMax-M2.7', - providerId: 'minimax', - name: 'MiniMax M2.7', - description: 'Latest flagship model', - contextWindow: 200000, - capabilities: ['chat', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'MiniMax-M2.7-highspeed', - providerId: 'minimax', - name: 'MiniMax M2.7 HS', - description: 'M2.7 high-speed', - contextWindow: 200000, - capabilities: ['chat', 'function_calling'], - recommendedFor: ['chat'], - status: 'stable', - }), - builtin({ - id: 'MiniMax-M2.5', - providerId: 'minimax', - name: 'MiniMax M2.5', - description: 'M2.5 standard', - contextWindow: 204800, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), - builtin({ - id: 'MiniMax-M2.5-highspeed', - providerId: 'minimax', - name: 'MiniMax M2.5 HS', - description: 'M2.5 high-speed', - contextWindow: 204800, - capabilities: ['chat', 'function_calling'], - recommendedFor: [], - status: 'stable', - }), -] - -// ==================== 汇总导出 ==================== - -export const BUILTIN_MODELS: ModelDefinition[] = [ - ...OPENAI_MODELS, - ...ANTHROPIC_MODELS, - ...GEMINI_MODELS, - ...DEEPSEEK_MODELS, - ...QWEN_MODELS, - ...GLM_MODELS, - ...KIMI_MODELS, - ...DOUBAO_MODELS, - ...SILICONFLOW_MODELS, - ...GROQ_MODELS, - ...OPENROUTER_MODELS, - ...XAI_MODELS, - ...MINIMAX_MODELS, -] - -/** 按 provider 筛选内置模型 */ -export function getBuiltinModelsByProvider(providerId: string): ModelDefinition[] { - return BUILTIN_MODELS.filter((m) => m.providerId === providerId) -} - -/** 按 id + providerId 查找内置模型 */ -export function getBuiltinModelById(providerId: string, modelId: string): ModelDefinition | null { - return BUILTIN_MODELS.find((m) => m.providerId === providerId && m.id === modelId) || null -} +export { BUILTIN_MODELS, getBuiltinModelsByProvider, getBuiltinModelById } from '@openchatlab/core' diff --git a/electron/main/ai/llm/model-types.ts b/electron/main/ai/llm/model-types.ts index 8173e40b..0b3281fc 100644 --- a/electron/main/ai/llm/model-types.ts +++ b/electron/main/ai/llm/model-types.ts @@ -1,48 +1,24 @@ /** - * 模型系统核心类型定义 - * 基于 Provider Registry + Model Catalog 分层架构 + * 模型系统核心类型定义 — 从 @openchatlab/core 重导出 */ -// ==================== Provider 定义 ==================== +import type { + ProviderDefinition as _ProviderDefinition, + ModelDefinition as _ModelDefinition, + ModelSlot as _ModelSlot, +} from '@openchatlab/core' -export type ProviderKind = 'official' | 'aggregator' | 'openai-compatible' +export type { + ProviderKind, + ProviderDefinition, + ModelCapability, + ModelStatus, + ModelRecommendedFor, + ModelDefinition, + ModelSlot, +} from '@openchatlab/core' -export interface ProviderDefinition { - id: string - name: string - kind: ProviderKind - website?: string - consoleUrl?: string - defaultBaseUrl: string - authMode: 'api-key' - supportsCustomModels: boolean - builtin: boolean - enabledByDefault: boolean - modelIds: string[] -} - -// ==================== Model 定义 ==================== - -export type ModelCapability = 'chat' | 'reasoning' | 'vision' | 'function_calling' | 'embedding' | 'ranking' - -export type ModelStatus = 'stable' | 'preview' | 'deprecated' - -export type ModelRecommendedFor = 'chat' | 'embedding' | 'rerank' - -export interface ModelDefinition { - id: string - providerId: string - name: string - description?: string - contextWindow?: number - capabilities: ModelCapability[] - recommendedFor: ModelRecommendedFor[] - status: ModelStatus - builtin: boolean - editable: boolean -} - -// ==================== 连接配置 ==================== +// Electron 专用扩展类型(不在 core 中) export interface LLMConnectionConfig { id: string @@ -62,8 +38,6 @@ export interface LLMConnectionConfigCompat extends LLMConnectionConfig { customModels?: Array<{ id: string; name: string }> } -// ==================== 用途选择(预留) ==================== - export type ModelUsage = 'chat' | 'embedding' export interface ModelSelectionState { @@ -73,24 +47,17 @@ export interface ModelSelectionState { modelId: string } -// ==================== 存储结构 ==================== - export interface ProviderRegistryStore { - providers: ProviderDefinition[] + providers: _ProviderDefinition[] } export interface ModelCatalogStore { - models: ModelDefinition[] -} - -export interface ModelSlot { - configId: string - modelId: string + models: _ModelDefinition[] } export interface LLMConnectionStore { configs: LLMConnectionConfigCompat[] - defaultAssistant: ModelSlot | null - fastModel: ModelSlot | null + defaultAssistant: _ModelSlot | null + fastModel: _ModelSlot | null schemaVersion: number } diff --git a/electron/main/ai/llm/provider-registry.ts b/electron/main/ai/llm/provider-registry.ts index 5fa36272..1c97a8b7 100644 --- a/electron/main/ai/llm/provider-registry.ts +++ b/electron/main/ai/llm/provider-registry.ts @@ -1,284 +1,5 @@ /** - * Provider Registry — 内置 provider 目录 + * Provider Registry — 从 @openchatlab/core 重导出 */ -import type { ProviderDefinition } from './model-types' - -// ==================== 内置 Provider 目录 ==================== - -const OPENAI: ProviderDefinition = { - id: 'openai', - name: 'OpenAI', - kind: 'official', - website: 'https://openai.com', - consoleUrl: 'https://platform.openai.com/api-keys', - defaultBaseUrl: 'https://api.openai.com/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: [ - 'gpt-5.4-pro', - 'gpt-5.4', - 'gpt-5.2-pro', - 'gpt-5.2', - 'gpt-5.1', - 'gpt-5-pro', - 'gpt-5', - 'gpt-4.1', - 'gpt-4.1-mini', - 'gpt-4.1-nano', - 'gpt-4o', - 'gpt-4o-mini', - 'o3', - 'o4-mini', - 'text-embedding-3-small', - 'text-embedding-3-large', - ], -} - -const ANTHROPIC: ProviderDefinition = { - id: 'anthropic', - name: 'Anthropic', - kind: 'official', - website: 'https://www.anthropic.com', - consoleUrl: 'https://console.anthropic.com/settings/keys', - defaultBaseUrl: 'https://api.anthropic.com', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: [ - 'claude-opus-4-6', - 'claude-sonnet-4-6', - 'claude-opus-4-5', - 'claude-sonnet-4-5', - 'claude-haiku-4-5', - 'claude-sonnet-4-20250514', - 'claude-opus-4-20250514', - 'claude-haiku-3-5-20241022', - ], -} - -const GEMINI: ProviderDefinition = { - id: 'gemini', - name: 'Gemini', - kind: 'official', - website: 'https://ai.google.dev', - consoleUrl: 'https://aistudio.google.com/apikey', - defaultBaseUrl: 'https://generativelanguage.googleapis.com/v1beta', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: [ - 'gemini-3.1-pro-preview', - 'gemini-3-pro-preview', - 'gemini-2.5-flash', - 'gemini-2.5-pro', - 'gemini-2.0-flash', - 'text-embedding-004', - ], -} - -const DEEPSEEK: ProviderDefinition = { - id: 'deepseek', - name: 'DeepSeek', - kind: 'official', - website: 'https://www.deepseek.com', - consoleUrl: 'https://platform.deepseek.com/api_keys', - defaultBaseUrl: 'https://api.deepseek.com/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: ['deepseek-v4-pro', 'deepseek-v4-flash'], -} - -const QWEN: ProviderDefinition = { - id: 'qwen', - name: 'Qwen', - kind: 'official', - website: 'https://tongyi.aliyun.com', - consoleUrl: 'https://dashscope.console.aliyun.com/apiKey', - defaultBaseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: [ - 'qwen3.5-plus', - 'qwen3.5-flash', - 'qwen3-max', - 'qwen-max', - 'qwen-plus', - 'qwen-turbo', - 'qwen-long', - 'text-embedding-v3', - ], -} - -const GLM: ProviderDefinition = { - id: 'glm', - name: 'GLM', - kind: 'official', - website: 'https://www.zhipuai.cn', - consoleUrl: 'https://open.bigmodel.cn/usercenter/apikeys', - defaultBaseUrl: 'https://open.bigmodel.cn/api/paas/v4', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: [ - 'glm-5', - 'glm-4.7', - 'glm-4.6v', - 'glm-4.6v-flash', - 'glm-4.5', - 'glm-4.5-flash', - 'glm-4-plus', - 'glm-4-flash', - 'embedding-3', - ], -} - -const KIMI: ProviderDefinition = { - id: 'kimi', - name: 'Kimi', - kind: 'official', - website: 'https://www.moonshot.cn', - consoleUrl: 'https://platform.moonshot.cn/console/api-keys', - defaultBaseUrl: 'https://api.moonshot.cn/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: ['kimi-k2.5', 'kimi-k2-thinking', 'kimi-k2-thinking-turbo', 'kimi-k2-0905-Preview', 'moonshot-v1-auto'], -} - -const DOUBAO: ProviderDefinition = { - id: 'doubao', - name: 'Doubao', - kind: 'official', - website: 'https://www.volcengine.com/product/doubao', - consoleUrl: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey', - defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: [ - 'doubao-seed-2-0-pro-260215', - 'doubao-seed-2-0-lite-260215', - 'doubao-seed-2-0-mini-260215', - 'doubao-seed-1-8-251228', - 'doubao-seed-1-6-251015', - 'doubao-seed-1-6-lite-251015', - ], -} - -const SILICONFLOW: ProviderDefinition = { - id: 'siliconflow', - name: 'SiliconFlow', - kind: 'official', - website: 'https://siliconflow.cn', - consoleUrl: 'https://cloud.siliconflow.cn/account/ak', - defaultBaseUrl: 'https://api.siliconflow.cn/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: ['deepseek-ai/DeepSeek-V3.2', 'deepseek-ai/DeepSeek-R1', 'Qwen/Qwen3-8B'], -} - -const GROQ: ProviderDefinition = { - id: 'groq', - name: 'Groq', - kind: 'official', - website: 'https://groq.com', - consoleUrl: 'https://console.groq.com/keys', - defaultBaseUrl: 'https://api.groq.com/openai/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: ['llama3-70b-8192', 'llama3-8b-8192', 'mistral-saba-24b', 'gemma-9b-it'], -} - -const OPENROUTER: ProviderDefinition = { - id: 'openrouter', - name: 'OpenRouter', - kind: 'aggregator', - website: 'https://openrouter.ai', - consoleUrl: 'https://openrouter.ai/keys', - defaultBaseUrl: 'https://openrouter.ai/api/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: ['deepseek/deepseek-v4-pro', 'google/gemini-2.5-flash-preview', 'mistralai/mistral-7b-instruct:free'], -} - -const XAI: ProviderDefinition = { - id: 'xai', - name: 'xAI', - kind: 'official', - website: 'https://x.ai', - consoleUrl: 'https://console.x.ai', - defaultBaseUrl: 'https://api.x.ai/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: ['grok-4', 'grok-3', 'grok-3-fast', 'grok-3-mini', 'grok-3-mini-fast'], -} - -const MINIMAX: ProviderDefinition = { - id: 'minimax', - name: 'MiniMax', - kind: 'official', - website: 'https://www.minimaxi.com', - consoleUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key', - defaultBaseUrl: 'https://api.minimaxi.com/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: ['MiniMax-M2.7', 'MiniMax-M2.7-highspeed', 'MiniMax-M2.5', 'MiniMax-M2.5-highspeed'], -} - -const OPENAI_COMPATIBLE: ProviderDefinition = { - id: 'openai-compatible', - name: 'OpenAI Compatible', - kind: 'openai-compatible', - defaultBaseUrl: 'http://localhost:11434/v1', - authMode: 'api-key', - supportsCustomModels: true, - builtin: true, - enabledByDefault: true, - modelIds: [], -} - -// ==================== 内置目录导出 ==================== - -export const BUILTIN_PROVIDERS: ProviderDefinition[] = [ - OPENAI, - ANTHROPIC, - GEMINI, - DEEPSEEK, - QWEN, - GLM, - KIMI, - DOUBAO, - SILICONFLOW, - GROQ, - OPENROUTER, - XAI, - MINIMAX, - OPENAI_COMPATIBLE, -] - -/** 按 id 查找内置 provider */ -export function getBuiltinProviderById(id: string): ProviderDefinition | null { - return BUILTIN_PROVIDERS.find((p) => p.id === id) || null -} +export { BUILTIN_PROVIDERS, getBuiltinProviderById } from '@openchatlab/core' diff --git a/electron/main/ai/skills/parser.ts b/electron/main/ai/skills/parser.ts index a186304a..e791215c 100644 --- a/electron/main/ai/skills/parser.ts +++ b/electron/main/ai/skills/parser.ts @@ -1,68 +1,5 @@ /** - * 技能文件解析器 - * 将 Markdown + YAML Frontmatter 格式的 .md 文件解析为 SkillDef + * 技能文件解析器 — 从 @openchatlab/node-runtime 重导出 */ -import * as path from 'path' -import matter from 'gray-matter' -import type { SkillDef } from './types' - -/** - * 解析技能 Markdown 文件内容为 SkillDef - * - * 解析失败时返回 null(不抛异常),调用方负责处理。 - * 单文件解析失败不能影响整个 SkillManager 初始化。 - */ -export function parseSkillFile(content: string, filePath: string): SkillDef | null { - try { - const { data: fm, content: prompt } = matter(content) - - const id = fm.id ?? path.basename(filePath, '.md') - const name = fm.name - if (!name) { - return null - } - - return { - id, - name, - description: fm.description ?? '', - tags: parseTags(fm.tags), - chatScope: validateChatScope(fm.chatScope), - tools: Array.isArray(fm.tools) ? fm.tools : [], - prompt: prompt.trim(), - } - } catch { - return null - } -} - -/** - * 将技能 Markdown 原始内容的 frontmatter 中提取 id(轻量级,不做完整解析) - */ -export function extractSkillId(content: string, filePath: string): string | null { - try { - const { data: fm } = matter(content) - return fm.id ?? path.basename(filePath, '.md') - } catch { - return null - } -} - -function parseTags(raw: unknown): string[] { - if (typeof raw === 'string') { - return raw - .split(',') - .map((t) => t.trim()) - .filter(Boolean) - } - if (Array.isArray(raw)) { - return raw.map(String).filter(Boolean) - } - return [] -} - -function validateChatScope(raw: unknown): 'all' | 'group' | 'private' { - if (raw === 'group' || raw === 'private') return raw - return 'all' -} +export { parseSkillFile, extractSkillId } from '@openchatlab/node-runtime' diff --git a/packages/core/src/ai/index.ts b/packages/core/src/ai/index.ts new file mode 100644 index 00000000..b61e2996 --- /dev/null +++ b/packages/core/src/ai/index.ts @@ -0,0 +1,24 @@ +/** + * AI 模块(平台无关的静态数据和类型) + */ + +// 内置工具目录 +export type { ToolCategory, BuiltinToolCatalogEntry } from './tool-catalog' +export { BUILTIN_TOOL_CATALOG } from './tool-catalog' + +// LLM 模型系统类型 +export type { + ProviderKind, + ProviderDefinition, + ModelCapability, + ModelStatus, + ModelRecommendedFor, + ModelDefinition, + ModelSlot, +} from './model-types' + +// Provider Registry(内置 provider 目录) +export { BUILTIN_PROVIDERS, getBuiltinProviderById } from './provider-registry' + +// Model Catalog(内置模型目录) +export { BUILTIN_MODELS, getBuiltinModelsByProvider, getBuiltinModelById } from './model-catalog' diff --git a/packages/core/src/ai/model-catalog.ts b/packages/core/src/ai/model-catalog.ts new file mode 100644 index 00000000..e1ede409 --- /dev/null +++ b/packages/core/src/ai/model-catalog.ts @@ -0,0 +1,897 @@ +/** + * Model Catalog — 内置模型目录 + */ + +import type { ModelDefinition } from './model-types' +export type { ModelDefinition } + +// ==================== Helper ==================== + +type ModelInput = Omit + +function builtin(m: ModelInput): ModelDefinition { + return { ...m, builtin: true, editable: false } +} + +// ==================== OpenAI ==================== + +const OPENAI_MODELS: ModelDefinition[] = [ + builtin({ + id: 'gpt-5.4-pro', + providerId: 'openai', + name: 'GPT-5.4 Pro', + description: 'Latest flagship, best reasoning & coding', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'gpt-5.4', + providerId: 'openai', + name: 'GPT-5.4', + description: 'GPT-5.4 standard', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'gpt-5.2-pro', + providerId: 'openai', + name: 'GPT-5.2 Pro', + description: 'GPT-5.2 Pro', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'gpt-5.2', + providerId: 'openai', + name: 'GPT-5.2', + description: 'GPT-5.2 standard', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'gpt-5.1', + providerId: 'openai', + name: 'GPT-5.1', + description: 'GPT-5.1', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'gpt-5-pro', + providerId: 'openai', + name: 'GPT-5 Pro', + description: 'GPT-5 Pro', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'gpt-5', + providerId: 'openai', + name: 'GPT-5', + description: 'GPT-5 standard', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'gpt-4.1', + providerId: 'openai', + name: 'GPT-4.1', + description: 'Best at coding & instruction following', + contextWindow: 1047576, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'gpt-4.1-mini', + providerId: 'openai', + name: 'GPT-4.1 Mini', + description: 'Cost-effective', + contextWindow: 1047576, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'gpt-4.1-nano', + providerId: 'openai', + name: 'GPT-4.1 Nano', + description: 'Fastest and most affordable', + contextWindow: 1047576, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'gpt-4o', + providerId: 'openai', + name: 'GPT-4o', + description: 'Multimodal flagship model', + contextWindow: 128000, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'gpt-4o-mini', + providerId: 'openai', + name: 'GPT-4o Mini', + description: 'Lightweight multimodal model', + contextWindow: 128000, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'o3', + providerId: 'openai', + name: 'o3', + description: 'Advanced reasoning model', + contextWindow: 200000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'o4-mini', + providerId: 'openai', + name: 'o4-mini', + description: 'Efficient reasoning model', + contextWindow: 200000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'text-embedding-3-small', + providerId: 'openai', + name: 'Text Embedding 3 Small', + description: 'Efficient embedding model', + contextWindow: 8191, + capabilities: ['embedding'], + recommendedFor: ['embedding'], + status: 'stable', + }), + builtin({ + id: 'text-embedding-3-large', + providerId: 'openai', + name: 'Text Embedding 3 Large', + description: 'High-precision embedding model', + contextWindow: 8191, + capabilities: ['embedding'], + recommendedFor: ['embedding'], + status: 'stable', + }), +] + +// ==================== Anthropic ==================== + +const ANTHROPIC_MODELS: ModelDefinition[] = [ + builtin({ + id: 'claude-opus-4-6', + providerId: 'anthropic', + name: 'Claude Opus 4.6', + description: 'Latest flagship, adaptive thinking', + contextWindow: 200000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'claude-sonnet-4-6', + providerId: 'anthropic', + name: 'Claude Sonnet 4.6', + description: 'Cost-effective, adaptive thinking', + contextWindow: 200000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'claude-opus-4-5', + providerId: 'anthropic', + name: 'Claude Opus 4.5', + description: 'Previous generation flagship', + contextWindow: 200000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'claude-sonnet-4-5', + providerId: 'anthropic', + name: 'Claude Sonnet 4.5', + description: 'Previous generation cost-effective', + contextWindow: 200000, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'claude-haiku-4-5', + providerId: 'anthropic', + name: 'Claude Haiku 4.5', + description: 'Fast and lightweight', + contextWindow: 200000, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'claude-sonnet-4-20250514', + providerId: 'anthropic', + name: 'Claude Sonnet 4', + description: 'Sonnet 4 snapshot', + contextWindow: 200000, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'claude-opus-4-20250514', + providerId: 'anthropic', + name: 'Claude Opus 4', + description: 'Opus 4 snapshot', + contextWindow: 200000, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'claude-haiku-3-5-20241022', + providerId: 'anthropic', + name: 'Claude 3.5 Haiku', + description: '3.5 Haiku snapshot', + contextWindow: 200000, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), +] + +// ==================== Gemini ==================== + +const GEMINI_MODELS: ModelDefinition[] = [ + builtin({ + id: 'gemini-3.1-pro-preview', + providerId: 'gemini', + name: 'Gemini 3.1 Pro', + description: 'Latest Gemini 3.1 Pro preview', + contextWindow: 1048576, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'preview', + }), + builtin({ + id: 'gemini-3-pro-preview', + providerId: 'gemini', + name: 'Gemini 3 Pro', + description: 'Gemini 3 Pro preview', + contextWindow: 1048576, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'preview', + }), + builtin({ + id: 'gemini-2.5-flash', + providerId: 'gemini', + name: 'Gemini 2.5 Flash', + description: 'Cost-effective, low latency, adaptive thinking', + contextWindow: 1048576, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'gemini-2.5-pro', + providerId: 'gemini', + name: 'Gemini 2.5 Pro', + description: 'Deep reasoning and complex tasks', + contextWindow: 1048576, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'gemini-2.0-flash', + providerId: 'gemini', + name: 'Gemini 2.0 Flash', + description: 'Previous generation fast model', + contextWindow: 1048576, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'text-embedding-004', + providerId: 'gemini', + name: 'Text Embedding 004', + description: 'Google embedding model', + contextWindow: 2048, + capabilities: ['embedding'], + recommendedFor: ['embedding'], + status: 'stable', + }), +] + +// ==================== DeepSeek ==================== + +const DEEPSEEK_MODELS: ModelDefinition[] = [ + builtin({ + id: 'deepseek-v4-pro', + providerId: 'deepseek', + name: 'DeepSeek V4 Pro', + description: 'Flagship model, 1M context, thinking & non-thinking', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'deepseek-v4-flash', + providerId: 'deepseek', + name: 'DeepSeek V4 Flash', + description: 'Fast model, 1M context, thinking & non-thinking', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), +] + +// ==================== 通义千问 ==================== + +const QWEN_MODELS: ModelDefinition[] = [ + builtin({ + id: 'qwen3.5-plus', + providerId: 'qwen', + name: 'Qwen3.5 Plus', + description: 'Latest Qwen3.5 flagship', + contextWindow: 262144, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'qwen3.5-flash', + providerId: 'qwen', + name: 'Qwen3.5 Flash', + description: 'Qwen3.5 fast version', + contextWindow: 262144, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'qwen3-max', + providerId: 'qwen', + name: 'Qwen3 Max', + description: 'Qwen3 massive-scale', + contextWindow: 262144, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'qwen-max', + providerId: 'qwen', + name: 'Qwen Max', + description: 'Massive-scale language model', + contextWindow: 262144, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'qwen-plus', + providerId: 'qwen', + name: 'Qwen Plus', + description: 'Good quality, suitable for most scenarios', + contextWindow: 262144, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'qwen-turbo', + providerId: 'qwen', + name: 'Qwen Turbo', + description: 'Fast and cost-effective', + contextWindow: 1000000, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'qwen-long', + providerId: 'qwen', + name: 'Qwen Long', + description: 'Ultra-long context model', + contextWindow: 262144, + capabilities: ['chat'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'text-embedding-v3', + providerId: 'qwen', + name: 'Text Embedding V3', + description: 'Qwen embedding model', + contextWindow: 8192, + capabilities: ['embedding'], + recommendedFor: ['embedding'], + status: 'stable', + }), +] + +// ==================== 智谱 GLM ==================== + +const GLM_MODELS: ModelDefinition[] = [ + builtin({ + id: 'glm-5', + providerId: 'glm', + name: 'GLM-5', + description: 'Latest flagship model', + contextWindow: 200000, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'glm-4.7', + providerId: 'glm', + name: 'GLM-4.7', + description: 'GLM-4.7 high performance', + contextWindow: 200000, + capabilities: ['chat', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'glm-4.6v', + providerId: 'glm', + name: 'GLM-4.6V', + description: '4.6 multimodal model', + contextWindow: 200000, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'glm-4.6v-flash', + providerId: 'glm', + name: 'GLM-4.6V Flash', + description: '4.6V free multimodal', + contextWindow: 200000, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'glm-4.5', + providerId: 'glm', + name: 'GLM-4.5', + description: 'GLM-4.5 standard', + contextWindow: 200000, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'glm-4.5-flash', + providerId: 'glm', + name: 'GLM-4.5 Flash', + description: '4.5 free model', + contextWindow: 200000, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'glm-4-plus', + providerId: 'glm', + name: 'GLM-4 Plus', + description: 'Previous generation flagship', + contextWindow: 128000, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'glm-4-flash', + providerId: 'glm', + name: 'GLM-4 Flash', + description: 'Fast model', + contextWindow: 128000, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'embedding-3', + providerId: 'glm', + name: 'Embedding-3', + description: 'Zhipu embedding model', + contextWindow: 8192, + capabilities: ['embedding'], + recommendedFor: ['embedding'], + status: 'stable', + }), +] + +// ==================== Kimi ==================== + +const KIMI_MODELS: ModelDefinition[] = [ + builtin({ + id: 'kimi-k2.5', + providerId: 'kimi', + name: 'Kimi K2.5', + description: 'Latest flagship with vision support', + contextWindow: 262144, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'kimi-k2-thinking', + providerId: 'kimi', + name: 'Kimi K2 Thinking', + description: 'K2 deep reasoning', + contextWindow: 131072, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'kimi-k2-thinking-turbo', + providerId: 'kimi', + name: 'Kimi K2 Thinking Turbo', + description: 'K2 fast reasoning', + contextWindow: 131072, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'kimi-k2-0905-Preview', + providerId: 'kimi', + name: 'Kimi K2 Preview', + description: 'K2 preview', + contextWindow: 131072, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'preview', + }), + builtin({ + id: 'moonshot-v1-auto', + providerId: 'kimi', + name: 'Moonshot V1 Auto', + description: 'Auto-select context length', + contextWindow: 131072, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), +] + +// ==================== 豆包 ==================== + +const DOUBAO_MODELS: ModelDefinition[] = [ + builtin({ + id: 'doubao-seed-2-0-pro-260215', + providerId: 'doubao', + name: 'Doubao Seed 2.0 Pro', + description: 'Latest flagship model', + contextWindow: 256000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'doubao-seed-2-0-lite-260215', + providerId: 'doubao', + name: 'Doubao Seed 2.0 Lite', + description: 'Seed 2.0 lightweight', + contextWindow: 256000, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'doubao-seed-2-0-mini-260215', + providerId: 'doubao', + name: 'Doubao Seed 2.0 Mini', + description: 'Seed 2.0 ultra-fast', + contextWindow: 256000, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'doubao-seed-1-8-251228', + providerId: 'doubao', + name: 'Doubao Seed 1.8', + description: 'Previous generation flagship', + contextWindow: 128000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'doubao-seed-1-6-251015', + providerId: 'doubao', + name: 'Doubao Seed 1.6', + description: 'High-performance general model', + contextWindow: 128000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'doubao-seed-1-6-lite-251015', + providerId: 'doubao', + name: 'Doubao Seed 1.6 Lite', + description: 'Lightweight and cost-effective', + contextWindow: 128000, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), +] + +// ==================== 硅基流动 ==================== + +const SILICONFLOW_MODELS: ModelDefinition[] = [ + builtin({ + id: 'deepseek-ai/DeepSeek-V3.2', + providerId: 'siliconflow', + name: 'DeepSeek V3.2', + description: 'DeepSeek V3.2 on SiliconFlow', + contextWindow: 128000, + capabilities: ['chat', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'deepseek-ai/DeepSeek-R1', + providerId: 'siliconflow', + name: 'DeepSeek R1', + description: 'DeepSeek R1 reasoning model', + contextWindow: 65536, + capabilities: ['chat', 'reasoning'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'Qwen/Qwen3-8B', + providerId: 'siliconflow', + name: 'Qwen3 8B', + description: 'Qwen3 8B lightweight model', + contextWindow: 32768, + capabilities: ['chat'], + recommendedFor: [], + status: 'stable', + }), +] + +// ==================== Groq ==================== + +const GROQ_MODELS: ModelDefinition[] = [ + builtin({ + id: 'llama3-70b-8192', + providerId: 'groq', + name: 'LLaMA3 70B', + description: 'Meta LLaMA3 70B ultra-fast inference', + contextWindow: 8192, + capabilities: ['chat', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'llama3-8b-8192', + providerId: 'groq', + name: 'LLaMA3 8B', + description: 'Meta LLaMA3 8B lightweight ultra-fast', + contextWindow: 8192, + capabilities: ['chat'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'mistral-saba-24b', + providerId: 'groq', + name: 'Mistral Saba 24B', + description: 'Mistral Saba 24B', + contextWindow: 32768, + capabilities: ['chat'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'gemma-9b-it', + providerId: 'groq', + name: 'Gemma 9B', + description: 'Google Gemma 9B', + contextWindow: 8192, + capabilities: ['chat'], + recommendedFor: [], + status: 'stable', + }), +] + +// ==================== OpenRouter ==================== + +const OPENROUTER_MODELS: ModelDefinition[] = [ + builtin({ + id: 'deepseek/deepseek-v4-pro', + providerId: 'openrouter', + name: 'DeepSeek V4 Pro', + description: 'DeepSeek V4 Pro via OpenRouter', + contextWindow: 1000000, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'google/gemini-2.5-flash-preview', + providerId: 'openrouter', + name: 'Gemini 2.5 Flash', + description: 'Google Gemini 2.5 Flash via OpenRouter', + contextWindow: 1048576, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'mistralai/mistral-7b-instruct:free', + providerId: 'openrouter', + name: 'Mistral 7B (Free)', + description: 'Mistral 7B free tier', + contextWindow: 32768, + capabilities: ['chat'], + recommendedFor: [], + status: 'stable', + }), +] + +// ==================== xAI ==================== + +const XAI_MODELS: ModelDefinition[] = [ + builtin({ + id: 'grok-4', + providerId: 'xai', + name: 'Grok 4', + description: 'Latest flagship model', + contextWindow: 256000, + capabilities: ['chat', 'reasoning', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'grok-3', + providerId: 'xai', + name: 'Grok 3', + description: 'Grok 3 standard', + contextWindow: 131072, + capabilities: ['chat', 'vision', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'grok-3-fast', + providerId: 'xai', + name: 'Grok 3 Fast', + description: 'Grok 3 fast version', + contextWindow: 131072, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'grok-3-mini', + providerId: 'xai', + name: 'Grok 3 Mini', + description: 'Grok 3 small reasoning model', + contextWindow: 131072, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'grok-3-mini-fast', + providerId: 'xai', + name: 'Grok 3 Mini Fast', + description: 'Grok 3 Mini fast version', + contextWindow: 131072, + capabilities: ['chat', 'reasoning', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), +] + +// ==================== MiniMax ==================== + +const MINIMAX_MODELS: ModelDefinition[] = [ + builtin({ + id: 'MiniMax-M2.7', + providerId: 'minimax', + name: 'MiniMax M2.7', + description: 'Latest flagship model', + contextWindow: 200000, + capabilities: ['chat', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'MiniMax-M2.7-highspeed', + providerId: 'minimax', + name: 'MiniMax M2.7 HS', + description: 'M2.7 high-speed', + contextWindow: 200000, + capabilities: ['chat', 'function_calling'], + recommendedFor: ['chat'], + status: 'stable', + }), + builtin({ + id: 'MiniMax-M2.5', + providerId: 'minimax', + name: 'MiniMax M2.5', + description: 'M2.5 standard', + contextWindow: 204800, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), + builtin({ + id: 'MiniMax-M2.5-highspeed', + providerId: 'minimax', + name: 'MiniMax M2.5 HS', + description: 'M2.5 high-speed', + contextWindow: 204800, + capabilities: ['chat', 'function_calling'], + recommendedFor: [], + status: 'stable', + }), +] + +// ==================== 汇总导出 ==================== + +export const BUILTIN_MODELS: ModelDefinition[] = [ + ...OPENAI_MODELS, + ...ANTHROPIC_MODELS, + ...GEMINI_MODELS, + ...DEEPSEEK_MODELS, + ...QWEN_MODELS, + ...GLM_MODELS, + ...KIMI_MODELS, + ...DOUBAO_MODELS, + ...SILICONFLOW_MODELS, + ...GROQ_MODELS, + ...OPENROUTER_MODELS, + ...XAI_MODELS, + ...MINIMAX_MODELS, +] + +/** 按 provider 筛选内置模型 */ +export function getBuiltinModelsByProvider(providerId: string): ModelDefinition[] { + return BUILTIN_MODELS.filter((m) => m.providerId === providerId) +} + +/** 按 id + providerId 查找内置模型 */ +export function getBuiltinModelById(providerId: string, modelId: string): ModelDefinition | null { + return BUILTIN_MODELS.find((m) => m.providerId === providerId && m.id === modelId) || null +} diff --git a/packages/core/src/ai/model-types.ts b/packages/core/src/ai/model-types.ts new file mode 100644 index 00000000..8173e40b --- /dev/null +++ b/packages/core/src/ai/model-types.ts @@ -0,0 +1,96 @@ +/** + * 模型系统核心类型定义 + * 基于 Provider Registry + Model Catalog 分层架构 + */ + +// ==================== Provider 定义 ==================== + +export type ProviderKind = 'official' | 'aggregator' | 'openai-compatible' + +export interface ProviderDefinition { + id: string + name: string + kind: ProviderKind + website?: string + consoleUrl?: string + defaultBaseUrl: string + authMode: 'api-key' + supportsCustomModels: boolean + builtin: boolean + enabledByDefault: boolean + modelIds: string[] +} + +// ==================== Model 定义 ==================== + +export type ModelCapability = 'chat' | 'reasoning' | 'vision' | 'function_calling' | 'embedding' | 'ranking' + +export type ModelStatus = 'stable' | 'preview' | 'deprecated' + +export type ModelRecommendedFor = 'chat' | 'embedding' | 'rerank' + +export interface ModelDefinition { + id: string + providerId: string + name: string + description?: string + contextWindow?: number + capabilities: ModelCapability[] + recommendedFor: ModelRecommendedFor[] + status: ModelStatus + builtin: boolean + editable: boolean +} + +// ==================== 连接配置 ==================== + +export interface LLMConnectionConfig { + id: string + name: string + providerId: string + modelId: string + apiKey: string + baseUrl?: string + maxTokens?: number + createdAt: number + updatedAt: number +} + +export interface LLMConnectionConfigCompat extends LLMConnectionConfig { + disableThinking?: boolean + isReasoningModel?: boolean + customModels?: Array<{ id: string; name: string }> +} + +// ==================== 用途选择(预留) ==================== + +export type ModelUsage = 'chat' | 'embedding' + +export interface ModelSelectionState { + usage: ModelUsage + configId: string + providerId: string + modelId: string +} + +// ==================== 存储结构 ==================== + +export interface ProviderRegistryStore { + providers: ProviderDefinition[] +} + +export interface ModelCatalogStore { + models: ModelDefinition[] +} + +export interface ModelSlot { + configId: string + modelId: string +} + +export interface LLMConnectionStore { + configs: LLMConnectionConfigCompat[] + defaultAssistant: ModelSlot | null + fastModel: ModelSlot | null + schemaVersion: number +} diff --git a/packages/core/src/ai/provider-registry.ts b/packages/core/src/ai/provider-registry.ts new file mode 100644 index 00000000..cba6c282 --- /dev/null +++ b/packages/core/src/ai/provider-registry.ts @@ -0,0 +1,285 @@ +/** + * Provider Registry — 内置 provider 目录 + */ + +import type { ProviderDefinition } from './model-types' +export type { ProviderDefinition } + +// ==================== 内置 Provider 目录 ==================== + +const OPENAI: ProviderDefinition = { + id: 'openai', + name: 'OpenAI', + kind: 'official', + website: 'https://openai.com', + consoleUrl: 'https://platform.openai.com/api-keys', + defaultBaseUrl: 'https://api.openai.com/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: [ + 'gpt-5.4-pro', + 'gpt-5.4', + 'gpt-5.2-pro', + 'gpt-5.2', + 'gpt-5.1', + 'gpt-5-pro', + 'gpt-5', + 'gpt-4.1', + 'gpt-4.1-mini', + 'gpt-4.1-nano', + 'gpt-4o', + 'gpt-4o-mini', + 'o3', + 'o4-mini', + 'text-embedding-3-small', + 'text-embedding-3-large', + ], +} + +const ANTHROPIC: ProviderDefinition = { + id: 'anthropic', + name: 'Anthropic', + kind: 'official', + website: 'https://www.anthropic.com', + consoleUrl: 'https://console.anthropic.com/settings/keys', + defaultBaseUrl: 'https://api.anthropic.com', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: [ + 'claude-opus-4-6', + 'claude-sonnet-4-6', + 'claude-opus-4-5', + 'claude-sonnet-4-5', + 'claude-haiku-4-5', + 'claude-sonnet-4-20250514', + 'claude-opus-4-20250514', + 'claude-haiku-3-5-20241022', + ], +} + +const GEMINI: ProviderDefinition = { + id: 'gemini', + name: 'Gemini', + kind: 'official', + website: 'https://ai.google.dev', + consoleUrl: 'https://aistudio.google.com/apikey', + defaultBaseUrl: 'https://generativelanguage.googleapis.com/v1beta', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: [ + 'gemini-3.1-pro-preview', + 'gemini-3-pro-preview', + 'gemini-2.5-flash', + 'gemini-2.5-pro', + 'gemini-2.0-flash', + 'text-embedding-004', + ], +} + +const DEEPSEEK: ProviderDefinition = { + id: 'deepseek', + name: 'DeepSeek', + kind: 'official', + website: 'https://www.deepseek.com', + consoleUrl: 'https://platform.deepseek.com/api_keys', + defaultBaseUrl: 'https://api.deepseek.com/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: ['deepseek-v4-pro', 'deepseek-v4-flash'], +} + +const QWEN: ProviderDefinition = { + id: 'qwen', + name: 'Qwen', + kind: 'official', + website: 'https://tongyi.aliyun.com', + consoleUrl: 'https://dashscope.console.aliyun.com/apiKey', + defaultBaseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: [ + 'qwen3.5-plus', + 'qwen3.5-flash', + 'qwen3-max', + 'qwen-max', + 'qwen-plus', + 'qwen-turbo', + 'qwen-long', + 'text-embedding-v3', + ], +} + +const GLM: ProviderDefinition = { + id: 'glm', + name: 'GLM', + kind: 'official', + website: 'https://www.zhipuai.cn', + consoleUrl: 'https://open.bigmodel.cn/usercenter/apikeys', + defaultBaseUrl: 'https://open.bigmodel.cn/api/paas/v4', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: [ + 'glm-5', + 'glm-4.7', + 'glm-4.6v', + 'glm-4.6v-flash', + 'glm-4.5', + 'glm-4.5-flash', + 'glm-4-plus', + 'glm-4-flash', + 'embedding-3', + ], +} + +const KIMI: ProviderDefinition = { + id: 'kimi', + name: 'Kimi', + kind: 'official', + website: 'https://www.moonshot.cn', + consoleUrl: 'https://platform.moonshot.cn/console/api-keys', + defaultBaseUrl: 'https://api.moonshot.cn/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: ['kimi-k2.5', 'kimi-k2-thinking', 'kimi-k2-thinking-turbo', 'kimi-k2-0905-Preview', 'moonshot-v1-auto'], +} + +const DOUBAO: ProviderDefinition = { + id: 'doubao', + name: 'Doubao', + kind: 'official', + website: 'https://www.volcengine.com/product/doubao', + consoleUrl: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey', + defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: [ + 'doubao-seed-2-0-pro-260215', + 'doubao-seed-2-0-lite-260215', + 'doubao-seed-2-0-mini-260215', + 'doubao-seed-1-8-251228', + 'doubao-seed-1-6-251015', + 'doubao-seed-1-6-lite-251015', + ], +} + +const SILICONFLOW: ProviderDefinition = { + id: 'siliconflow', + name: 'SiliconFlow', + kind: 'official', + website: 'https://siliconflow.cn', + consoleUrl: 'https://cloud.siliconflow.cn/account/ak', + defaultBaseUrl: 'https://api.siliconflow.cn/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: ['deepseek-ai/DeepSeek-V3.2', 'deepseek-ai/DeepSeek-R1', 'Qwen/Qwen3-8B'], +} + +const GROQ: ProviderDefinition = { + id: 'groq', + name: 'Groq', + kind: 'official', + website: 'https://groq.com', + consoleUrl: 'https://console.groq.com/keys', + defaultBaseUrl: 'https://api.groq.com/openai/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: ['llama3-70b-8192', 'llama3-8b-8192', 'mistral-saba-24b', 'gemma-9b-it'], +} + +const OPENROUTER: ProviderDefinition = { + id: 'openrouter', + name: 'OpenRouter', + kind: 'aggregator', + website: 'https://openrouter.ai', + consoleUrl: 'https://openrouter.ai/keys', + defaultBaseUrl: 'https://openrouter.ai/api/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: ['deepseek/deepseek-v4-pro', 'google/gemini-2.5-flash-preview', 'mistralai/mistral-7b-instruct:free'], +} + +const XAI: ProviderDefinition = { + id: 'xai', + name: 'xAI', + kind: 'official', + website: 'https://x.ai', + consoleUrl: 'https://console.x.ai', + defaultBaseUrl: 'https://api.x.ai/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: ['grok-4', 'grok-3', 'grok-3-fast', 'grok-3-mini', 'grok-3-mini-fast'], +} + +const MINIMAX: ProviderDefinition = { + id: 'minimax', + name: 'MiniMax', + kind: 'official', + website: 'https://www.minimaxi.com', + consoleUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key', + defaultBaseUrl: 'https://api.minimaxi.com/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: ['MiniMax-M2.7', 'MiniMax-M2.7-highspeed', 'MiniMax-M2.5', 'MiniMax-M2.5-highspeed'], +} + +const OPENAI_COMPATIBLE: ProviderDefinition = { + id: 'openai-compatible', + name: 'OpenAI Compatible', + kind: 'openai-compatible', + defaultBaseUrl: 'http://localhost:11434/v1', + authMode: 'api-key', + supportsCustomModels: true, + builtin: true, + enabledByDefault: true, + modelIds: [], +} + +// ==================== 内置目录导出 ==================== + +export const BUILTIN_PROVIDERS: ProviderDefinition[] = [ + OPENAI, + ANTHROPIC, + GEMINI, + DEEPSEEK, + QWEN, + GLM, + KIMI, + DOUBAO, + SILICONFLOW, + GROQ, + OPENROUTER, + XAI, + MINIMAX, + OPENAI_COMPATIBLE, +] + +/** 按 id 查找内置 provider */ +export function getBuiltinProviderById(id: string): ProviderDefinition | null { + return BUILTIN_PROVIDERS.find((p) => p.id === id) || null +} diff --git a/packages/core/src/ai/tool-catalog.ts b/packages/core/src/ai/tool-catalog.ts new file mode 100644 index 00000000..49523262 --- /dev/null +++ b/packages/core/src/ai/tool-catalog.ts @@ -0,0 +1,47 @@ +/** + * 内置工具目录(静态数据) + * + * 仅包含工具名称和分类,不含任何运行时依赖。 + * 用于前端展示工具列表和助手配置面板。 + */ + +export type ToolCategory = 'core' | 'analysis' + +export interface BuiltinToolCatalogEntry { + name: string + category: ToolCategory +} + +export const BUILTIN_TOOL_CATALOG: BuiltinToolCatalogEntry[] = [ + // Core 工具 + { name: 'get_chat_overview', category: 'core' }, + { name: 'search_messages', category: 'core' }, + { name: 'deep_search_messages', category: 'core' }, + { name: 'get_recent_messages', category: 'core' }, + { name: 'get_message_context', category: 'core' }, + { name: 'search_sessions', category: 'core' }, + { name: 'get_session_messages', category: 'core' }, + { name: 'get_members', category: 'core' }, + + // Analysis 工具 + { name: 'get_member_stats', category: 'analysis' }, + { name: 'get_time_stats', category: 'analysis' }, + { name: 'get_member_name_history', category: 'analysis' }, + { name: 'get_conversation_between', category: 'analysis' }, + { name: 'get_session_summaries', category: 'analysis' }, + { name: 'response_time_analysis', category: 'analysis' }, + { name: 'keyword_frequency', category: 'analysis' }, + + // SQL Analysis 工具 + { name: 'message_type_breakdown', category: 'analysis' }, + { name: 'peak_chat_hours_by_member', category: 'analysis' }, + { name: 'member_activity_trend', category: 'analysis' }, + { name: 'silent_members', category: 'analysis' }, + { name: 'reply_interaction_ranking', category: 'analysis' }, + { name: 'mutual_interaction_pairs', category: 'analysis' }, + { name: 'member_message_length_stats', category: 'analysis' }, + { name: 'daily_active_members', category: 'analysis' }, + { name: 'conversation_initiator_stats', category: 'analysis' }, + { name: 'activity_heatmap', category: 'analysis' }, + { name: 'unanswered_messages', category: 'analysis' }, +] diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 490b89c2..7d1db057 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -116,6 +116,26 @@ export type { DictInfo, } from './nlp' +// AI(内置工具目录、LLM 模型系统等静态数据) +export type { ToolCategory, BuiltinToolCatalogEntry } from './ai' +export { BUILTIN_TOOL_CATALOG } from './ai' +export type { + ProviderKind, + ProviderDefinition, + ModelCapability, + ModelStatus, + ModelRecommendedFor, + ModelDefinition, + ModelSlot, +} from './ai' +export { + BUILTIN_PROVIDERS, + getBuiltinProviderById, + BUILTIN_MODELS, + getBuiltinModelsByProvider, + getBuiltinModelById, +} from './ai' + // Schema 与迁移 export { CURRENT_SCHEMA_VERSION, diff --git a/packages/node-runtime/package.json b/packages/node-runtime/package.json index 1554c5f9..648ab24a 100644 --- a/packages/node-runtime/package.json +++ b/packages/node-runtime/package.json @@ -6,6 +6,7 @@ "main": "./src/index.ts", "types": "./src/index.ts", "dependencies": { - "@openchatlab/core": "workspace:*" + "@openchatlab/core": "workspace:*", + "gray-matter": "^4.0.3" } } diff --git a/packages/node-runtime/src/ai/assistant-parser.ts b/packages/node-runtime/src/ai/assistant-parser.ts new file mode 100644 index 00000000..20b618e8 --- /dev/null +++ b/packages/node-runtime/src/ai/assistant-parser.ts @@ -0,0 +1,56 @@ +/** + * 助手 MD 文件解析器(平台无关,Node.js 实现) + */ + +import * as path from 'path' +import matter from 'gray-matter' +import type { AssistantConfig } from './types' + +export function parseAssistantFile(content: string, filePath: string): AssistantConfig | null { + try { + const { data: fm, content: body } = matter(content) + + const id = fm.id ?? path.basename(filePath, '.md') + const name = fm.name + if (!name) return null + + return { + id, + name, + systemPrompt: body.trim(), + presetQuestions: parseStringArray(fm.presetQuestions), + allowedBuiltinTools: parseStringArray(fm.allowedBuiltinTools), + builtinId: typeof fm.builtinId === 'string' ? fm.builtinId : undefined, + applicableChatTypes: parseChatTypes(fm.applicableChatTypes), + supportedLocales: parseStringArray(fm.supportedLocales), + } + } catch { + return null + } +} + +export function serializeAssistant(config: AssistantConfig): string { + const fm: Record = { + id: config.id, + name: config.name, + } + + if (config.builtinId) fm.builtinId = config.builtinId + if (config.applicableChatTypes?.length) fm.applicableChatTypes = config.applicableChatTypes + if (config.supportedLocales?.length) fm.supportedLocales = config.supportedLocales + if (config.allowedBuiltinTools?.length) fm.allowedBuiltinTools = config.allowedBuiltinTools + if (config.presetQuestions?.length) fm.presetQuestions = config.presetQuestions + + return matter.stringify(`\n${config.systemPrompt}\n`, fm) +} + +function parseStringArray(raw: unknown): string[] { + if (Array.isArray(raw)) return raw.map(String).filter(Boolean) + return [] +} + +function parseChatTypes(raw: unknown): ('group' | 'private')[] | undefined { + if (!Array.isArray(raw)) return undefined + const valid = raw.filter((v): v is 'group' | 'private' => v === 'group' || v === 'private') + return valid.length > 0 ? valid : undefined +} diff --git a/packages/node-runtime/src/ai/index.ts b/packages/node-runtime/src/ai/index.ts new file mode 100644 index 00000000..65a077e9 --- /dev/null +++ b/packages/node-runtime/src/ai/index.ts @@ -0,0 +1,9 @@ +/** + * AI 模块(Node.js 实现) + * + * 助手/技能 MD 文件解析器和共享类型。 + */ + +export type { AssistantConfig, AssistantSummary, SkillDef, SkillSummary } from './types' +export { parseAssistantFile, serializeAssistant } from './assistant-parser' +export { parseSkillFile, extractSkillId } from './skill-parser' diff --git a/packages/node-runtime/src/ai/skill-parser.ts b/packages/node-runtime/src/ai/skill-parser.ts new file mode 100644 index 00000000..eb91ad4e --- /dev/null +++ b/packages/node-runtime/src/ai/skill-parser.ts @@ -0,0 +1,54 @@ +/** + * 技能 MD 文件解析器(平台无关,Node.js 实现) + */ + +import * as path from 'path' +import matter from 'gray-matter' +import type { SkillDef } from './types' + +export function parseSkillFile(content: string, filePath: string): SkillDef | null { + try { + const { data: fm, content: prompt } = matter(content) + + const id = fm.id ?? path.basename(filePath, '.md') + const name = fm.name + if (!name) return null + + return { + id, + name, + description: fm.description ?? '', + tags: parseTags(fm.tags), + chatScope: validateChatScope(fm.chatScope), + tools: Array.isArray(fm.tools) ? fm.tools : [], + prompt: prompt.trim(), + } + } catch { + return null + } +} + +export function extractSkillId(content: string, filePath: string): string | null { + try { + const { data: fm } = matter(content) + return fm.id ?? path.basename(filePath, '.md') + } catch { + return null + } +} + +function parseTags(raw: unknown): string[] { + if (typeof raw === 'string') { + return raw + .split(',') + .map((t) => t.trim()) + .filter(Boolean) + } + if (Array.isArray(raw)) return raw.map(String).filter(Boolean) + return [] +} + +function validateChatScope(raw: unknown): 'all' | 'group' | 'private' { + if (raw === 'group' || raw === 'private') return raw + return 'all' +} diff --git a/packages/node-runtime/src/ai/types.ts b/packages/node-runtime/src/ai/types.ts new file mode 100644 index 00000000..cea40618 --- /dev/null +++ b/packages/node-runtime/src/ai/types.ts @@ -0,0 +1,47 @@ +/** + * AI 模块共享类型(助手、技能的解析结构) + * + * 与 Electron 端 types.ts 的同名接口结构一致(TypeScript 结构类型兼容)。 + */ + +export interface AssistantConfig { + id: string + name: string + systemPrompt: string + presetQuestions: string[] + allowedBuiltinTools?: string[] + builtinId?: string + applicableChatTypes?: ('group' | 'private')[] + supportedLocales?: string[] +} + +export interface AssistantSummary { + id: string + name: string + systemPrompt: string + presetQuestions: string[] + builtinId?: string + applicableChatTypes?: ('group' | 'private')[] + supportedLocales?: string[] +} + +export interface SkillDef { + id: string + name: string + description: string + tags: string[] + chatScope: 'all' | 'group' | 'private' + prompt: string + tools: string[] + builtinId?: string +} + +export interface SkillSummary { + id: string + name: string + description: string + tags: string[] + chatScope: 'all' | 'group' | 'private' + tools: string[] + builtinId?: string +} diff --git a/packages/node-runtime/src/index.ts b/packages/node-runtime/src/index.ts index c2763315..38e6b602 100644 --- a/packages/node-runtime/src/index.ts +++ b/packages/node-runtime/src/index.ts @@ -29,3 +29,7 @@ export { deleteDict, ensureDefaultDict, } from './nlp' + +// AI 助手/技能解析器 +export type { AssistantConfig, AssistantSummary, SkillDef, SkillSummary } from './ai' +export { parseAssistantFile, serializeAssistant, parseSkillFile, extractSkillId } from './ai' diff --git a/packages/server/src/http/index.ts b/packages/server/src/http/index.ts index 3c2fd31f..4d302c3d 100644 --- a/packages/server/src/http/index.ts +++ b/packages/server/src/http/index.ts @@ -18,6 +18,7 @@ import { registerSystemRoutes } from './routes/system' import { registerSessionRoutes } from './routes/sessions' import { registerWebRoutes } from './routes/web' import { registerNlpRoutes } from './routes/nlp' +import { registerAiRoutes } from './routes/ai' let server: FastifyInstance | null = null let dbManager: DatabaseManager | null = null @@ -96,6 +97,7 @@ export async function startHttpServer(options?: HttpServerOptions): Promise<{ registerSystemRoutes(server, dbManager) registerSessionRoutes(server, dbManager) registerNlpRoutes(server, dbManager) + registerAiRoutes(server, dbManager) registerWebRoutes(server, dbManager) // 托管 Web SPA 静态资源 @@ -139,3 +141,4 @@ export { registerSystemRoutes } from './routes/system' export { registerSessionRoutes } from './routes/sessions' export { registerWebRoutes } from './routes/web' export { registerNlpRoutes } from './routes/nlp' +export { registerAiRoutes } from './routes/ai' diff --git a/packages/server/src/http/routes/ai.ts b/packages/server/src/http/routes/ai.ts new file mode 100644 index 00000000..4aad884e --- /dev/null +++ b/packages/server/src/http/routes/ai.ts @@ -0,0 +1,233 @@ +/** + * AI Web API — /_web/ai/ routes + * + * 提供助手、技能、LLM 配置和工具目录的只读 HTTP 接口, + * 供 CLI serve Web 前端使用(对齐 Electron preload 的 window.*Api)。 + * + * - 助手/技能数据来自 ~/.chatlab/ai/{assistants,skills}/*.md + * - LLM 配置来自 ~/.chatlab/ai/llm-config.json + * - 工具目录来自 @openchatlab/core 静态数据 + */ + +import * as fs from 'fs' +import * as path from 'path' +import type { FastifyInstance } from 'fastify' +import type { DatabaseManager } from '@openchatlab/node-runtime' +import { parseAssistantFile, parseSkillFile } from '@openchatlab/node-runtime' +import { BUILTIN_TOOL_CATALOG, BUILTIN_PROVIDERS, BUILTIN_MODELS, getBuiltinModelsByProvider } from '@openchatlab/core' +import type { AssistantSummary, SkillSummary } from '@openchatlab/node-runtime' + +function getAiDir(dbManager: DatabaseManager): string { + const pathProvider = (dbManager as any)['pathProvider'] + if (!pathProvider) { + throw Object.assign(new Error('PathProvider not available'), { statusCode: 500 }) + } + return pathProvider.getAiDataDir() +} + +function scanMdFiles(dir: string): string[] { + if (!fs.existsSync(dir)) return [] + return fs + .readdirSync(dir) + .filter((f) => f.endsWith('.md')) + .map((f) => path.join(dir, f)) +} + +export function registerAiRoutes(server: FastifyInstance, dbManager: DatabaseManager): void { + // ==================== Assistants ==================== + + server.get('/_web/ai/assistants', async () => { + const dir = path.join(getAiDir(dbManager), 'assistants') + const files = scanMdFiles(dir) + const results: AssistantSummary[] = [] + for (const filePath of files) { + try { + const content = fs.readFileSync(filePath, 'utf-8') + const parsed = parseAssistantFile(content, filePath) + if (parsed) { + results.push({ + id: parsed.id, + name: parsed.name, + systemPrompt: parsed.systemPrompt, + presetQuestions: parsed.presetQuestions, + builtinId: parsed.builtinId, + applicableChatTypes: parsed.applicableChatTypes, + supportedLocales: parsed.supportedLocales, + }) + } + } catch { + // skip unparseable files + } + } + return results + }) + + server.get<{ Params: { id: string } }>('/_web/ai/assistants/:id', async (request, reply) => { + const { id } = request.params + const dir = path.join(getAiDir(dbManager), 'assistants') + const filePath = path.join(dir, `${id}.md`) + + if (!fs.existsSync(filePath)) { + return reply.code(404).send({ error: 'Not found' }) + } + + const content = fs.readFileSync(filePath, 'utf-8') + const parsed = parseAssistantFile(content, filePath) + if (!parsed) { + return reply.code(404).send({ error: 'Parse failed' }) + } + return parsed + }) + + // ==================== Skills ==================== + + server.get('/_web/ai/skills', async () => { + const dir = path.join(getAiDir(dbManager), 'skills') + const files = scanMdFiles(dir) + const results: SkillSummary[] = [] + for (const filePath of files) { + try { + const content = fs.readFileSync(filePath, 'utf-8') + const parsed = parseSkillFile(content, filePath) + if (parsed) { + results.push({ + id: parsed.id, + name: parsed.name, + description: parsed.description, + tags: parsed.tags, + chatScope: parsed.chatScope, + tools: parsed.tools, + builtinId: parsed.builtinId, + }) + } + } catch { + // skip unparseable files + } + } + return results + }) + + server.get<{ Params: { id: string } }>('/_web/ai/skills/:id', async (request, reply) => { + const { id } = request.params + const dir = path.join(getAiDir(dbManager), 'skills') + const filePath = path.join(dir, `${id}.md`) + + if (!fs.existsSync(filePath)) { + return reply.code(404).send({ error: 'Not found' }) + } + + const content = fs.readFileSync(filePath, 'utf-8') + const parsed = parseSkillFile(content, filePath) + if (!parsed) { + return reply.code(404).send({ error: 'Parse failed' }) + } + return parsed + }) + + // ==================== LLM Config ==================== + + server.get('/_web/ai/llm/has-config', async () => { + const configPath = path.join(getAiDir(dbManager), 'llm-config.json') + if (!fs.existsSync(configPath)) return false + + try { + const data = JSON.parse(fs.readFileSync(configPath, 'utf-8')) + if (!data.configs || !Array.isArray(data.configs) || data.configs.length === 0) return false + return data.defaultAssistant != null + } catch { + return false + } + }) + + server.get('/_web/ai/llm/configs', async () => { + const configPath = path.join(getAiDir(dbManager), 'llm-config.json') + if (!fs.existsSync(configPath)) return { configs: [], defaultAssistant: null, fastModel: null } + + try { + const data = JSON.parse(fs.readFileSync(configPath, 'utf-8')) + const configs = (data.configs || []).map((c: Record) => { + const { apiKey: _k, ...rest } = c + return { ...rest, apiKey: '' } + }) + return { + configs, + defaultAssistant: data.defaultAssistant ?? null, + fastModel: data.fastModel ?? null, + } + } catch { + return { configs: [], defaultAssistant: null, fastModel: null } + } + }) + + // ==================== Provider Registry & Model Catalog ==================== + + server.get('/_web/ai/llm/providers', async () => { + return BUILTIN_PROVIDERS.map((p) => { + const models = getBuiltinModelsByProvider(p.id) + return { + id: p.id, + name: p.name, + defaultBaseUrl: p.defaultBaseUrl, + models: models + .filter((m) => !m.capabilities.includes('embedding') && !m.capabilities.includes('ranking')) + .map((m) => ({ id: m.id, name: m.name, description: m.description })), + } + }) + }) + + server.get('/_web/ai/llm/provider-registry', async () => { + const customPath = path.join(getAiDir(dbManager), 'custom-providers.json') + let custom: unknown[] = [] + if (fs.existsSync(customPath)) { + try { + custom = JSON.parse(fs.readFileSync(customPath, 'utf-8')) + if (!Array.isArray(custom)) custom = [] + } catch { + custom = [] + } + } + return [...BUILTIN_PROVIDERS, ...custom] + }) + + server.get('/_web/ai/llm/model-catalog', async () => { + const customPath = path.join(getAiDir(dbManager), 'custom-models.json') + let custom: unknown[] = [] + if (fs.existsSync(customPath)) { + try { + custom = JSON.parse(fs.readFileSync(customPath, 'utf-8')) + if (!Array.isArray(custom)) custom = [] + } catch { + custom = [] + } + } + return [...BUILTIN_MODELS, ...custom] + }) + + server.get('/_web/ai/llm/default-assistant-slot', async () => { + const configPath = path.join(getAiDir(dbManager), 'llm-config.json') + if (!fs.existsSync(configPath)) return null + try { + const data = JSON.parse(fs.readFileSync(configPath, 'utf-8')) + return data.defaultAssistant ?? null + } catch { + return null + } + }) + + server.get('/_web/ai/llm/fast-model-slot', async () => { + const configPath = path.join(getAiDir(dbManager), 'llm-config.json') + if (!fs.existsSync(configPath)) return null + try { + const data = JSON.parse(fs.readFileSync(configPath, 'utf-8')) + return data.fastModel ?? null + } catch { + return null + } + }) + + // ==================== Tool Catalog ==================== + + server.get('/_web/ai/tools/catalog', async () => { + return BUILTIN_TOOL_CATALOG + }) +} diff --git a/src/services/ai/web.ts b/src/services/ai/web.ts index 53eb4ae1..a4f7e667 100644 --- a/src/services/ai/web.ts +++ b/src/services/ai/web.ts @@ -16,7 +16,7 @@ import type { } from './types' import type { TimeFilter } from '@/types/base' -const NOT_AVAILABLE = 'AI features are not available in web mode' +const NOT_AVAILABLE = 'AI 对话功能暂不支持 Web 模式,请使用桌面客户端' /** * Web 模式下的 AIAdapter 降级实现 diff --git a/src/services/registry.ts b/src/services/registry.ts index ba3ce6cc..a53d63c3 100644 --- a/src/services/registry.ts +++ b/src/services/registry.ts @@ -100,6 +100,7 @@ async function initWebServeAdapters(): Promise { await installChartPluginShims() await installNlpApiShim() + await installAiApiShims() } /** @@ -140,6 +141,73 @@ async function installNlpApiShim(): Promise { } } +const WEB_STUB = { success: false as const, error: 'Not available in web mode' } + +async function installAiApiShims(): Promise { + const { get } = await import('./utils/http') + + ;(window as any).assistantApi = { + getAll: () => get('/ai/assistants'), + getConfig: (id: string) => get(`/ai/assistants/${id}`), + getBuiltinToolCatalog: () => get('/ai/tools/catalog'), + getBuiltinCatalog: () => Promise.resolve([]), + importFromMd: () => Promise.resolve(WEB_STUB), + update: () => Promise.resolve(WEB_STUB), + reset: () => Promise.resolve(WEB_STUB), + importAssistant: () => Promise.resolve(WEB_STUB), + reimportAssistant: () => Promise.resolve(WEB_STUB), + create: () => Promise.resolve(WEB_STUB), + delete: () => Promise.resolve(WEB_STUB), + } + ;(window as any).skillApi = { + getAll: () => get('/ai/skills'), + getConfig: (id: string) => get(`/ai/skills/${id}`), + getBuiltinCatalog: () => Promise.resolve([]), + importFromMd: () => Promise.resolve(WEB_STUB), + update: () => Promise.resolve(WEB_STUB), + create: () => Promise.resolve(WEB_STUB), + delete: () => Promise.resolve(WEB_STUB), + importSkill: () => Promise.resolve(WEB_STUB), + reimportSkill: () => Promise.resolve(WEB_STUB), + } + + let _cachedLlmData: { configs: any[]; defaultAssistant: any; fastModel: any } | null = null + async function loadLlmData() { + if (!_cachedLlmData) { + _cachedLlmData = await get('/ai/llm/configs') + } + return _cachedLlmData! + } + + ;(window as any).llmApi = { + hasConfig: () => get('/ai/llm/has-config'), + getAllConfigs: async () => (await loadLlmData()).configs, + getDefaultAssistantSlot: () => get('/ai/llm/default-assistant-slot'), + getFastModelSlot: () => get('/ai/llm/fast-model-slot'), + getProviders: () => get('/ai/llm/providers'), + getProviderRegistry: () => get('/ai/llm/provider-registry'), + getModelCatalog: () => get('/ai/llm/model-catalog'), + setDefaultAssistantModel: () => Promise.resolve(WEB_STUB), + setFastModel: () => Promise.resolve(WEB_STUB), + deleteConfig: () => Promise.resolve(WEB_STUB), + addConfig: () => Promise.resolve(WEB_STUB), + updateConfig: () => Promise.resolve(WEB_STUB), + validateApiKey: () => Promise.resolve(WEB_STUB), + fetchRemoteModels: () => Promise.resolve({ success: false, error: 'Not available in web mode', models: [] }), + chatStream: () => Promise.resolve({ success: false, error: 'Not available in web mode' }), + addCustomModel: () => Promise.resolve(WEB_STUB), + updateCustomModel: () => Promise.resolve(WEB_STUB), + deleteCustomModel: () => Promise.resolve(WEB_STUB), + } + ;(window as any).agentApi = { + runStream: () => ({ + requestId: '', + promise: Promise.resolve({ success: false, error: 'Not available in web mode' }), + }), + abort: () => Promise.resolve(), + } +} + async function initWebBrowserAdapters(): Promise { // Phase 6+: BrowserSql Adapter throw new Error('[services] web-browser platform not yet supported') diff --git a/src/stores/llm.ts b/src/stores/llm.ts index 6d044bc5..e4d45109 100644 --- a/src/stores/llm.ts +++ b/src/stores/llm.ts @@ -1,7 +1,6 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { ProviderDefinition, ModelDefinition } from '@electron/preload/index' -import { IS_ELECTRON } from '@/utils/platform' /** * LLM 服务配置(展示用,不含敏感信息) @@ -73,7 +72,6 @@ export const useLLMStore = defineStore('llm', () => { } async function loadConfigs() { - if (!IS_ELECTRON) return isLoading.value = true try { const [providersData, registryData, catalogData, configsData, assistantSlot, fastSlot] = await Promise.all([