feat(web-ai): support qwen web search agent
This commit is contained in:
@@ -5,8 +5,10 @@ type ApiResult<T> = { code: number; msg: string; data: T };
|
||||
export interface WebSearchProvider {
|
||||
id: number;
|
||||
name: string;
|
||||
providerCode: 'tavily' | string;
|
||||
providerCode: 'tavily' | 'qwen';
|
||||
endpoint: string;
|
||||
agentId?: string;
|
||||
agentVersion?: string;
|
||||
configured: boolean;
|
||||
enabled: boolean;
|
||||
lastTestOk: boolean;
|
||||
@@ -16,9 +18,11 @@ export interface WebSearchProvider {
|
||||
export interface WebSearchProviderForm {
|
||||
id?: number;
|
||||
name: string;
|
||||
providerCode: 'tavily';
|
||||
providerCode: 'tavily' | 'qwen';
|
||||
endpoint: string;
|
||||
apiKey: string;
|
||||
agentId: string;
|
||||
agentVersion: 'beta' | 'release';
|
||||
}
|
||||
|
||||
export const listWebSearchProviders = (): Promise<ApiResult<WebSearchProvider[]>> => request({
|
||||
@@ -43,5 +47,6 @@ export const toggleWebSearchProvider = (id: number, enabled: boolean): Promise<A
|
||||
export const testWebSearchProvider = (id: number): Promise<ApiResult<{ ok: boolean; sourceCount: number; message: string }>> => request({
|
||||
url: `/api/aihr/web-search/providers/${id}/test`,
|
||||
method: 'post',
|
||||
headers: { repeatSubmit: false }
|
||||
headers: { repeatSubmit: false },
|
||||
timeout: 45000
|
||||
});
|
||||
|
||||
@@ -21,12 +21,13 @@
|
||||
<div class="provider-head">
|
||||
<div>
|
||||
<h2>{{ item.name }}</h2>
|
||||
<p>{{ item.providerCode }}</p>
|
||||
<p>{{ providerLabel(item.providerCode) }}</p>
|
||||
</div>
|
||||
<el-tag :type="item.enabled ? 'success' : 'info'">{{ item.enabled ? '已启用' : '未启用' }}</el-tag>
|
||||
</div>
|
||||
<dl>
|
||||
<div><dt>接口地址</dt><dd>{{ item.endpoint }}</dd></div>
|
||||
<div v-if="item.providerCode === 'qwen'"><dt>Agent</dt><dd>{{ item.agentId }}({{ item.agentVersion }})</dd></div>
|
||||
<div><dt>访问密钥</dt><dd>{{ item.configured ? '已安全保存' : '未配置' }}</dd></div>
|
||||
<div>
|
||||
<dt>连接测试</dt>
|
||||
@@ -52,11 +53,25 @@
|
||||
<el-empty v-if="!loading && !providers.length" description="尚未配置全网检索提供方" />
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="editorVisible" :title="form.id ? '编辑提供方' : '新增提供方'" width="560px">
|
||||
<el-dialog v-model="editorVisible" class="web-search-editor" :title="form.id ? '编辑提供方' : '新增提供方'" width="560px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="名称"><el-input v-model="form.name" maxlength="100" placeholder="例如:Tavily 生产检索" /></el-form-item>
|
||||
<el-form-item label="提供方"><el-input model-value="Tavily" disabled /></el-form-item>
|
||||
<el-form-item label="HTTPS 接口地址"><el-input v-model="form.endpoint" placeholder="https://api.tavily.com/search" /></el-form-item>
|
||||
<el-form-item label="提供方">
|
||||
<el-select v-model="form.providerCode" style="width:100%" @change="changeProvider">
|
||||
<el-option label="千问联网检索" value="qwen" />
|
||||
<el-option label="Tavily" value="tavily" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="HTTPS 接口地址"><el-input v-model="form.endpoint" /></el-form-item>
|
||||
<template v-if="form.providerCode === 'qwen'">
|
||||
<el-form-item label="Agent ID"><el-input v-model="form.agentId" placeholder="aid-..." /></el-form-item>
|
||||
<el-form-item label="Agent Version">
|
||||
<el-select v-model="form.agentVersion" style="width:100%">
|
||||
<el-option label="release(已发布)" value="release" />
|
||||
<el-option label="beta(测试版)" value="beta" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item :label="form.id ? '访问密钥(留空表示不修改)' : '访问密钥'">
|
||||
<el-input v-model="form.apiKey" type="password" show-password autocomplete="new-password" />
|
||||
</el-form-item>
|
||||
@@ -87,11 +102,19 @@ const saving = ref(false);
|
||||
const testingId = ref(0);
|
||||
const togglingId = ref(0);
|
||||
const editorVisible = ref(false);
|
||||
const providerDefaults = {
|
||||
qwen: {
|
||||
name: '千问联网检索',
|
||||
endpoint: 'https://dashscope.aliyuncs.com/api/v2/apps/web-search-agent/chat/completions'
|
||||
},
|
||||
tavily: { name: 'Tavily 全网检索', endpoint: 'https://api.tavily.com/search' }
|
||||
} as const;
|
||||
const emptyForm = (): WebSearchProviderForm => ({
|
||||
name: 'Tavily 全网检索',
|
||||
providerCode: 'tavily',
|
||||
endpoint: 'https://api.tavily.com/search',
|
||||
apiKey: ''
|
||||
...providerDefaults.qwen,
|
||||
providerCode: 'qwen',
|
||||
apiKey: '',
|
||||
agentId: '',
|
||||
agentVersion: 'release'
|
||||
});
|
||||
const form = reactive<WebSearchProviderForm>(emptyForm());
|
||||
|
||||
@@ -111,21 +134,29 @@ const openEditor = (item?: WebSearchProvider) => {
|
||||
Object.assign(form, emptyForm(), item ? {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
providerCode: 'tavily',
|
||||
providerCode: item.providerCode === 'qwen' ? 'qwen' : 'tavily',
|
||||
endpoint: item.endpoint,
|
||||
apiKey: ''
|
||||
apiKey: '',
|
||||
agentId: item.agentId || '',
|
||||
agentVersion: item.agentVersion === 'beta' ? 'beta' : 'release'
|
||||
} : {});
|
||||
editorVisible.value = true;
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (!form.name.trim() || !form.endpoint.trim() || (!form.id && !form.apiKey.trim())) {
|
||||
ElMessage.warning('请完整填写名称、接口地址和访问密钥');
|
||||
const qwenIncomplete = form.providerCode === 'qwen' && (!form.agentId.trim() || !form.agentVersion);
|
||||
if (!form.name.trim() || !form.endpoint.trim() || (!form.id && !form.apiKey.trim()) || qwenIncomplete) {
|
||||
ElMessage.warning('请完整填写名称、接口地址、访问密钥和 Agent 信息');
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await saveWebSearchProvider({ ...form, name: form.name.trim(), endpoint: form.endpoint.trim() });
|
||||
await saveWebSearchProvider({
|
||||
...form,
|
||||
name: form.name.trim(),
|
||||
endpoint: form.endpoint.trim(),
|
||||
agentId: form.agentId.trim()
|
||||
});
|
||||
editorVisible.value = false;
|
||||
ElMessage.success('配置已保存,启用前请先执行连接测试');
|
||||
await load();
|
||||
@@ -134,6 +165,15 @@ const submit = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const changeProvider = (code: 'tavily' | 'qwen') => {
|
||||
Object.assign(form, providerDefaults[code], {
|
||||
agentId: code === 'qwen' ? form.agentId : '',
|
||||
agentVersion: code === 'qwen' ? form.agentVersion : 'release'
|
||||
});
|
||||
};
|
||||
|
||||
const providerLabel = (code: string) => code === 'qwen' ? '千问联网检索' : 'Tavily';
|
||||
|
||||
const testProvider = async (item: WebSearchProvider) => {
|
||||
testingId.value = item.id;
|
||||
try {
|
||||
@@ -162,5 +202,5 @@ onMounted(load);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.web-search-page{padding:24px;color:#172033}.page-head,.provider-head,.provider-actions{display:flex;gap:12px;align-items:center}.page-head,.provider-head{justify-content:space-between}.page-head{margin-bottom:18px}.page-head h1,.provider-head h2{margin:0}.page-head h1{font-size:28px}.page-head p,.provider-head p{margin:7px 0 0;color:#667085}.provider-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(360px,1fr));gap:16px;margin-top:18px}.provider-card{padding:20px;border:1px solid #e4e7ec;border-radius:14px;background:#fff;box-shadow:0 4px 18px rgba(16,24,40,.05)}dl{display:grid;gap:12px;margin:20px 0}dl div{display:grid;grid-template-columns:90px minmax(0,1fr);gap:12px}dt{color:#667085}dd{margin:0;overflow-wrap:anywhere}.test-time{margin-left:8px;color:#667085;font-size:12px}.provider-actions{justify-content:flex-end;flex-wrap:wrap}@media(max-width:760px){.web-search-page{padding:16px}.page-head{align-items:flex-start}.provider-grid{grid-template-columns:1fr}.provider-actions{justify-content:flex-start}}
|
||||
.web-search-page{padding:24px;color:#172033}.page-head,.provider-head,.provider-actions{display:flex;gap:12px;align-items:center}.page-head,.provider-head{justify-content:space-between}.page-head{margin-bottom:18px}.page-head h1,.provider-head h2{margin:0}.page-head h1{font-size:28px}.page-head p,.provider-head p{margin:7px 0 0;color:#667085}.provider-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(360px,1fr));gap:16px;margin-top:18px}.provider-card{padding:20px;border:1px solid #e4e7ec;border-radius:14px;background:#fff;box-shadow:0 4px 18px rgba(16,24,40,.05)}dl{display:grid;gap:12px;margin:20px 0}dl div{display:grid;grid-template-columns:90px minmax(0,1fr);gap:12px}dt{color:#667085}dd{margin:0;overflow-wrap:anywhere}.test-time{margin-left:8px;color:#667085;font-size:12px}.provider-actions{justify-content:flex-end;flex-wrap:wrap}:deep(.web-search-editor .el-dialog__body){max-height:calc(100vh - 180px);overflow-y:auto}@media(max-width:760px){.web-search-page{padding:16px}.page-head{align-items:flex-start}.provider-grid{grid-template-columns:1fr}.provider-actions{justify-content:flex-start}}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user