feat: make RAG conditional on useCustomEmbed toggle; filter embed model list to only show embedding models
This commit is contained in:
1 parent
ff8cac3d9c
commit
43096fc5af
4 files changed
+38
-26
No files matched your search
+27
-17
@@ -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 });
|
||||
|
||||
@@ -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 || []); }
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+7
-5
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user