feat: add agent mode toggle to AiAssistant + i18n nav keys

AiAssistant: new mode switch (AI助手/数字员工) in dialog header. Agent mode shows a product info form that triggers the agent pipeline (search->analyze->outreach). Pipeline progress stages and lead results displayed inline. i18n: added missing nav keys (home, more, productsQuotations) to both zh-CN and en locale files.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
wlt
2026-06-17 10:48:25 +08:00
parent 5590f81536
commit caa484f651
3 changed files with 150 additions and 2 deletions
+144 -2
View File
@@ -6,13 +6,69 @@
<el-dialog <el-dialog
v-model="visible" v-model="visible"
title="TradeMate AI 助手"
width="480px" width="480px"
:close-on-click-modal="false" :close-on-click-modal="false"
class="ai-dialog" class="ai-dialog"
top="5vh" top="5vh"
@opened="onOpened" @opened="onOpened"
> >
<template #header>
<div class="ai-header">
<span>{{ agentMode ? '数字员工' : 'TradeMate AI 助手' }}</span>
<el-switch
v-model="agentMode"
active-text="数字员工"
inactive-text="AI助手"
size="small"
style="margin-left:12px"
/>
</div>
</template>
<!-- Agent Mode: Pipeline form -->
<div v-if="agentMode" class="ai-agent-panel">
<div class="ai-agent-intro">
输入产品信息AI 数字员工将自动搜索客户分析匹配并生成开发信
</div>
<el-form label-position="top" style="padding:0 4px">
<el-form-item label="产品名称">
<el-input v-model="agentForm.product_name" placeholder="如:LED路灯" />
</el-form-item>
<el-form-item label="产品描述">
<el-input v-model="agentForm.product_description" type="textarea" :rows="3" placeholder="描述产品的规格、优势、用途..." />
</el-form-item>
<el-form-item label="目标市场(可选)">
<el-input v-model="agentForm.target_market" placeholder="如:美国、德国,留空为全球" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="agentRunning" @click="startAgent" style="width:100%">
{{ agentRunning ? '运行中...' : '🚀 启动数字员工' }}
</el-button>
</el-form-item>
</el-form>
<div v-if="agentPipeline" class="ai-agent-result">
<el-divider />
<div class="ai-agent-stages">
<div v-for="(stage, si) in agentPipeline.pipeline_data?.stages || []" :key="si" class="ai-agent-stage">
<el-steps :active="stage.completed ? 1 : (stage.running ? 0 : -1)" size="small" align-center>
<el-step :title="stage.name" :status="stage.completed ? 'success' : (stage.running ? 'process' : 'wait')" />
</el-steps>
<div v-if="stage.error" class="ai-stage-error">{{ stage.error }}</div>
</div>
</div>
<div v-if="agentPipeline.pipeline_data?.leads?.length" class="ai-agent-leads">
<div class="ai-agent-leads-title">发现 {{ agentPipeline.pipeline_data.leads.length }} 个高匹配客户</div>
<div v-for="(lead, li) in agentPipeline.pipeline_data.leads.slice(0, 5)" :key="li" class="ai-agent-lead">
<strong>{{ lead.company || lead.name || '未知' }}</strong>
<span v-if="lead.match_score" class="ai-match-badge">匹配度 {{ lead.match_score }}%</span>
</div>
</div>
</div>
</div>
<!-- Chat Mode -->
<template v-if="!agentMode">
<div class="ai-messages" ref="msgContainer"> <div class="ai-messages" ref="msgContainer">
<div v-for="(msg, i) in messages" :key="i" class="ai-msg-row" :class="msg.role"> <div v-for="(msg, i) in messages" :key="i" class="ai-msg-row" :class="msg.role">
<div class="ai-avatar" v-if="msg.role === 'assistant'"> <div class="ai-avatar" v-if="msg.role === 'assistant'">
@@ -59,9 +115,10 @@
</div> </div>
</div> </div>
</div> </div>
</template>
<template #footer> <template #footer>
<div class="ai-input-bar"> <div v-if="!agentMode" class="ai-input-bar">
<el-input <el-input
v-model="inputText" v-model="inputText"
placeholder="输入你的问题..." placeholder="输入你的问题..."
@@ -84,6 +141,7 @@ import {
aiChat, aiQuickQuestions, aiChat, aiQuickQuestions,
createCustomer, createProduct, createQuotation, createCustomer, createProduct, createQuotation,
scanFollowups, generateMarketing, discoverySearch, scanFollowups, generateMarketing, discoverySearch,
startAgentPipeline,
} from '@/api' } from '@/api'
const visible = ref(false) const visible = ref(false)
@@ -96,6 +154,11 @@ const messages = ref([
]) ])
const showSuggestions = ref(false) const showSuggestions = ref(false)
const agentMode = ref(false)
const agentForm = ref({ product_name: '', product_description: '', target_market: '' })
const agentRunning = ref(false)
const agentPipeline = ref(null)
const fieldLabel = (type, key) => { const fieldLabel = (type, key) => {
const labels = { const labels = {
name: '名称', phone: '电话', email: '邮箱', company: '公司', name: '名称', phone: '电话', email: '邮箱', company: '公司',
@@ -239,6 +302,28 @@ const scrollToBottom = async () => {
const el = msgContainer.value const el = msgContainer.value
if (el) el.scrollTop = el.scrollHeight if (el) el.scrollTop = el.scrollHeight
} }
async function startAgent() {
if (!agentForm.value.product_name?.trim()) {
ElMessage.warning('产品名称不能为空')
return
}
agentRunning.value = true
agentPipeline.value = null
try {
const res = await startAgentPipeline({
product_name: agentForm.value.product_name.trim(),
product_description: agentForm.value.product_description?.trim() || '',
target_market: agentForm.value.target_market?.trim() || '',
})
agentPipeline.value = res.data || res
ElMessage.success('数字员工任务完成!')
} catch (e) {
ElMessage.error(e?.detail || e?.message || '启动数字员工失败')
} finally {
agentRunning.value = false
}
}
</script> </script>
<style scoped> <style scoped>
@@ -379,4 +464,61 @@ const scrollToBottom = async () => {
padding: 10px 16px; padding: 10px 16px;
border-top: 1px solid #f0f0f0; border-top: 1px solid #f0f0f0;
} }
.ai-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.ai-agent-panel {
padding: 20px 16px;
overflow-y: auto;
height: 100%;
}
.ai-agent-intro {
font-size: 13px;
color: #666;
line-height: 1.6;
margin-bottom: 16px;
padding: 12px;
background: #f0f5ff;
border-radius: 6px;
}
.ai-agent-result {
margin-top: 8px;
}
.ai-agent-stages {
margin-bottom: 12px;
}
.ai-agent-stage {
margin-bottom: 8px;
}
.ai-stage-error {
color: #f56c6c;
font-size: 12px;
margin-top: 4px;
}
.ai-agent-leads-title {
font-weight: 600;
font-size: 14px;
margin-bottom: 8px;
color: #303133;
}
.ai-agent-lead {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
margin-bottom: 6px;
background: #f9f9f9;
border-radius: 6px;
font-size: 13px;
}
.ai-match-badge {
background: #e6f7e6;
color: #52c41a;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
</style> </style>
+3
View File
@@ -1,5 +1,8 @@
{ {
"nav": { "nav": {
"home": "Workspace",
"more": "More",
"productsQuotations": "Products & Quotes",
"discovery": "Find Buyers", "discovery": "Find Buyers",
"workspace": "Dashboard", "workspace": "Dashboard",
"customers": "Customers", "customers": "Customers",
+3
View File
@@ -1,5 +1,8 @@
{ {
"nav": { "nav": {
"home": "首页工作台",
"more": "更多",
"productsQuotations": "报价产品",
"discovery": "发现客户", "discovery": "发现客户",
"workspace": "工作台", "workspace": "工作台",
"customers": "客户管理", "customers": "客户管理",