mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
feat: 支持拉取记忆与嵌入模型列表
This commit is contained in:
47
index.js
47
index.js
@@ -30,7 +30,7 @@ import {
|
||||
getNode,
|
||||
} from "./graph.js";
|
||||
import { estimateTokens, formatInjection } from "./injector.js";
|
||||
import { testLLMConnection } from "./llm.js";
|
||||
import { fetchMemoryLLMModels, testLLMConnection } from "./llm.js";
|
||||
import { retrieve } from "./retriever.js";
|
||||
import {
|
||||
appendBatchJournal,
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
getVectorIndexStats,
|
||||
isBackendVectorConfig,
|
||||
isDirectVectorConfig,
|
||||
fetchAvailableEmbeddingModels,
|
||||
syncGraphVectorIndex,
|
||||
testVectorConnection,
|
||||
validateVectorConfig,
|
||||
@@ -251,8 +252,11 @@ function getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
function getEmbeddingConfig() {
|
||||
return getVectorConfigFromSettings(getSettings());
|
||||
function getEmbeddingConfig(mode = null) {
|
||||
const settings = getSettings();
|
||||
return getVectorConfigFromSettings(
|
||||
mode ? { ...settings, embeddingTransportMode: mode } : settings,
|
||||
);
|
||||
}
|
||||
|
||||
function getCurrentChatId(context = getContext()) {
|
||||
@@ -1454,6 +1458,41 @@ async function onTestMemoryLLM() {
|
||||
}
|
||||
}
|
||||
|
||||
async function onFetchMemoryLLMModels() {
|
||||
toastr.info("正在拉取记忆 LLM 模型列表...");
|
||||
const result = await fetchMemoryLLMModels();
|
||||
|
||||
if (result.success) {
|
||||
toastr.success(`已拉取 ${result.models.length} 个记忆 LLM 模型`);
|
||||
} else {
|
||||
toastr.error(`拉取失败: ${result.error}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function onFetchEmbeddingModels(mode = null) {
|
||||
const config = getEmbeddingConfig(mode);
|
||||
const targetMode = mode || config?.mode || "direct";
|
||||
const validation = validateVectorConfig(config);
|
||||
if (!validation.valid) {
|
||||
toastr.warning(validation.error);
|
||||
return { success: false, models: [], error: validation.error };
|
||||
}
|
||||
|
||||
toastr.info("正在拉取 Embedding 模型列表...");
|
||||
const result = await fetchAvailableEmbeddingModels(config);
|
||||
|
||||
if (result.success) {
|
||||
const modeLabel = targetMode === "backend" ? "后端" : "直连";
|
||||
toastr.success(`已拉取 ${result.models.length} 个${modeLabel} Embedding 模型`);
|
||||
} else {
|
||||
toastr.error(`拉取失败: ${result.error}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function onManualExtract() {
|
||||
if (isExtracting) return;
|
||||
if (!(await recoverHistoryIfNeeded("manual-extract"))) return;
|
||||
@@ -1650,6 +1689,8 @@ async function onReembedDirect() {
|
||||
evolve: onManualEvolve,
|
||||
testEmbedding: onTestEmbedding,
|
||||
testMemoryLLM: onTestMemoryLLM,
|
||||
fetchMemoryLLMModels: onFetchMemoryLLMModels,
|
||||
fetchEmbeddingModels: onFetchEmbeddingModels,
|
||||
rebuildVectorIndex: () => onRebuildVectorIndex(),
|
||||
rebuildVectorRange: (range) => onRebuildVectorIndex(range),
|
||||
reembedDirect: onReembedDirect,
|
||||
|
||||
68
llm.js
68
llm.js
@@ -27,6 +27,32 @@ function hasDedicatedLLMConfig(config = getMemoryLLMConfig()) {
|
||||
return Boolean(config.apiUrl && config.model);
|
||||
}
|
||||
|
||||
function normalizeModelList(items = []) {
|
||||
if (!Array.isArray(items)) return [];
|
||||
|
||||
const seen = new Set();
|
||||
const models = [];
|
||||
|
||||
for (const item of items) {
|
||||
let id = "";
|
||||
let label = "";
|
||||
|
||||
if (typeof item === "string") {
|
||||
id = item.trim();
|
||||
label = id;
|
||||
} else if (item && typeof item === "object") {
|
||||
id = String(item.id || item.name || item.value || item.slug || "").trim();
|
||||
label = String(item.name || item.id || item.value || item.slug || "").trim();
|
||||
}
|
||||
|
||||
if (!id || seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
models.push({ id, label: label || id });
|
||||
}
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
// 自动检测:如果 API 不支持 response_format,记住并跳过
|
||||
let _jsonModeSupported = true;
|
||||
|
||||
@@ -206,6 +232,48 @@ export async function testLLMConnection() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchMemoryLLMModels() {
|
||||
const config = getMemoryLLMConfig();
|
||||
if (!config.apiUrl) {
|
||||
return {
|
||||
success: false,
|
||||
models: [],
|
||||
error: "请先填写记忆 LLM API 地址",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/backends/chat-completions/status", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
reverse_proxy: config.apiUrl,
|
||||
proxy_password: config.apiKey || "",
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const message = payload?.error || payload?.message || response.statusText;
|
||||
return { success: false, models: [], error: message || `HTTP ${response.status}` };
|
||||
}
|
||||
|
||||
const models = normalizeModelList(payload?.data);
|
||||
if (models.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
models: [],
|
||||
error: "未拉取到可用模型,请检查接口是否支持 /models",
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, models, error: "" };
|
||||
} catch (error) {
|
||||
return { success: false, models: [], error: String(error) };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 LLM 响应文本中提取 JSON 对象
|
||||
* 处理各种常见格式:纯 JSON、markdown 代码块、混合文本等
|
||||
|
||||
27
panel.html
27
panel.html
@@ -367,6 +367,15 @@
|
||||
placeholder="gpt-4.1-mini / qwen-max / deepseek-chat"
|
||||
/>
|
||||
</div>
|
||||
<div class="bme-model-fetch-block">
|
||||
<button class="bme-config-secondary-btn" id="bme-fetch-llm-models" type="button">
|
||||
<i class="fa-solid fa-rotate"></i>
|
||||
<span>拉取模型</span>
|
||||
</button>
|
||||
<select id="bme-select-llm-model" class="bme-config-input bme-model-select" style="display:none">
|
||||
<option value="">从拉取结果中选择模型</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="bme-config-actions">
|
||||
<button class="bme-config-test-btn" id="bme-test-llm" type="button">
|
||||
<i class="fa-solid fa-plug"></i>
|
||||
@@ -417,6 +426,15 @@
|
||||
placeholder="text-embedding-3-small / nomic-embed-text / BAAI/bge-m3"
|
||||
/>
|
||||
</div>
|
||||
<div class="bme-model-fetch-block">
|
||||
<button class="bme-config-secondary-btn" id="bme-fetch-embed-backend-models" type="button">
|
||||
<i class="fa-solid fa-rotate"></i>
|
||||
<span>拉取模型</span>
|
||||
</button>
|
||||
<select id="bme-select-embed-backend-model" class="bme-config-input bme-model-select" style="display:none">
|
||||
<option value="">从拉取结果中选择模型</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-embed-backend-url">后端 API 地址</label>
|
||||
<input
|
||||
@@ -466,6 +484,15 @@
|
||||
placeholder="text-embedding-3-small"
|
||||
/>
|
||||
</div>
|
||||
<div class="bme-model-fetch-block">
|
||||
<button class="bme-config-secondary-btn" id="bme-fetch-embed-direct-models" type="button">
|
||||
<i class="fa-solid fa-rotate"></i>
|
||||
<span>拉取模型</span>
|
||||
</button>
|
||||
<select id="bme-select-embed-direct-model" class="bme-config-input bme-model-select" style="display:none">
|
||||
<option value="">从拉取结果中选择模型</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bme-config-actions">
|
||||
|
||||
108
panel.js
108
panel.js
@@ -110,6 +110,9 @@ let graphRenderer = null;
|
||||
let mobileGraphRenderer = null;
|
||||
let currentTabId = "dashboard";
|
||||
let currentConfigSectionId = "api";
|
||||
let fetchedMemoryLLMModels = [];
|
||||
let fetchedBackendEmbeddingModels = [];
|
||||
let fetchedDirectEmbeddingModels = [];
|
||||
|
||||
|
||||
// 由 index.js 注入的引用
|
||||
@@ -795,6 +798,7 @@ function _refreshConfigTab() {
|
||||
_setInputValue("bme-setting-synopsis-prompt", settings.synopsisPrompt || DEFAULT_PROMPTS.synopsis);
|
||||
_setInputValue("bme-setting-reflection-prompt", settings.reflectionPrompt || DEFAULT_PROMPTS.reflection);
|
||||
|
||||
_refreshFetchedModelSelects(settings);
|
||||
_refreshGuardedConfigStates(settings);
|
||||
_refreshStageCardStates(settings);
|
||||
_refreshPromptCardStates(settings);
|
||||
@@ -1063,6 +1067,48 @@ function _bindConfigControls() {
|
||||
document.getElementById("bme-test-embedding")?.addEventListener("click", async () => {
|
||||
await _actionHandlers.testEmbedding?.();
|
||||
});
|
||||
document.getElementById("bme-fetch-llm-models")?.addEventListener("click", async () => {
|
||||
const result = await _actionHandlers.fetchMemoryLLMModels?.();
|
||||
if (!result?.success) return;
|
||||
fetchedMemoryLLMModels = result.models || [];
|
||||
_renderFetchedModelOptions(
|
||||
"bme-select-llm-model",
|
||||
fetchedMemoryLLMModels,
|
||||
(_getSettings?.() || {}).llmModel || "",
|
||||
);
|
||||
});
|
||||
document.getElementById("bme-fetch-embed-backend-models")?.addEventListener("click", async () => {
|
||||
const result = await _actionHandlers.fetchEmbeddingModels?.("backend");
|
||||
if (!result?.success) return;
|
||||
fetchedBackendEmbeddingModels = result.models || [];
|
||||
_renderFetchedModelOptions(
|
||||
"bme-select-embed-backend-model",
|
||||
fetchedBackendEmbeddingModels,
|
||||
(_getSettings?.() || {}).embeddingBackendModel || "",
|
||||
);
|
||||
});
|
||||
document.getElementById("bme-fetch-embed-direct-models")?.addEventListener("click", async () => {
|
||||
const result = await _actionHandlers.fetchEmbeddingModels?.("direct");
|
||||
if (!result?.success) return;
|
||||
fetchedDirectEmbeddingModels = result.models || [];
|
||||
_renderFetchedModelOptions(
|
||||
"bme-select-embed-direct-model",
|
||||
fetchedDirectEmbeddingModels,
|
||||
(_getSettings?.() || {}).embeddingModel || "",
|
||||
);
|
||||
});
|
||||
|
||||
bindSelectModel("bme-select-llm-model", "bme-setting-llm-model", "llmModel");
|
||||
bindSelectModel(
|
||||
"bme-select-embed-backend-model",
|
||||
"bme-setting-embed-backend-model",
|
||||
"embeddingBackendModel",
|
||||
);
|
||||
bindSelectModel(
|
||||
"bme-select-embed-direct-model",
|
||||
"bme-setting-embed-model",
|
||||
"embeddingModel",
|
||||
);
|
||||
|
||||
panelEl.dataset.bmeConfigBound = "true";
|
||||
}
|
||||
@@ -1122,6 +1168,17 @@ function bindPromptText(id, settingKey, promptKey) {
|
||||
element.dataset.bmeBound = "true";
|
||||
}
|
||||
|
||||
function bindSelectModel(selectId, inputId, settingKey) {
|
||||
const element = document.getElementById(selectId);
|
||||
if (!element || element.dataset.bmeBound === "true") return;
|
||||
element.addEventListener("change", () => {
|
||||
if (!element.value) return;
|
||||
_setInputValue(inputId, element.value);
|
||||
_patchSettings({ [settingKey]: element.value });
|
||||
});
|
||||
element.dataset.bmeBound = "true";
|
||||
}
|
||||
|
||||
// ==================== 工具函数 ====================
|
||||
|
||||
function _setText(id, text) {
|
||||
@@ -1186,6 +1243,57 @@ function _refreshStageCardStates(settings = _getSettings?.() || {}) {
|
||||
});
|
||||
}
|
||||
|
||||
function _refreshFetchedModelSelects(settings = _getSettings?.() || {}) {
|
||||
_renderFetchedModelOptions(
|
||||
"bme-select-llm-model",
|
||||
fetchedMemoryLLMModels,
|
||||
settings.llmModel || "",
|
||||
);
|
||||
_renderFetchedModelOptions(
|
||||
"bme-select-embed-backend-model",
|
||||
fetchedBackendEmbeddingModels,
|
||||
settings.embeddingBackendModel || "",
|
||||
);
|
||||
_renderFetchedModelOptions(
|
||||
"bme-select-embed-direct-model",
|
||||
fetchedDirectEmbeddingModels,
|
||||
settings.embeddingModel || "",
|
||||
);
|
||||
}
|
||||
|
||||
function _renderFetchedModelOptions(selectId, models, currentValue = "") {
|
||||
const select = document.getElementById(selectId);
|
||||
if (!select) return;
|
||||
|
||||
const normalized = Array.isArray(models) ? models : [];
|
||||
select.innerHTML = "";
|
||||
|
||||
const placeholder = document.createElement("option");
|
||||
placeholder.value = "";
|
||||
placeholder.textContent = normalized.length
|
||||
? "从拉取结果中选择模型"
|
||||
: "暂无已拉取模型";
|
||||
select.appendChild(placeholder);
|
||||
|
||||
normalized.forEach((model) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = String(model?.id || "");
|
||||
option.textContent = String(model?.label || model?.id || "");
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
if (
|
||||
currentValue &&
|
||||
normalized.some((model) => String(model?.id || "") === String(currentValue))
|
||||
) {
|
||||
select.value = String(currentValue);
|
||||
} else {
|
||||
select.value = "";
|
||||
}
|
||||
|
||||
select.style.display = normalized.length > 0 ? "" : "none";
|
||||
}
|
||||
|
||||
function _refreshPromptCardStates(settings = _getSettings?.() || {}) {
|
||||
if (!panelEl) return;
|
||||
panelEl.querySelectorAll(".bme-prompt-card").forEach((card) => {
|
||||
|
||||
44
style.css
44
style.css
@@ -930,6 +930,46 @@
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.bme-model-fetch-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: -2px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.bme-config-secondary-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: fit-content;
|
||||
min-height: 38px;
|
||||
padding: 0 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
color: var(--bme-on-surface);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
transition: border-color 0.15s, background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.bme-config-secondary-btn:hover {
|
||||
border-color: var(--bme-primary);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: var(--bme-primary);
|
||||
}
|
||||
|
||||
.bme-config-secondary-btn i {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.bme-model-select {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.bme-config-test-btn {
|
||||
display: inline-flex !important;
|
||||
flex-direction: row !important;
|
||||
@@ -1408,6 +1448,10 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bme-config-secondary-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bme-config-test-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
205
vector-index.js
205
vector-index.js
@@ -27,6 +27,19 @@ const BACKEND_SOURCES_REQUIRING_API_URL = new Set([
|
||||
"vllm",
|
||||
]);
|
||||
|
||||
const MODEL_LIST_ENDPOINTS = {
|
||||
openrouter: "/api/openrouter/models/embedding",
|
||||
chutes: "/api/openai/chutes/models/embedding",
|
||||
nanogpt: "/api/openai/nanogpt/models/embedding",
|
||||
electronhub: "/api/openai/electronhub/models",
|
||||
};
|
||||
|
||||
const BACKEND_STATUS_MODEL_SOURCES = {
|
||||
openai: "openai",
|
||||
cohere: "cohere",
|
||||
mistral: "mistralai",
|
||||
};
|
||||
|
||||
export const BACKEND_DEFAULT_MODELS = {
|
||||
openai: "text-embedding-3-small",
|
||||
openrouter: "openai/text-embedding-3-small",
|
||||
@@ -639,3 +652,195 @@ export function getVectorIndexStats(graph) {
|
||||
}
|
||||
return state.lastStats || { total: 0, indexed: 0, stale: 0, pending: 0 };
|
||||
}
|
||||
|
||||
function normalizeModelOptions(items = [], { embeddingOnly = false } = {}) {
|
||||
if (!Array.isArray(items)) return [];
|
||||
|
||||
const candidates = [];
|
||||
for (const item of items) {
|
||||
if (typeof item === "string") {
|
||||
const id = item.trim();
|
||||
if (id) candidates.push({ id, label: id, raw: item });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!item || typeof item !== "object") continue;
|
||||
const id = String(item.id || item.name || item.label || item.slug || item.value || "").trim();
|
||||
const label = String(item.label || item.name || item.id || item.slug || item.value || "").trim();
|
||||
if (!id) continue;
|
||||
|
||||
if (
|
||||
embeddingOnly &&
|
||||
Array.isArray(item.endpoints) &&
|
||||
!item.endpoints.includes("/v1/embeddings")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
candidates.push({ id, label: label || id, raw: item });
|
||||
}
|
||||
|
||||
const embeddingRegex =
|
||||
/(embed|embedding|bge|e5|gte|nomic|voyage|mxbai|jina|minilm)/i;
|
||||
const embeddingTagged = candidates.filter((item) => embeddingRegex.test(item.id) || embeddingRegex.test(item.label));
|
||||
const source = embeddingTagged.length > 0 ? embeddingTagged : candidates;
|
||||
|
||||
const seen = new Set();
|
||||
return source
|
||||
.filter((item) => {
|
||||
if (seen.has(item.id)) return false;
|
||||
seen.add(item.id);
|
||||
return true;
|
||||
})
|
||||
.map(({ id, label }) => ({ id, label }));
|
||||
}
|
||||
|
||||
async function fetchJsonEndpoint(url, { method = "POST" } = {}) {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: getRequestHeaders({ omitContentType: true }),
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => []);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
(typeof payload === "object" && payload?.error) ||
|
||||
response.statusText ||
|
||||
`HTTP ${response.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function fetchBackendStatusModelList(source) {
|
||||
const chatCompletionSource = BACKEND_STATUS_MODEL_SOURCES[source];
|
||||
if (!chatCompletionSource) {
|
||||
throw new Error("当前后端向量源暂不支持自动拉取模型,请手动填写");
|
||||
}
|
||||
|
||||
const response = await fetch("/api/backends/chat-completions/status", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
chat_completion_source: chatCompletionSource,
|
||||
}),
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok || payload?.error) {
|
||||
throw new Error(
|
||||
payload?.message || payload?.error || response.statusText || `HTTP ${response.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
return normalizeModelOptions(payload?.data || payload, { embeddingOnly: false });
|
||||
}
|
||||
|
||||
async function fetchOpenAICompatibleModelList(apiUrl, apiKey = "") {
|
||||
const normalizedUrl = normalizeOpenAICompatibleBaseUrl(apiUrl);
|
||||
if (!normalizedUrl) {
|
||||
throw new Error("请先填写 API 地址");
|
||||
}
|
||||
|
||||
const response = await fetch(`${normalizedUrl}/models`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(payload?.error?.message || payload?.message || response.statusText);
|
||||
}
|
||||
|
||||
return normalizeModelOptions(payload?.data || payload, { embeddingOnly: false });
|
||||
}
|
||||
|
||||
async function fetchOllamaModelList(apiUrl) {
|
||||
const normalizedUrl = normalizeOpenAICompatibleBaseUrl(apiUrl).replace(/\/v1$/i, "");
|
||||
if (!normalizedUrl) {
|
||||
throw new Error("请先填写 Ollama API 地址");
|
||||
}
|
||||
|
||||
const response = await fetch(`${normalizedUrl}/api/tags`, { method: "GET" });
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(payload?.error || payload?.message || response.statusText);
|
||||
}
|
||||
|
||||
return normalizeModelOptions(
|
||||
Array.isArray(payload?.models)
|
||||
? payload.models.map((item) => ({
|
||||
id: item?.model || item?.name,
|
||||
name: item?.model || item?.name,
|
||||
}))
|
||||
: [],
|
||||
{ embeddingOnly: false },
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchAvailableEmbeddingModels(config) {
|
||||
const validation = validateVectorConfig(config);
|
||||
if (!validation.valid) {
|
||||
return { success: false, models: [], error: validation.error };
|
||||
}
|
||||
|
||||
try {
|
||||
if (isDirectVectorConfig(config)) {
|
||||
const models = normalizeModelOptions(
|
||||
await fetchOpenAICompatibleModelList(config.apiUrl, config.apiKey),
|
||||
);
|
||||
if (models.length === 0) {
|
||||
return { success: false, models: [], error: "未拉取到可用 Embedding 模型" };
|
||||
}
|
||||
return { success: true, models, error: "" };
|
||||
}
|
||||
|
||||
if (config.source === "ollama") {
|
||||
const models = await fetchOllamaModelList(config.apiUrl);
|
||||
if (models.length === 0) {
|
||||
return { success: false, models: [], error: "未拉取到可用 Ollama 模型" };
|
||||
}
|
||||
return { success: true, models, error: "" };
|
||||
}
|
||||
|
||||
if (MODEL_LIST_ENDPOINTS[config.source]) {
|
||||
const payload = await fetchJsonEndpoint(MODEL_LIST_ENDPOINTS[config.source]);
|
||||
const models = normalizeModelOptions(payload, {
|
||||
embeddingOnly: config.source === "electronhub",
|
||||
});
|
||||
if (models.length === 0) {
|
||||
return { success: false, models: [], error: "未拉取到可用 Embedding 模型" };
|
||||
}
|
||||
return { success: true, models, error: "" };
|
||||
}
|
||||
|
||||
if (BACKEND_STATUS_MODEL_SOURCES[config.source]) {
|
||||
const models = await fetchBackendStatusModelList(config.source);
|
||||
if (models.length === 0) {
|
||||
return { success: false, models: [], error: "未拉取到可用 Embedding 模型" };
|
||||
}
|
||||
return { success: true, models, error: "" };
|
||||
}
|
||||
|
||||
if (config.apiUrl) {
|
||||
const models = normalizeModelOptions(
|
||||
await fetchOpenAICompatibleModelList(config.apiUrl),
|
||||
);
|
||||
if (models.length === 0) {
|
||||
return { success: false, models: [], error: "未拉取到可用 Embedding 模型" };
|
||||
}
|
||||
return { success: true, models, error: "" };
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
models: [],
|
||||
error: "当前后端向量源暂不支持自动拉取模型,请手动填写",
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, models: [], error: String(error) };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user