771b70843f
Add agent (数字员工), followup (智能跟进), outreach (开发信), competitor (竞品分析) to ai_routing field labels in admin Config.vue Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
112 lines
5.3 KiB
Vue
112 lines
5.3 KiB
Vue
<template>
|
|
<div>
|
|
<el-card v-for="cfg in configs" :key="cfg.key" shadow="never" class="cfg-card">
|
|
<template #header>
|
|
<div class="cfg-header">
|
|
<span>{{ configLabels[cfg.key] || cfg.key }}</span>
|
|
<el-tag size="small" v-if="cfg.description">{{ cfg.description }}</el-tag>
|
|
</div>
|
|
</template>
|
|
<div v-if="cfg.key === 'ai_routing'">
|
|
<div v-for="(taskVal, taskKey) in cfg.value" :key="taskKey" class="cfg-nested-group">
|
|
<div class="cfg-group-title">{{ fieldLabelsMap.ai_routing?.[taskKey] || taskKey }}</div>
|
|
<div class="cfg-field">
|
|
<span class="cfg-label">主选</span>
|
|
<el-select v-model="edits[cfg.key][taskKey].primary" size="small" style="width:380px" filterable>
|
|
<el-option v-for="p in providers" :key="p.name + '|' + p.model_name" :value="p.name + '|' + p.model_name" :label="p.name + ' — ' + p.model_name" />
|
|
</el-select>
|
|
</div>
|
|
<div class="cfg-field">
|
|
<span class="cfg-label">备用</span>
|
|
<el-select v-model="edits[cfg.key][taskKey].fallback" size="small" style="width:500px" multiple filterable collapse-tags>
|
|
<el-option v-for="p in providers" :key="p.name + '|' + p.model_name" :value="p.name + '|' + p.model_name" :label="p.name + ' — ' + p.model_name" />
|
|
</el-select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="typeof cfg.value === 'object' && !Array.isArray(cfg.value)">
|
|
<div class="cfg-field" v-for="(v, k) in cfg.value" :key="k">
|
|
<span class="cfg-label">{{ fieldLabel(cfg.key, k) }}</span>
|
|
<el-input v-if="typeof v === 'string'" v-model="edits[cfg.key][k]" size="small" style="width:300px" />
|
|
<el-input-number v-else-if="typeof v === 'number'" v-model="edits[cfg.key][k]" size="small" />
|
|
<el-switch v-else-if="typeof v === 'boolean'" v-model="edits[cfg.key][k]" />
|
|
<el-input v-else v-model="edits[cfg.key][k]" type="textarea" :rows="2" size="small" style="width:400px" />
|
|
</div>
|
|
</div>
|
|
<div v-else-if="typeof cfg.value === 'boolean'">
|
|
<el-switch v-model="edits[cfg.key]" />
|
|
</div>
|
|
<div v-else>
|
|
<el-input v-model="edits[cfg.key]" type="textarea" :rows="3" style="max-width:600px" />
|
|
</div>
|
|
<div class="cfg-actions">
|
|
<el-button type="success" size="small" @click="save(cfg.key)">保存</el-button>
|
|
</div>
|
|
</el-card>
|
|
<el-empty v-if="!configs.length" description="暂无配置" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import { listConfig, updateConfig } from '@/api'
|
|
import http from '@/api'
|
|
|
|
const configs = ref([])
|
|
const edits = reactive({})
|
|
const providers = ref([])
|
|
const configLabels = { system_maintenance: '系统维护', feature_flags: '功能开关', translation_providers: '翻译服务', ai_model_config: 'AI模型', cache_ttl: '缓存时间', ai_routing: 'AI路由规则' }
|
|
const fieldLabelsMap = { system_maintenance: { maintenance_mode: '维护模式', maintenance_message: '维护消息' }, feature_flags: { feature_wechat_login: '微信登录', feature_export: '数据导出' }, translation_providers: { primary: '首选服务', fallback: '备用服务' }, ai_model_config: { default_model: '默认模型', max_tokens: '最大Token' }, ai_routing: { translate: '翻译', reply: '回复建议', marketing: '营销文案', extract: '信息提取', quotation: '报价单', chat: 'AI助手', agent: '数字员工', followup: '智能跟进', outreach: '开发信', competitor: '竞品分析' } }
|
|
const taskFieldLabels = { primary: '主选', fallback: '备用' }
|
|
function fieldLabel(key, k) {
|
|
if (key === 'ai_routing') return configLabels[key] + ' > ' + (taskFieldLabels[k] || k)
|
|
return fieldLabelsMap[key]?.[k] || k
|
|
}
|
|
function taskFieldLabel(cfgKey, taskKey, subKey) {
|
|
if (cfgKey === 'ai_routing') {
|
|
const taskLabel = fieldLabelsMap.ai_routing?.[taskKey] || taskKey
|
|
const subLabel = taskFieldLabels[subKey] || subKey
|
|
return taskLabel + ' > ' + subLabel
|
|
}
|
|
return subKey || taskKey
|
|
}
|
|
|
|
async function loadProviders() {
|
|
try {
|
|
const res = await http.get('/admin/ai-providers')
|
|
providers.value = (res.items || []).filter(p => p.enabled)
|
|
} catch (_) {}
|
|
}
|
|
|
|
async function load() {
|
|
try {
|
|
const r = await listConfig()
|
|
configs.value = r.items || r || []
|
|
for (const cfg of configs.value) {
|
|
edits[cfg.key] = JSON.parse(JSON.stringify(cfg.value))
|
|
}
|
|
await loadProviders()
|
|
} catch (e) { console.error(e) }
|
|
}
|
|
|
|
async function save(key) {
|
|
try {
|
|
await updateConfig(key, edits[key])
|
|
ElMessage.success('已保存,AI 路由器已重载')
|
|
} catch (e) { ElMessage.error(e?.detail || '保存失败') }
|
|
}
|
|
|
|
onMounted(load)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.cfg-card { margin-bottom: 16px; }
|
|
.cfg-header { display: flex; align-items: center; gap: 12px; font-weight: 600; }
|
|
.cfg-field { display: flex; align-items: center; gap: 12px; padding: 8px 0; }
|
|
.cfg-label { width: 160px; font-size: 13px; color: #666; flex-shrink: 0; }
|
|
.cfg-nested-group { margin-bottom: 12px; padding: 8px 12px; background: #f9fafb; border-radius: 6px; }
|
|
.cfg-group-title { font-weight: 600; font-size: 13px; color: #333; margin-bottom: 4px; padding-bottom: 4px; border-bottom: 1px solid #e5e7eb; }
|
|
.cfg-actions { margin-top: 12px; }
|
|
</style>
|