diff --git a/modules/story-summary/story-summary-ui.js b/modules/story-summary/story-summary-ui.js
index 858f493..cc74289 100644
--- a/modules/story-summary/story-summary-ui.js
+++ b/modules/story-summary/story-summary-ui.js
@@ -916,16 +916,6 @@ All checks passed. Beginning incremental extraction...
$('vector-config-area').classList.toggle('hidden', !e.target.checked);
};
- $('btn-test-vector-api').onclick = () => {
- $('btn-test-vector-api').disabled = true;
- setStatusText($('embedding-api-connect-status'), '测试中...', 'loading');
- saveConfig(); // 先保存新 Key 到 localStorage
- postMsg('VECTOR_TEST_ONLINE', {
- provider: getVectorConfig().embeddingApi.provider,
- config: getVectorConfig().embeddingApi
- });
- };
-
['l0', 'embedding', 'rerank'].forEach(prefix => {
$(`${prefix}-api-provider`).onchange = e => {
saveCurrentVectorApiProfile(prefix);
@@ -941,6 +931,18 @@ All checks passed. Beginning incremental extraction...
};
$(`${prefix}-btn-connect`).onclick = () => fetchVectorModels(prefix);
+ $(`${prefix}-btn-test`).onclick = () => {
+ const btn = $(`${prefix}-btn-test`);
+ if (btn) btn.disabled = true;
+ setStatusText($(`${prefix}-api-connect-status`), '测试中...', 'loading');
+ saveConfig();
+ const cfg = getVectorConfig();
+ postMsg('VECTOR_TEST_ONLINE', {
+ target: prefix,
+ provider: cfg[`${prefix}Api`].provider,
+ config: cfg[`${prefix}Api`],
+ });
+ };
});
$('btn-add-filter-rule').onclick = addFilterRule;
@@ -987,11 +989,12 @@ All checks passed. Beginning incremental extraction...
postMsg('REQUEST_ANCHOR_STATS');
}
- function updateVectorOnlineStatus(status, message) {
- const btn = $('btn-test-vector-api');
+ function updateVectorOnlineStatus(target, status, message) {
+ const prefix = target || 'embedding';
+ const btn = $(`${prefix}-btn-test`);
if (btn) btn.disabled = false;
setStatusText(
- $('embedding-api-connect-status'),
+ $(`${prefix}-api-connect-status`),
message || '',
status === 'error' ? 'error' : status === 'success' ? 'success' : 'loading'
);
@@ -2099,7 +2102,7 @@ All checks passed. Beginning incremental extraction...
break;
case 'VECTOR_ONLINE_STATUS':
- updateVectorOnlineStatus(d.status, d.message);
+ updateVectorOnlineStatus(d.target, d.status, d.message);
break;
case 'VECTOR_STATS':
diff --git a/modules/story-summary/story-summary.html b/modules/story-summary/story-summary.html
index 6281ece..a111f1f 100644
--- a/modules/story-summary/story-summary.html
+++ b/modules/story-summary/story-summary.html
@@ -472,6 +472,7 @@
+
@@ -520,7 +521,7 @@
-
+
@@ -569,6 +570,7 @@
+
diff --git a/modules/story-summary/story-summary.js b/modules/story-summary/story-summary.js
index 245cdd6..f69140e 100644
--- a/modules/story-summary/story-summary.js
+++ b/modules/story-summary/story-summary.js
@@ -43,6 +43,8 @@ import { runSummaryGeneration } from "./generate/generator.js";
// vector service
import { embed, getEngineFingerprint, testOnlineService } from "./vector/utils/embedder.js";
+import { testL0Service } from "./vector/llm/llm-service.js";
+import { testRerankService } from "./vector/llm/reranker.js";
// tokenizer
import { preload as preloadTokenizer, injectEntities, isReady as isTokenizerReady } from "./vector/utils/tokenizer.js";
@@ -421,17 +423,23 @@ function handleAnchorCancel() {
postToFrame({ type: "ANCHOR_GEN_PROGRESS", current: -1, total: 0 });
}
-async function handleTestOnlineService(provider, config) {
+async function handleTestOnlineService(provider, config, target = "embedding") {
try {
- postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "downloading", message: "连接中..." });
- const result = await testOnlineService(provider, config);
+ postToFrame({ type: "VECTOR_ONLINE_STATUS", target, status: "downloading", message: "连接中..." });
+ let result;
+ if (target === "l0") result = await testL0Service(config);
+ else if (target === "rerank") result = await testRerankService(config);
+ else result = await testOnlineService(provider, config);
postToFrame({
type: "VECTOR_ONLINE_STATUS",
+ target,
status: "success",
- message: `连接成功 (${result.dims}维)`,
+ message: target === "embedding"
+ ? `连接成功 (${result.dims}维)`
+ : (result.message || "连接成功"),
});
} catch (e) {
- postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "error", message: e.message });
+ postToFrame({ type: "VECTOR_ONLINE_STATUS", target, status: "error", message: e.message });
}
}
@@ -1631,7 +1639,7 @@ async function handleFrameMessage(event) {
break;
case "VECTOR_TEST_ONLINE":
- handleTestOnlineService(data.provider, data.config);
+ handleTestOnlineService(data.provider, data.config, data.target || "embedding");
break;
case "VECTOR_GENERATE":
diff --git a/modules/story-summary/vector/llm/llm-service.js b/modules/story-summary/vector/llm/llm-service.js
index 3608fbd..d906976 100644
--- a/modules/story-summary/vector/llm/llm-service.js
+++ b/modules/story-summary/vector/llm/llm-service.js
@@ -39,6 +39,17 @@ function getL0ApiConfig() {
};
}
+function normalizeL0ApiConfig(apiConfig = null) {
+ const fallback = getL0ApiConfig();
+ const next = apiConfig || {};
+ return {
+ provider: String(next.provider || fallback.provider || 'siliconflow').trim(),
+ url: String(next.url || fallback.url || DEFAULT_L0_API_URL).trim(),
+ key: String(next.key || fallback.key || '').trim(),
+ model: String(next.model || fallback.model || DEFAULT_L0_MODEL).trim(),
+ };
+}
+
function getNextKey(rawKey) {
const keys = String(rawKey || '')
.split(/[,;|\n]+/)
@@ -60,12 +71,13 @@ export async function callLLM(messages, options = {}) {
temperature = 0.2,
max_tokens = 500,
timeout = 40000,
+ apiConfig = null,
} = options;
const mod = getStreamingModule();
if (!mod) throw new Error('Streaming module not ready');
- const apiCfg = getL0ApiConfig();
+ const apiCfg = normalizeL0ApiConfig(apiConfig);
const apiKey = getNextKey(apiCfg.key);
if (!apiKey) {
throw new Error('L0 requires siliconflow API key');
@@ -111,6 +123,24 @@ export async function callLLM(messages, options = {}) {
}
}
+export async function testL0Service(apiConfig = {}) {
+ if (!apiConfig?.key) {
+ throw new Error('请配置 L0 API Key');
+ }
+ const result = await callLLM([
+ { role: 'system', content: '你是一个测试助手。请只输出 OK。' },
+ { role: 'user', content: '只输出 OK' },
+ ], {
+ apiConfig,
+ temperature: 0,
+ max_tokens: 16,
+ timeout: 15000,
+ });
+ const text = String(result || '').trim();
+ if (!text) throw new Error('返回为空');
+ return { success: true, message: `连接成功:${text.slice(0, 60)}` };
+}
+
export function cancelAllL0Requests() {
const mod = getStreamingModule();
if (!mod?.cancel) return;
diff --git a/modules/story-summary/vector/llm/reranker.js b/modules/story-summary/vector/llm/reranker.js
index 4fbd37f..5266d65 100644
--- a/modules/story-summary/vector/llm/reranker.js
+++ b/modules/story-summary/vector/llm/reranker.js
@@ -273,19 +273,51 @@ export async function rerankChunks(query, chunks, options = {}) {
/**
* 测试 Rerank 服务连接
*/
-export async function testRerankService() {
- const key = getApiKey();
- if (!key) {
- throw new Error('请配置硅基 API Key');
+export async function testRerankService(apiConfig = {}) {
+ const next = {
+ provider: String(apiConfig.provider || 'siliconflow').trim(),
+ url: String(apiConfig.url || DEFAULT_RERANK_URL).trim(),
+ key: String(apiConfig.key || '').trim(),
+ model: String(apiConfig.model || RERANK_MODEL).trim(),
+ };
+ if (!next.key) {
+ throw new Error('请配置 Rerank API Key');
}
+ const key = getNextRerankKey(next.key);
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 15000);
try {
- const { results } = await rerank('测试查询', ['测试文档1', '测试文档2'], { topN: 2 });
- return {
- success: true,
- message: `连接成功,返回 ${results.length} 个结果`,
+ const baseUrl = String(next.url || DEFAULT_RERANK_URL).replace(/\/+$/, '');
+ const response = await fetch(`${baseUrl}/rerank`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${key}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ model: next.model,
+ query: '测试查询',
+ documents: ['测试文档1', '测试文档2'],
+ top_n: 2,
+ return_documents: false,
+ }),
+ signal: controller.signal,
+ });
+ clearTimeout(timeoutId);
+ if (!response.ok) {
+ const errorText = await response.text().catch(() => '');
+ throw new Error(`Rerank API ${response.status}: ${errorText.slice(0, 200)}`);
+ }
+ const data = await response.json();
+ const results = Array.isArray(data.results) ? data.results : [];
+ return {
+ success: true,
+ message: `连接成功:返回 ${results.length} 个结果`,
};
} catch (e) {
throw new Error(`连接失败: ${e.message}`);
+ } finally {
+ clearTimeout(timeoutId);
}
}