diff --git a/app/api/ai/models/route.js b/app/api/ai/models/route.js index 9921dbd..ea22339 100644 --- a/app/api/ai/models/route.js +++ b/app/api/ai/models/route.js @@ -3,7 +3,7 @@ import { NextResponse } from 'next/server'; // 通用模型列表拉取 — 支持 OpenAI 兼容格式和 Gemini 原生格式 export async function POST(request) { try { - const { apiKey, baseUrl, provider } = await request.json(); + const { apiKey, baseUrl, provider, embedOnly } = await request.json(); if (!apiKey) { return NextResponse.json( @@ -14,11 +14,11 @@ export async function POST(request) { // Gemini 原生格式 if (provider === 'gemini-native') { - return await fetchGeminiModels(apiKey, baseUrl); + return await fetchGeminiModels(apiKey, baseUrl, embedOnly); } // OpenAI 兼容格式(适用于所有其他供应商) - return await fetchOpenAIModels(apiKey, baseUrl); + return await fetchOpenAIModels(apiKey, baseUrl, embedOnly); } catch (error) { console.error('拉取模型列表错误:', error); @@ -30,7 +30,7 @@ export async function POST(request) { } // Gemini 原生格式拉取模型 -async function fetchGeminiModels(apiKey, baseUrl) { +async function fetchGeminiModels(apiKey, baseUrl, embedOnly) { const base = (baseUrl || 'https://generativelanguage.googleapis.com/v1beta').replace(/\/$/, ''); const url = `${base}/models?key=${apiKey}`; @@ -44,19 +44,25 @@ async function fetchGeminiModels(apiKey, baseUrl) { } const data = await response.json(); - const models = (data.models || []) - .filter(m => m.supportedGenerationMethods?.includes('generateContent') || m.supportedGenerationMethods?.includes('embedContent')) - .map(m => ({ - id: m.name?.replace('models/', '') || m.name, - displayName: m.displayName || m.name, - })) + let models = (data.models || []); + + if (embedOnly) { + models = models.filter(m => m.supportedGenerationMethods?.includes('embedContent')); + } else { + models = models.filter(m => m.supportedGenerationMethods?.includes('generateContent') || m.supportedGenerationMethods?.includes('embedContent')); + } + + models = models.map(m => ({ + id: m.name?.replace('models/', '') || m.name, + displayName: m.displayName || m.name, + })) .sort((a, b) => a.id.localeCompare(b.id)); return NextResponse.json({ models }); } // OpenAI 兼容格式拉取模型(/v1/models) -async function fetchOpenAIModels(apiKey, baseUrl) { +async function fetchOpenAIModels(apiKey, baseUrl, embedOnly) { const base = (baseUrl || '').replace(/\/$/, ''); if (!base) { return NextResponse.json( @@ -80,12 +86,16 @@ async function fetchOpenAIModels(apiKey, baseUrl) { } const data = await response.json(); - // OpenAI 格式返回 { data: [{id, ...}, ...] } - const models = (data.data || []) - .map(m => ({ - id: m.id, - displayName: m.id, - })) + let models = (data.data || []); + + if (embedOnly) { + models = models.filter(m => /embed/i.test(m.id)); + } + + models = models.map(m => ({ + id: m.id, + displayName: m.id, + })) .sort((a, b) => a.id.localeCompare(b.id)); return NextResponse.json({ models }); diff --git a/app/components/SettingsPanel.js b/app/components/SettingsPanel.js index d08e275..a81a4f1 100644 --- a/app/components/SettingsPanel.js +++ b/app/components/SettingsPanel.js @@ -550,7 +550,7 @@ function ApiConfigForm({ data, onChange }) { try { const embedKey = data.embedApiKey || data.apiKey; const embedBase = data.embedBaseUrl || data.baseUrl; - const res = await fetch('/api/ai/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: embedKey, baseUrl: embedBase, provider: data.embedProvider }) }); + const res = await fetch('/api/ai/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: embedKey, baseUrl: embedBase, provider: data.embedProvider, embedOnly: true }) }); const result = await res.json(); if (result.error) { setFetchedEmbedModels(null); setTestStatus({ success: false, error: t('apiConfig.embedApiPrefix') + result.error }); } else { setFetchedEmbedModels(result.models || []); } diff --git a/app/lib/context-engine.js b/app/lib/context-engine.js index cb941a9..41f75b4 100644 --- a/app/lib/context-engine.js +++ b/app/lib/context-engine.js @@ -203,14 +203,14 @@ export async function buildContext(activeChapterId, selectedText, selectedIds = } } - // --- RAG 自动检索 --- + // --- RAG 自动检索(仅当开启嵌入功能时) --- let autoRetrievedNodes = []; const queryText = (selectedText || '').trim(); - if (queryText && unselectedItemNodes.length > 0) { + const { apiConfig } = getProjectSettings(); + if (apiConfig.useCustomEmbed && queryText && unselectedItemNodes.length > 0) { try { // 获取 Query 的 Embedding // 此处的文本可以是光标前的一小段文字,为了简便使用 selectedText - const { apiConfig } = getProjectSettings(); // TODO: 这里如果是空文本,可能需要补当前章节最近的内容 let ragSourceText = queryText; if (ragSourceText.length < 50 && currentChapter) { diff --git a/app/lib/settings.js b/app/lib/settings.js index 3bbbb0e..38e347f 100644 --- a/app/lib/settings.js +++ b/app/lib/settings.js @@ -490,9 +490,11 @@ export async function addSettingsNode({ name, type, category, parentId, icon, co }; if (node.type === 'item') { - const textToEmbed = extractTextForEmbedding(node); const { apiConfig } = getProjectSettings(); - node.embedding = await getEmbedding(textToEmbed, apiConfig); + if (apiConfig.useCustomEmbed) { + const textToEmbed = extractTextForEmbedding(node); + node.embedding = await getEmbedding(textToEmbed, apiConfig); + } } nodes.push(node); @@ -514,12 +516,12 @@ export async function updateSettingsNode(id, updates) { delete updates.parentId; } - // 如果名称或内容发生改变,且是条目,重新计算 embedding + // 如果名称或内容发生改变,且是条目,且开启了嵌入功能,重新计算 embedding const nodeType = updates.type || nodes[idx].type; - if (nodeType === 'item' && (updates.name !== undefined || updates.content !== undefined)) { + const { apiConfig } = getProjectSettings(); + if (nodeType === 'item' && apiConfig.useCustomEmbed && (updates.name !== undefined || updates.content !== undefined)) { const tempNode = { ...nodes[idx], ...updates }; const textToEmbed = extractTextForEmbedding(tempNode); - const { apiConfig } = getProjectSettings(); updates.embedding = await getEmbedding(textToEmbed, apiConfig); }