import { useEffect, useState, useCallback } from 'react' import { UserCircle, History, ChevronRight, ArrowLeft, ChevronDown, Pencil, Plus, Trash2 } from 'lucide-react' import { PageLayout } from '@/components/common/PageLayout' import { EmptyState } from '@/components/common/EmptyState' import { LoadingState } from '@/components/common/LoadingState' import { ErrorState } from '@/components/common/ErrorState' import { Card, CardContent } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Input } from '@/components/ui/input' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/components/ui/dialog' import { api } from '@/lib/api' import { cn, formatTime } from '@/lib/utils' /** 用户画像数据 */ interface ProfileData { phone: string industry: string company_size: string role: string common_scenes: string[] common_topics: string[] risk_tolerance: string communication_style: string decision_style: string key_facts: Record interaction_count: number last_active: string } /** 画像历史版本 */ interface ProfileHistory { id: number version: number profile: Record source: string created_at: string } /** 字段中文标签 */ const FIELD_LABELS: Record = { industry: '行业', company_size: '企业规模', role: '角色', risk_tolerance: '风险偏好', communication_style: '沟通偏好', decision_style: '决策风格', } /** 企业规模映射 */ const SIZE_LABELS: Record = { micro: '微型(<10人)', small: '小型(10-50人)', medium: '中型(50-200人)', large: '大型(200+人)', } /** 风险偏好映射 */ const RISK_LABELS: Record = { conservative: '保守型', moderate: '稳健型', aggressive: '进取型', } /** 角色映射 */ const ROLE_LABELS: Record = { owner: '老板/创始人', manager: '管理者', ceo: 'CEO', director: '总监', } /** 决策风格映射 */ const DECISION_STYLE_LABELS: Record = { 'data-driven': '数据驱动型', intuitive: '直觉型', collaborative: '协作型', } /** 沟通偏好映射 */ const COMMUNICATION_STYLE_LABELS: Record = { concise: '简洁', detailed: '详细', structured: '结构化', } /** 行业映射 */ const INDUSTRY_LABELS: Record = { retail: '零售', manufacturing: '制造业', tech: '科技', service: '服务业', finance: '金融', realestate: '房地产', education: '教育', healthcare: '医疗健康', construction: '建筑', logistics: '物流', agriculture: '农业', hospitality: '餐饮酒店', } /** key_facts 的 key 中文映射 */ const KEY_FACT_LABELS: Record = { region: '地区', company_type: '公司类型', team_ability: '团队能力', communication: '沟通情况', medical_proof: '医疗证明', contract_terms: '合同条款', medical_period: '医疗期', financial_status: '财务状况', injury_situation: '工伤情况', legal_procedures: '法律程序', market_potential: '市场潜力', alternative_option: '备选方案', work_injury_details: '工伤详情', alternative_solution: '替代方案', work_injury_insurance: '工伤保险', performance_assessment: '绩效评估', relevant_legal_provisions: '相关法律条款', employee_count: '员工人数', revenue: '营收', profit_status: '盈利状况', business_model: '商业模式', funding_stage: '融资阶段', expansion_plan: '扩张计划', competitor: '竞争对手', customer_concentration: '客户集中度', supply_chain: '供应链', cash_flow: '现金流', asset_status: '资产状况', liability: '负债', contract_dispute: '合同纠纷', debt_amount: '债务金额', tax_risk: '税务风险', litigation_status: '诉讼状态', shareholder_structure: '股权结构', partnership: '合伙关系', salary_status: '薪资状况', social_security: '社保', severance: '经济补偿', working_years: '工作年限', probation_period: '试用期', termination_reason: '解雇原因', overtime_status: '加班情况', export_capability: '出口能力', family_situation: '家庭情况', health_status: '健康状况', travel_plan: '出行计划', important_event: '重要事项', education_background: '教育背景', stress_source: '压力来源', personality: '性格特征', work_time: '工作时间', contract_status: '合同状态', salary_calculation_basis: '薪资计算依据', injury_detail: '受伤详情', treatment_status: '治疗状况', recovery_status: '康复状况', compensation_claim: '赔偿诉求', arbitration_status: '仲裁状态', employment_relationship: '劳动关系', wage_standard: '工资标准', working_hours: '工时', leave_record: '请假记录', discipline_record: '违纪记录', training_agreement: '培训协议', confidentiality_agreement: '保密协议', non_compete: '竞业限制', ip_ownership: '知识产权归属', data_protection: '数据保护', compliance_status: '合规状况', regulatory_requirement: '监管要求', license_status: '许可状态', audit_finding: '审计发现', internal_control: '内部控制', risk_management: '风险管理', insurance_coverage: '保险覆盖', claim_status: '理赔状态', policy_detail: '保单详情', premium_status: '保费状态', beneficiary: '受益人', coverage_amount: '保额', deductible: '免赔额', policy_period: '保险期间', renewal_status: '续保状态', cancellation_reason: '退保原因', loan_purpose: '借款用途', repayment_plan: '还款计划', collateral: '抵押物', guarantor_info: '担保人', interest_rate: '利率', penalty_rate: '罚息率', overdue_days: '逾期天数', collection_status: '催收状态', settlement_status: '结算状态', bankruptcy_status: '破产状态', restructure_plan: '重组方案', liquidation_status: '清算状态', debt_restructuring: '债务重组', asset_disposal: '资产处置', business_transfer: '业务转让', merger_status: '并购状态', acquisition_status: '收购状态', divestiture: '剥离', joint_venture: '合资', strategic_alliance: '战略联盟', ip_valuation: '知识产权估值', technology_transfer: '技术转让', royalty_rate: '许可费率', infringement_scope: '侵权范围', damages_claim: '损害赔偿诉求', injunction_status: '禁令状态', invalidation_request: '无效宣告请求', opposition_status: '异议状态', licensing_term: '许可期限', territory: '地域范围', exclusivity: '排他性', sublicensing: '再许可', warranty_term: '保证条款', indemnity: '补偿条款', limitation_liability: '责任限制', force_majeure: '不可抗力', dispute_resolution: '争议解决', governing_law: '适用法律', jurisdiction: '管辖', arbitration_clause: '仲裁条款', mediation_status: '调解状态', enforcement_status: '执行状态', execution_status: '执行状况', preservation_measure: '保全措施', objection_status: '异议状态', retrial_request: '再审请求', appeal_status: '上诉状态', court_level: '法院层级', case_procedure: '案件程序', hearing_date: '开庭日期', evidence_status: '举证状况', witness_status: '证人状况', expert_evaluation: '专家鉴定', judicial_interpretation: '司法解释', precedent_case: '先例案例', legislation_reference: '法律依据', // 补充:AI 分析维度生成的 key evidence: '证据', amount: '涉及金额', party: '对方当事人', timeline: '时间线', contract: '合同', scale: '决策规模', cashflow: '现金流', alternatives: '备选方案', market: '市场状况', team: '团队', risk_tolerance: '风险承受', profitability: '盈利能力', situation: '情境', people: '涉及人员', duration: '持续时间', attempt: '已尝试方式', goal: '期望结果', feeling: '情绪状态', trigger: '触发事件', time_range: '时间范围', region_policy: '地区政策', employer_status: '雇主身份', has_written_contract_agreement: '是否有书面合同', event_occurrence_and_current_status: '事件发生及现状', notification_negotiation: '通知与协商', labor_contract: '劳动合同', performance_evaluation: '绩效评估', salary_basis: '薪资计算依据', legal_provisions: '法律条款', alternative_plan: '备选方案', replacement_plan: '替代方案', financial_situation: '财务状况', } /** key_facts 的值中文映射 */ const KEY_FACT_VALUE_LABELS: Record = { yes: '是', no: '否', true: '是', false: '否', none: '无', unknown: '未知', 'n/a': '不适用', pending: '待定', active: '活跃', inactive: '停用', profit: '盈利', loss: '亏损', expansion: '扩张', contraction: '收缩', stable: '稳定', growth: '增长中', declining: '下滑中', ...SIZE_LABELS, ...RISK_LABELS, ...ROLE_LABELS, ...INDUSTRY_LABELS, // 补充值翻译 employer: '雇主', employee: '员工', 'contract review': '合同审查', 'work-life balance': '工作生活平衡', 'has growth potential': '有增长潜力', 'separate financing': '单独融资', 'change product line': '改变产品线', 'compulsory payment': '强制缴纳', 'can terminate if unqualified': '不合格可以辞退', 'can dismiss if unqualified': '不合格可以解除合同', 'in negotiation': '在协商', 'loss 1 million': '亏损100万', 'has written contract': '有书面合同', 'no written contract': '无书面合同', 'limited company': '有限公司', 'not capable': '不具备', 'can terminate': '可以解除', 'yes, recognized': '是,已认定', 'incompetent': '不胜任', 'communicated': '沟通了', 'not communicated': '未沟通', 'clear standards': '有明确评估标准和记录', '6 months ago': '6个月前', 'last year': '去年', 'has ended': '已结束', 'yes, mandatory': '是,强制', } /** 场景中文映射 */ const SCENE_LABELS: Record = { legal: '私人法务', decision: '经营决策', life: '生活助理', emotion: '情绪中心', } /** 来源映射 */ const SOURCE_LABELS: Record = { auto: 'AI 自动提取', manual: '用户确认', } /** 画像特征 key(稳定的用户属性,直接展示) */ const PROFILE_FACT_KEYS = new Set([ 'region', 'company_type', 'team_ability', 'industry', 'company_size', 'role', 'risk_tolerance', 'communication_style', 'decision_style', 'market_potential', 'financial_status', 'financial_situation', 'cash_flow', 'asset_status', 'liability', 'revenue', 'profit_status', 'business_model', 'funding_stage', 'expansion_plan', 'competitor', 'customer_concentration', 'supply_chain', 'employee_count', 'shareholder_structure', 'partnership', ]) /** 判断 key 属于画像特征还是个案事实 */ function isProfileFact(key: string): boolean { return PROFILE_FACT_KEYS.has(String(key).toLowerCase()) } const TOPIC_LABELS: Record = { 'contract review': '合同审查', 'work-life balance': '工作生活平衡', 'tax': '税务', 'expansion': '扩张', 'financing': '融资', 'labor': '劳动用工', 'employment': '劳动用工', 'litigation': '诉讼', 'contract': '合同', 'shareholder': '股东', 'debt': '债务', 'salary': '薪资', 'social security': '社保', 'work injury': '工伤', 'termination': '辞退', 'decision': '决策', 'pricing': '定价', 'hiring': '招聘', 'layoff': '裁员', 'partnership': '合伙', 'investment': '投资', 'family': '家庭', 'health': '健康', 'stress': '压力', 'sleep': '睡眠', 'parenting': '亲子', 'education': '教育', 'marriage': '婚姻', } /** 翻译常问话题 */ function translateTopic(topic: string): string { const t = String(topic) const lower = t.toLowerCase().trim() return TOPIC_LABELS[lower] || t } /** 格式化字段值 */ function formatValue(key: string, value: string): string { const v = String(value) if (key === 'company_size') return SIZE_LABELS[v] || v if (key === 'risk_tolerance') return RISK_LABELS[v] || v if (key === 'role') return ROLE_LABELS[v] || v if (key === 'decision_style') return DECISION_STYLE_LABELS[v] || v if (key === 'communication_style') return COMMUNICATION_STYLE_LABELS[v] || v if (key === 'industry') return INDUSTRY_LABELS[v] || v return v } /** 翻译 key_facts 的 key */ function translateFactKey(key: string): string { const k = String(key).toLowerCase() return KEY_FACT_LABELS[k] || String(key) } /** 翻译 key_facts 的值 */ function translateFactValue(value: string): string { const v = String(value) const lower = v.toLowerCase().trim() return KEY_FACT_VALUE_LABELS[lower] || v } export function MyProfile() { const [profile, setProfile] = useState(null) const [history, setHistory] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [currentHistory, setCurrentHistory] = useState(null) const [showCaseFacts, setShowCaseFacts] = useState(false) const [editing, setEditing] = useState<{ field: string; label: string; value: string; type: 'select' | 'text' } | null>(null) const [editValue, setEditValue] = useState('') const [saving, setSaving] = useState(false) const [addingFact, setAddingFact] = useState(false) const [newFactKey, setNewFactKey] = useState('') const [newFactValue, setNewFactValue] = useState('') const [deletingFact, setDeletingFact] = useState<{ key: string; label: string } | null>(null) const load = useCallback(async () => { setLoading(true) setError(null) try { const [profileRes, historyRes] = await Promise.all([ api.getProfile(), api.getProfileHistory(), ]) setProfile(profileRes as unknown as ProfileData) setHistory(historyRes.results) } catch (e) { setError(e instanceof Error ? e.message : '加载失败') } finally { setLoading(false) } }, []) useEffect(() => { load() }, [load]) /** 标准字段选项(下拉选择) */ const FIELD_OPTIONS: Record = { industry: Object.entries(INDUSTRY_LABELS).map(([v, l]) => ({ value: v, label: l })), company_size: Object.entries(SIZE_LABELS).map(([v, l]) => ({ value: v, label: l })), role: Object.entries(ROLE_LABELS).map(([v, l]) => ({ value: v, label: l })), risk_tolerance: Object.entries(RISK_LABELS).map(([v, l]) => ({ value: v, label: l })), communication_style: Object.entries(COMMUNICATION_STYLE_LABELS).map(([v, l]) => ({ value: v, label: l })), decision_style: Object.entries(DECISION_STYLE_LABELS).map(([v, l]) => ({ value: v, label: l })), } /** 打开编辑 */ const handleEdit = (field: string, label: string, value: string) => { const isSelect = !!FIELD_OPTIONS[field] setEditing({ field, label, value, type: isSelect ? 'select' : 'text' }) setEditValue(value) } /** 保存编辑 */ const handleSave = async () => { if (!editing || !editValue.trim()) { setEditing(null); return } setSaving(true) try { const field = editing.field if (FIELD_LABELS[field]) { // 标准字段 await api.updateProfile({ [field]: editValue }) setProfile((prev) => prev ? { ...prev, [field]: editValue } : prev) } else { // key_facts await api.updateProfile({ key_facts: { [field]: editValue } }) setProfile((prev) => prev ? { ...prev, key_facts: { ...prev.key_facts, [field]: editValue } } : prev) } setEditing(null) } catch (e) { setError(e instanceof Error ? e.message : '保存失败') } finally { setSaving(false) } } /** 删除画像特征(确认后执行) */ const handleDeleteFact = (key: string, label: string) => { setDeletingFact({ key, label }) } /** 确认删除 */ const confirmDeleteFact = async () => { if (!deletingFact || !profile) return try { const newFacts = { ...profile.key_facts } delete newFacts[deletingFact.key] await api.updateProfile({ key_facts: newFacts }) setProfile((prev) => prev ? { ...prev, key_facts: newFacts } : prev) setDeletingFact(null) } catch (e) { setError(e instanceof Error ? e.message : '删除失败') } } /** 添加画像特征 */ const handleAddFact = async () => { if (!newFactKey.trim() || !newFactValue.trim() || !profile) return setSaving(true) try { const key = newFactKey.trim() const newFacts = { ...profile.key_facts, [key]: newFactValue.trim() } await api.updateProfile({ key_facts: newFacts }) setProfile((prev) => prev ? { ...prev, key_facts: newFacts } : prev) setAddingFact(false) setNewFactKey('') setNewFactValue('') } catch (e) { setError(e instanceof Error ? e.message : '添加失败') } finally { setSaving(false) } } if (loading) return if (error) return if (!profile) return null // ===== 历史详情视图 ===== if (currentHistory) { const hp = currentHistory.profile as Record return ( setCurrentHistory(null)}> 返回 } >
{/* 标准字段:多列网格 */}
{Object.entries(FIELD_LABELS).map(([key, label]) => { const val = hp[key] as string if (!val) return null return (
{label} {formatValue(key, val)}
) })}
{Array.isArray(hp.common_topics) && (hp.common_topics as string[]).length > 0 && (

常问话题

{(hp.common_topics as string[]).map((t, i) => ( {translateTopic(String(t))} ))}
)} {Boolean(hp.key_facts && typeof hp.key_facts === 'object' && !Array.isArray(hp.key_facts) && Object.keys(hp.key_facts as Record).length > 0) && (

关键事实

{Object.entries(hp.key_facts as Record).map(([k, v]) => (
{translateFactKey(k)} {translateFactValue(String(v))}
))}
)} {Array.isArray(hp.common_scenes) && (hp.common_scenes as string[]).length > 0 && (
常用场景
{(hp.common_scenes as string[]).map((s, i) => ( {SCENE_LABELS[String(s)] || String(s)} ))}
)}
) } // ===== 画像主视图 ===== return ( {/* 当前画像 */}

