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:
@@ -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>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nav": {
|
"nav": {
|
||||||
|
"home": "首页工作台",
|
||||||
|
"more": "更多",
|
||||||
|
"productsQuotations": "报价产品",
|
||||||
"discovery": "发现客户",
|
"discovery": "发现客户",
|
||||||
"workspace": "工作台",
|
"workspace": "工作台",
|
||||||
"customers": "客户管理",
|
"customers": "客户管理",
|
||||||
|
|||||||
Reference in New Issue
Block a user