- 新增 web2/ 前端:任务卡看板、风险雷达、决策日志、我的团队、快捷指令 - ChatPanel 增强:风险卡置顶、案卡接入、起诉状向导、语音输入 - 事项看板:点击查看详情弹窗、断点续接(恢复历史对话+matterId) - 保密模式恢复:密码验证后重建内存 session,保留多轮上下文 - 多轮对话:ask/ask-stream/ask-analyze 注入 session 历史,AI 有上下文 - 追问优化:紧扣用户问题,不再偏离主题(社保问社保,工伤问工伤) - 对话区可滚动,输入区固定可见 - 状态机放宽:允许从任意非终态转到 pending_expert - session 存活检查端点,后端重启后自动重建 session - DB migration:风险推送表、案卡表 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
125 lines
5.5 KiB
TypeScript
125 lines
5.5 KiB
TypeScript
/** 生活助理页:话题选择 + 换一批 + ChatPanel
|
|
* scene='life', 默认保密模式
|
|
*/
|
|
import { useState, useCallback } from 'react'
|
|
import { PageLayout } from '@/components/common/PageLayout'
|
|
import { ChatPanel } from '@/components/common/ChatPanel'
|
|
import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
|
import { Heart, ChevronDown, RefreshCw, Loader2 } from 'lucide-react'
|
|
import { cn } from '@/lib/utils'
|
|
import { api } from '@/lib/api'
|
|
|
|
/** 话题类型 */
|
|
interface Topic {
|
|
title: string
|
|
desc: string
|
|
samples: string[]
|
|
}
|
|
|
|
/** 生活助理:初始兜底话题,换一批时 AI 动态生成 */
|
|
const FALLBACK_TOPICS: Topic[] = [
|
|
{ title: '家庭沟通', desc: '亲子、夫妻、关系', samples: ['怎么和青春期的孩子沟通?', '夫妻因工作吵架怎么办?', '家人不支持创业怎么说服?', '工作太忙忽略家人怎么办?'] },
|
|
{ title: '子女教育', desc: '升学、兴趣、规划', samples: ['孩子不想上学怎么办?', '兴趣班该报几个?', '要不要送孩子出国?', '孩子成绩下滑怎么沟通?'] },
|
|
{ title: '父母养老', desc: '养老、医疗、陪伴', samples: ['父母年纪大了养老方案怎么选?', '异地父母怎么照顾?', '养老院还是居家养老?', '父母医疗费用怎么分担?'] },
|
|
{ title: '睡眠与压力', desc: '失眠、焦虑、调节', samples: ['最近睡眠很差怎么改善?', '工作压力大怎么缓解?', '焦虑到影响工作怎么办?', '怎么判断需要看心理医生?'] },
|
|
{ title: '出差日程', desc: '出行、时间管理', samples: ['出差频繁怎么兼顾家庭?', '行程太满怎么优化?', '出差期间公司怎么管?', '如何减少无效出差?'] },
|
|
{ title: '健康习惯', desc: '体检、饮食、运动', samples: ['体检报告几项异常怎么办?', '久坐怎么改善健康?', '应酬多怎么控制饮食?', '没时间运动怎么办?'] },
|
|
]
|
|
|
|
export function LifeAssistant() {
|
|
const [topics, setTopics] = useState<Topic[]>(FALLBACK_TOPICS)
|
|
const [selectedTopic, setSelectedTopic] = useState(0)
|
|
const [topicsCollapsed, setTopicsCollapsed] = useState(false)
|
|
const [loadingTopics, setLoadingTopics] = useState(false)
|
|
|
|
/** 换一批:调用 AI 动态生成话题(避免重复) */
|
|
const refreshTopics = useCallback(async () => {
|
|
setLoadingTopics(true)
|
|
try {
|
|
const res = await api.getTopics('life', topics.map((t) => t.title))
|
|
if (res.topics?.length > 0) {
|
|
setTopics(res.topics)
|
|
setSelectedTopic(0)
|
|
}
|
|
} catch {
|
|
// AI 不可用时保持当前话题
|
|
} finally {
|
|
setLoadingTopics(false)
|
|
}
|
|
}, [topics])
|
|
|
|
const currentTopic = topics[selectedTopic]
|
|
|
|
return (
|
|
<PageLayout
|
|
title="生活助理"
|
|
description="家庭沟通、子女教育、父母养老、健康习惯、出行规划。老板的生活问题,也值得认真对待。"
|
|
>
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-center justify-between">
|
|
<button
|
|
onClick={() => setTopicsCollapsed(!topicsCollapsed)}
|
|
className="flex items-center gap-1.5 text-base font-semibold transition-opacity hover:opacity-70"
|
|
>
|
|
<ChevronDown className={cn('h-4 w-4 transition-transform', topicsCollapsed && '-rotate-90')} />
|
|
生活话题
|
|
</button>
|
|
<button
|
|
onClick={refreshTopics}
|
|
disabled={loadingTopics}
|
|
className="flex items-center gap-1 text-xs text-muted-foreground transition-opacity hover:opacity-70 disabled:opacity-50"
|
|
>
|
|
{loadingTopics ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <RefreshCw className="h-3.5 w-3.5" />}
|
|
换一批
|
|
</button>
|
|
</div>
|
|
</CardHeader>
|
|
{!topicsCollapsed && (
|
|
<CardContent>
|
|
{loadingTopics && (
|
|
<div className="flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground">
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
AI 正在生成新话题…
|
|
</div>
|
|
)}
|
|
<div className={cn('grid gap-3 sm:grid-cols-2 lg:grid-cols-3', loadingTopics && 'pointer-events-none opacity-50')}>
|
|
{topics.map((t, i) => (
|
|
<button
|
|
key={t.title + i}
|
|
onClick={() => setSelectedTopic(i)}
|
|
className={cn(
|
|
'flex items-start gap-3 rounded-md border p-3 text-left transition-colors',
|
|
selectedTopic === i
|
|
? 'border-primary bg-primary/5 ring-1 ring-primary'
|
|
: 'border-input hover:bg-accent',
|
|
)}
|
|
>
|
|
<Heart className="mt-0.5 h-5 w-5 shrink-0 text-primary" aria-hidden />
|
|
<div>
|
|
<p className="text-sm font-medium">{t.title}</p>
|
|
<p className="mt-0.5 text-xs text-muted-foreground">{t.desc}</p>
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
)}
|
|
</Card>
|
|
|
|
{currentTopic && (
|
|
<ChatPanel
|
|
scene="life"
|
|
samples={currentTopic.samples}
|
|
topic={currentTopic.title}
|
|
defaultMode="confidential"
|
|
placeholder={`描述你的${currentTopic.title}问题…`}
|
|
showRiskCard={false}
|
|
enableUpload={false}
|
|
footer="AI 输出仅供参考,重大事项请咨询相关领域专业人士。"
|
|
/>
|
|
)}
|
|
</PageLayout>
|
|
)
|
|
}
|