当前画像

交互 {profile.interaction_count} 次
{/* 标准字段:多列网格(可编辑) */}
{Object.entries(FIELD_LABELS).map(([key, label]) => { const val = profile[key as keyof ProfileData] as string if (!val) return null return (
{label}
{formatValue(key, val)}
) })}
{profile.common_topics && profile.common_topics.length > 0 && (

常问话题

{profile.common_topics.map((t, i) => ( {translateTopic(t)} ))}
)} {profile.key_facts && Object.keys(profile.key_facts).length > 0 && (() => { const entries = Object.entries(profile.key_facts) const profileFacts = entries.filter(([k]) => isProfileFact(k)) const caseFacts = entries.filter(([k]) => !isProfileFact(k)) return ( <> {/* 画像特征:直接展示(可编辑/可增删) */} {profileFacts.length > 0 && (

画像特征

{profileFacts.map(([k, v]) => (
{translateFactKey(k)}
{translateFactValue(v)}
))}
)} {/* 无画像特征时也显示添加按钮 */} {profileFacts.length === 0 && (
)} {/* 个案事实:折叠展示 */} {caseFacts.length > 0 && (
{showCaseFacts && (
{caseFacts.map(([k, v]) => (
{translateFactKey(k)} {translateFactValue(v)}
))}
)}
)} ) })()} {profile.common_scenes && profile.common_scenes.length > 0 && (
常用场景
{profile.common_scenes.map((s, i) => ( {SCENE_LABELS[s] || s} ))}
)} {profile.last_active && (

最后活跃:{formatTime(profile.last_active)}

)}
{/* 版本历史 */}

版本历史

最近 3 个版本
{history.length === 0 ? ( ) : (
{history.slice(0, 3).map((h) => ( setCurrentHistory(h)} >

版本 v{h.version}

{SOURCE_LABELS[h.source] || h.source} {formatTime(h.created_at)}
))}
)}
{/* 编辑弹窗 */} !open && setEditing(null)}> 编辑{editing?.label} 修改后将保存为新的画像版本 {editing?.type === 'select' && editing.field in FIELD_OPTIONS ? ( ) : ( setEditValue(e.target.value)} placeholder={`请输入${editing?.label}`} onKeyDown={(e) => e.key === 'Enter' && handleSave()} /> )} {/* 添加画像特征弹窗 */} 添加画像特征 填写特征名称和值,保存后生效
setNewFactKey(e.target.value)} placeholder="如:行业、公司规模、地区" />
setNewFactValue(e.target.value)} placeholder="如:科技、小型、上海" onKeyDown={(e) => e.key === 'Enter' && handleAddFact()} />
{/* 删除确认弹窗 */} !open && setDeletingFact(null)}> 删除画像特征 确定要删除「{deletingFact?.label}」吗?此操作不可撤销。
) }