feat(history): complete messaging core before multi-account gateways
This commit is contained in:
@@ -14,7 +14,9 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { updateCustomer } from '@/services/api/customers'
|
||||
import { createTask } from '@/services/api/crm'
|
||||
import { analyzeConversation } from '@/services/api/ai'
|
||||
import { useAuthStore } from '@/app/store/auth'
|
||||
import type { Customer, Conversation } from '@/types'
|
||||
import { Avatar } from '@/components/ui/avatar'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -29,6 +31,8 @@ import { IntentScoreBadge, CustomerStageBadge, SentimentBadge, RiskLevelBadge }
|
||||
import { CUSTOMER_STAGES, NEXT_ACTIONS, countryMeta, INTENT_LEVELS } from '@/config/constants'
|
||||
import { toast, errorMessage } from '@/app/store/toast'
|
||||
import { cn } from '@/utils/cn'
|
||||
import { CustomerProfileRadar } from '@/components/customer/CustomerProfileRadar'
|
||||
import { CustomerSemanticTag } from '@/components/customer/CustomerSemanticTag'
|
||||
|
||||
export function InsightPanel({
|
||||
customer,
|
||||
@@ -38,6 +42,7 @@ export function InsightPanel({
|
||||
conversation: Conversation
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
const user = useAuthStore((state) => state.user)
|
||||
const [analyzing, setAnalyzing] = useState(false)
|
||||
const [analysis, setAnalysis] = useState(customer.aiProfile.summary)
|
||||
const c = countryMeta(customer.country)
|
||||
@@ -54,6 +59,25 @@ export function InsightPanel({
|
||||
},
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
})
|
||||
const taskMut = useMutation({
|
||||
mutationFn: (title: string) => createTask({
|
||||
title,
|
||||
type: 'revisit',
|
||||
priority: customer.riskLevel === 'urgent' || customer.riskLevel === 'high' ? 'high' : 'medium',
|
||||
status: 'pending',
|
||||
customerId: customer.id,
|
||||
customerName: customer.name,
|
||||
conversationId: conversation.id,
|
||||
ownerId: user?.id || '',
|
||||
dueAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
||||
note: `由 AI 客户洞察建议创建:${title}`,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['tasks'] })
|
||||
toast.success('已创建跟进任务', '默认明天到期')
|
||||
},
|
||||
onError: (error) => toast.error(errorMessage(error)),
|
||||
})
|
||||
|
||||
const handleAnalyze = async () => {
|
||||
setAnalyzing(true)
|
||||
@@ -67,9 +91,9 @@ export function InsightPanel({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col overflow-y-auto scrollbar-thin">
|
||||
<div className="flex h-full flex-col overflow-y-auto bg-slate-50/40 scrollbar-thin">
|
||||
{/* 客户基本信息 */}
|
||||
<div className="border-b border-border p-4">
|
||||
<div className="border-b border-slate-100 bg-white p-5">
|
||||
<div className="mb-3 flex items-center gap-3">
|
||||
<Avatar name={customer.name} src={customer.avatarUrl} size="lg" />
|
||||
<div className="min-w-0 flex-1">
|
||||
@@ -120,7 +144,7 @@ export function InsightPanel({
|
||||
<Badge key={p} variant="primary">{p}</Badge>
|
||||
))}
|
||||
{customer.tags?.map((t) => (
|
||||
<Badge key={t} variant="outline">{t}</Badge>
|
||||
<CustomerSemanticTag key={t} tag={t} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -151,9 +175,14 @@ export function InsightPanel({
|
||||
</div>
|
||||
|
||||
{/* AI 分析 */}
|
||||
<div className="border-b border-border p-4">
|
||||
<div className="space-y-4 border-b border-slate-100 p-3">
|
||||
<CustomerProfileRadar customer={customer} compact />
|
||||
<div className="rounded-2xl border border-slate-200/80 bg-white p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-xs font-semibold text-muted-foreground">AI 分析摘要</h4>
|
||||
<div>
|
||||
<p className="text-sm font-semibold tracking-tight text-slate-900">AI 洞察摘要</p>
|
||||
<p className="text-[11px] text-slate-400">基于完整对话实时更新</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={handleAnalyze} disabled={analyzing}>
|
||||
{analyzing ? <Spinner className="h-3 w-3" /> : '重新分析'}
|
||||
</Button>
|
||||
@@ -164,41 +193,24 @@ export function InsightPanel({
|
||||
<span className="ml-2 text-sm text-muted-foreground">正在分析会话上下文…</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-2 text-xs leading-relaxed text-muted-foreground">{analysis}</p>
|
||||
<p className="mt-3 text-xs leading-6 text-slate-600">{analysis || 'AI 正在根据会话完善客户画像。'}</p>
|
||||
)}
|
||||
{/* AI 提取关键信息 */}
|
||||
{Object.values(customer.aiProfile || {}).some(Boolean) && (
|
||||
<div className="mt-3 space-y-1 border-t border-border pt-3">
|
||||
<div className="mt-4 grid grid-cols-2 gap-2 border-t border-slate-100 pt-4">
|
||||
{Object.entries({
|
||||
公司: customer.aiProfile.companyName,
|
||||
行业: customer.aiProfile.industry,
|
||||
商业模式: customer.aiProfile.businessModel,
|
||||
采购角色: customer.aiProfile.purchasingRole,
|
||||
决策权: customer.aiProfile.decisionPower,
|
||||
询价数量: customer.aiProfile.inquiryQuantity,
|
||||
目标价格: customer.aiProfile.targetPrice,
|
||||
预算: customer.aiProfile.budget,
|
||||
MOQ: customer.aiProfile.moqRequirement,
|
||||
样品: customer.aiProfile.sampleNeed,
|
||||
定制: customer.aiProfile.customRequirement,
|
||||
交付: customer.aiProfile.deliveryTime,
|
||||
紧迫程度: customer.aiProfile.urgency,
|
||||
采购周期: customer.aiProfile.purchaseTimeline,
|
||||
物流: customer.aiProfile.logistics,
|
||||
付款: customer.aiProfile.payment,
|
||||
核心需求: customer.aiProfile.keyNeeds?.join('、'),
|
||||
痛点: customer.aiProfile.painPoints?.join('、'),
|
||||
顾虑: customer.aiProfile.concerns?.join('、'),
|
||||
异议: customer.aiProfile.objections?.join('、'),
|
||||
购买信号: customer.aiProfile.buyingSignals?.join('、'),
|
||||
竞品: customer.aiProfile.competitorMentions?.join('、'),
|
||||
沟通风格: customer.aiProfile.communicationStyle,
|
||||
建议语气: customer.aiProfile.recommendedTone,
|
||||
跟进时机: customer.aiProfile.followUpTiming,
|
||||
}).filter(([, v]) => v).map(([k, v]) => (
|
||||
<div key={k} className="flex gap-2 text-xs">
|
||||
<span className="shrink-0 text-muted-foreground">{k}</span>
|
||||
<span className="text-foreground">{v as string}</span>
|
||||
<div key={k} className="min-w-0 rounded-xl bg-slate-50 px-3 py-2">
|
||||
<p className="text-[10px] font-medium text-slate-400">{k}</p>
|
||||
<p className="mt-0.5 truncate text-xs font-medium text-slate-700">{v as string}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -209,7 +221,7 @@ export function InsightPanel({
|
||||
['流失概率', customer.aiProfile.churnProbability],
|
||||
['分析可信度', customer.aiProfile.confidenceScore],
|
||||
].map(([label, value]) => (
|
||||
<div key={String(label)} className="rounded-lg bg-muted/70 px-2 py-2 text-center">
|
||||
<div key={String(label)} className="rounded-xl bg-slate-50 px-2 py-2 text-center">
|
||||
<p className="text-[10px] text-muted-foreground">{label}</p>
|
||||
<p className="text-sm font-semibold">{Number(value || 0)}%</p>
|
||||
</div>
|
||||
@@ -223,17 +235,19 @@ export function InsightPanel({
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 推荐动作 */}
|
||||
<div className="p-4">
|
||||
<h4 className="text-xs font-semibold text-muted-foreground">AI 推荐下一步</h4>
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||
<h4 className="text-xs font-semibold text-slate-500">AI 推荐下一步</h4>
|
||||
<div className="mt-2 space-y-2">
|
||||
{customer.aiProfile.nextActions.map((a) => (
|
||||
<button
|
||||
key={a}
|
||||
onClick={() => toast.info(`建议操作:${a}`)}
|
||||
className="rounded-full border border-primary-200 bg-primary-50 px-2.5 py-1 text-xs text-primary-700 hover:bg-primary-100"
|
||||
onClick={() => taskMut.mutate(a)}
|
||||
disabled={taskMut.isPending}
|
||||
className="w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-start text-xs text-slate-700 hover:border-primary-200 hover:bg-primary-50"
|
||||
>
|
||||
{a}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user