feat: select deep search llm in admin
This commit is contained in:
@@ -1,12 +1,23 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getMindSearchConfig, testMindSearchService, updateMindSearchConfig } from '../../api/client';
|
||||
import type { MindSearchConfig, MindSearchRoutes, MindSearchService } from '../../types';
|
||||
import {
|
||||
getMindSearchConfig,
|
||||
listLlmProviderKeys,
|
||||
testLlmProviderKey,
|
||||
testMindSearchService,
|
||||
updateMindSearchConfig,
|
||||
} from '../../api/client';
|
||||
import type {
|
||||
LlmProviderKeyRow,
|
||||
MindSearchConfig,
|
||||
MindSearchRoutes,
|
||||
MindSearchService,
|
||||
} from '../../types';
|
||||
|
||||
const builtInServices: MindSearchService[] = [
|
||||
{ id: 'searxng', name: 'SearXNG', kind: 'provider', adapter: 'searxng', capabilities: ['search.web', 'search.news'], endpoint: '', healthPath: '/config', enabled: false, timeoutMs: 8000, priority: 100 },
|
||||
{ id: 'github', name: 'GitHub Code', kind: 'provider', adapter: 'github', capabilities: ['search.code'], endpoint: 'https://api.github.com/search/code', healthPath: '', enabled: false, timeoutMs: 8000, priority: 90 },
|
||||
{ id: 'reader', name: 'Safe Reader', kind: 'reader', adapter: 'reader', capabilities: ['search.read'], endpoint: '', healthPath: '', enabled: false, timeoutMs: 8000, priority: 80 },
|
||||
{ id: 'deep-search', name: 'TKMind Deep Search', kind: 'orchestrator', adapter: 'research-http', capabilities: ['search.web', 'research.plan', 'research.execute'], endpoint: 'http://127.0.0.1:20100', healthPath: '/health', enabled: false, timeoutMs: 120000, priority: 100 },
|
||||
{ id: 'deep-search', name: 'TKMind Deep Search', kind: 'orchestrator', adapter: 'research-http', capabilities: ['search.web', 'research.plan', 'research.execute'], endpoint: 'http://127.0.0.1:20100', healthPath: '/health', enabled: false, timeoutMs: 120000, priority: 100, llmProviderKeyId: '', llmModel: '' },
|
||||
];
|
||||
|
||||
const initial: MindSearchConfig = {
|
||||
@@ -38,13 +49,18 @@ function mergeConfig(config: MindSearchConfig): MindSearchConfig {
|
||||
|
||||
export function MindSearchPage() {
|
||||
const [config, setConfig] = useState(initial);
|
||||
const [llmKeys, setLlmKeys] = useState<LlmProviderKeyRow[]>([]);
|
||||
const [meta, setMeta] = useState<{ source?: string; updatedAt?: number | null; updatedBy?: string | null }>({});
|
||||
const [message, setMessage] = useState('加载配置中…');
|
||||
|
||||
useEffect(() => {
|
||||
getMindSearchConfig()
|
||||
.then((result) => {
|
||||
Promise.all([
|
||||
getMindSearchConfig(),
|
||||
listLlmProviderKeys().catch(() => [] as LlmProviderKeyRow[]),
|
||||
])
|
||||
.then(([result, keys]) => {
|
||||
setConfig(mergeConfig(result.config));
|
||||
setLlmKeys(keys.filter((key) => key.status === 'active'));
|
||||
setMeta(result);
|
||||
setMessage('');
|
||||
})
|
||||
@@ -88,6 +104,8 @@ export function MindSearchPage() {
|
||||
enabled: false,
|
||||
timeoutMs: 30000,
|
||||
priority: 100,
|
||||
llmProviderKeyId: '',
|
||||
llmModel: '',
|
||||
}],
|
||||
}));
|
||||
};
|
||||
@@ -108,6 +126,21 @@ export function MindSearchPage() {
|
||||
setMessage(error instanceof Error ? error.message : '服务测试失败');
|
||||
}
|
||||
};
|
||||
const runLlmTest = async (service: MindSearchService) => {
|
||||
if (!service.llmProviderKeyId || !service.llmModel) {
|
||||
setMessage('请先选择 Deep Search 的 LLM 配置和模型');
|
||||
return;
|
||||
}
|
||||
setMessage(`测试 ${service.llmModel} 中…`);
|
||||
try {
|
||||
const result = await testLlmProviderKey(service.llmProviderKeyId, service.llmModel);
|
||||
setMessage(result.ok
|
||||
? `LLM 通过:${result.model ?? service.llmModel}${result.latencyMs == null ? '' : `;${result.latencyMs}ms`}`
|
||||
: `LLM 失败:${result.message ?? '模型连接失败'}`);
|
||||
} catch (error) {
|
||||
setMessage(error instanceof Error ? error.message : 'LLM 测试失败');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="admin-page">
|
||||
@@ -145,7 +178,9 @@ export function MindSearchPage() {
|
||||
<h2>服务注册表</h2>
|
||||
<p>服务独立部署;后台只管理连接、路由和健康检查,不直接操作 Docker 或生产进程。测试使用已保存配置。</p>
|
||||
<div className="search-service-grid">
|
||||
{config.services.map((service) => (
|
||||
{config.services.map((service) => {
|
||||
const selectedLlmKey = llmKeys.find((key) => key.id === service.llmProviderKeyId);
|
||||
return (
|
||||
<fieldset key={service.id} className="search-service-card">
|
||||
<legend>{service.name}({service.id})</legend>
|
||||
<label className="search-service-toggle"><input type="checkbox" checked={service.enabled} onChange={(event) => updateService(service.id, { enabled: event.target.checked })} /><span>启用服务</span></label>
|
||||
@@ -173,13 +208,52 @@ export function MindSearchPage() {
|
||||
<label className="form-span-all"><span>Capabilities</span><input value={service.capabilities.join(', ')} onChange={(event) => updateService(service.id, { capabilities: event.target.value.split(',').map((item) => item.trim()).filter(Boolean) })} /></label>
|
||||
<label><span>超时(毫秒)</span><input type="number" min={1000} max={120000} value={service.timeoutMs} onChange={(event) => updateService(service.id, { timeoutMs: Number(event.target.value) })} /></label>
|
||||
<label><span>优先级</span><input type="number" min={1} max={1000} value={service.priority} onChange={(event) => updateService(service.id, { priority: Number(event.target.value) })} /></label>
|
||||
{service.adapter === 'research-http' && <>
|
||||
<label className="form-span-all">
|
||||
<span>规划与报告 LLM(可选)</span>
|
||||
<select
|
||||
value={service.llmProviderKeyId ?? ''}
|
||||
onChange={(event) => {
|
||||
const key = llmKeys.find((item) => item.id === event.target.value);
|
||||
updateService(service.id, {
|
||||
llmProviderKeyId: event.target.value,
|
||||
llmModel: key?.defaultModel ?? '',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<option value="">不使用 LLM(确定性降级)</option>
|
||||
{service.llmProviderKeyId && !selectedLlmKey
|
||||
&& <option value={service.llmProviderKeyId}>原 LLM 配置已停用或删除</option>}
|
||||
{llmKeys.map((key) => (
|
||||
<option key={key.id} value={key.id}>{key.name} · {key.providerLabel}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="form-span-all">
|
||||
<span>模型</span>
|
||||
<select
|
||||
value={service.llmModel ?? ''}
|
||||
disabled={!selectedLlmKey}
|
||||
onChange={(event) => updateService(service.id, { llmModel: event.target.value })}
|
||||
>
|
||||
{!selectedLlmKey && <option value="">请先选择 LLM 配置</option>}
|
||||
{selectedLlmKey && service.llmModel && !selectedLlmKey.models.includes(service.llmModel)
|
||||
&& <option value={service.llmModel}>原模型已不可用:{service.llmModel}</option>}
|
||||
{selectedLlmKey?.models.map((model) => <option key={model} value={model}>{model}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<p className="search-llm-hint form-span-all">密钥复用“统一模型中心”,不会保存到 MindSearch 配置或下发给 Deep Search 服务。</p>
|
||||
</>}
|
||||
</div>
|
||||
<div className="search-service-actions">
|
||||
<button className="send-btn" type="button" onClick={() => runServiceTest(service.id)}>测试已保存配置</button>
|
||||
{!['searxng', 'github', 'reader'].includes(service.id) && <button type="button" onClick={() => removeService(service.id)}>移除</button>}
|
||||
{service.adapter === 'research-http' && service.llmProviderKeyId && service.llmModel
|
||||
&& <button type="button" onClick={() => runLlmTest(service)}>测试 LLM</button>}
|
||||
{!['searxng', 'github', 'reader', 'deep-search'].includes(service.id) && <button type="button" onClick={() => removeService(service.id)}>移除</button>}
|
||||
</div>
|
||||
</fieldset>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<button className="send-btn" type="button" onClick={addService}>添加 Research Service</button>
|
||||
|
||||
|
||||
@@ -615,6 +615,13 @@ body,
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.search-llm-hint {
|
||||
margin: -2px 0 0;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.form-span-all {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
@@ -640,6 +640,8 @@ export type MindSearchService = {
|
||||
enabled: boolean;
|
||||
timeoutMs: number;
|
||||
priority: number;
|
||||
llmProviderKeyId?: string;
|
||||
llmModel?: string;
|
||||
};
|
||||
|
||||
export type MindSearchRoutes = {
|
||||
|
||||
Reference in New Issue
Block a user