feat: 话题动态化 + 要素化立案 + 知识库增强
话题动态化: - 新增 POST /topics 端点,AI 根据场景+画像动态生成话题 - 前端3个助手页面改为兜底话题先行,换一批时AI生成 - 去除固化话题列表,支持个性化推荐 要素化立案(对接最高法67类要素表): - 导入68类要素式起诉状模板到知识库(法〔2025〕82号) - 覆盖民事/商事/知识产权/行政/刑事自诉/海事/环境/国家赔偿/执行9领域 - ask-analyze 法务场景自动匹配案由模板 - 新增 POST /element-filing 端点,AI填充要素生成要素式起诉状 - ChatPanel 确认事实后可一键生成要素式起诉状,支持复制全文 知识库增强: - 要素表模板展示全中文化(type/required/标签翻译) - 知识库页面要素表模板结构化展示(案由/要素清单/诉讼请求) - 各类型tab显示数量和总数量 画像翻译完善: - 补充90+个key_facts key中文翻译 - MyProfile多列紧凑布局 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
+93
-108
@@ -1,84 +1,51 @@
|
||||
import { useState } from 'react'
|
||||
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 { Wallet, Store, Handshake, Tag, UserPlus, Repeat, ChevronDown } from 'lucide-react'
|
||||
import { TrendingUp, ChevronDown, RefreshCw, Loader2 } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { api } from '@/lib/api'
|
||||
|
||||
/** 决策话题:每个话题对应不同的示例问题 */
|
||||
const TOPICS = [
|
||||
{
|
||||
icon: Store,
|
||||
title: '扩张收缩',
|
||||
desc: '开店、关店、业务线调整',
|
||||
samples: [
|
||||
'现在适合扩张开第二家店吗?',
|
||||
'要不要关闭亏损的业务线?',
|
||||
'新业务线该自营还是加盟?',
|
||||
'应该先扩张还是先巩固现有市场?',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Handshake,
|
||||
title: '项目承接',
|
||||
desc: '大单、长期合同、付款条件',
|
||||
samples: [
|
||||
'是否应该接一个付款周期 90 天的大单?',
|
||||
'长期框架合同要不要签?',
|
||||
'预付款比例低的项目能接吗?',
|
||||
'客户要求独家合作,利弊怎么权衡?',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Tag,
|
||||
title: '定价策略',
|
||||
desc: '降价、涨价、促销',
|
||||
samples: [
|
||||
'该不该降价 15% 抢竞品的客户?',
|
||||
'产品涨价 10% 会流失多少客户?',
|
||||
'要不要做满减促销活动?',
|
||||
'新产品定价应该走高端还是性价比?',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: UserPlus,
|
||||
title: '招人裁员',
|
||||
desc: '关键岗位、团队扩张、减员',
|
||||
samples: [
|
||||
'应该招一个销售总监吗?',
|
||||
'要不要招应届生自己培养?',
|
||||
'团队人效低,该裁谁?',
|
||||
'核心员工被挖,要不要加薪留人?',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Wallet,
|
||||
title: '融资合伙',
|
||||
desc: '投资、入股、融资',
|
||||
samples: [
|
||||
'要不要接受天使投资?',
|
||||
'合伙人要退股,怎么谈条件?',
|
||||
'朋友要入股,股权怎么分?',
|
||||
'银行贷款还是股权融资更合适?',
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Repeat,
|
||||
title: '转型外包',
|
||||
desc: '业务转型、外包、合作',
|
||||
samples: [
|
||||
'传统业务要不要转型线上?',
|
||||
'非核心业务外包还是自己做?',
|
||||
'要不要和同行做联合采购?',
|
||||
'代工还是自建生产线?',
|
||||
],
|
||||
},
|
||||
/** 话题类型 */
|
||||
interface Topic {
|
||||
title: string
|
||||
desc: string
|
||||
samples: string[]
|
||||
}
|
||||
|
||||
/** 经营决策助手:初始兜底话题,换一批时 AI 动态生成 */
|
||||
const FALLBACK_TOPICS: Topic[] = [
|
||||
{ title: '扩张收缩', desc: '开店、关店、业务线', samples: ['现在适合扩张开第二家店吗?', '要不要关闭亏损的业务线?', '新业务线该自营还是加盟?', '应该先扩张还是先巩固?'] },
|
||||
{ title: '项目承接', desc: '大单、长期合同', samples: ['是否应该接付款周期90天的大单?', '长期框架合同要不要签?', '预付款比例低的项目能接吗?', '客户要求独家合作利弊?'] },
|
||||
{ title: '定价策略', desc: '涨价、降价、促销', samples: ['该不该降价15%抢竞品客户?', '产品涨价怎么跟客户沟通?', '促销活动会不会损害品牌?', '成本上涨如何调整定价?'] },
|
||||
{ title: '招人裁员', desc: '招聘、解雇、绩效', samples: ['现在该招人还是裁人?', '裁掉绩效差的员工怎么操作?', '关键岗位招人还是培养?', '外包和正式员工怎么选?'] },
|
||||
{ title: '融资合伙', desc: '融资、合伙、股权', samples: ['现在适合融资吗?', '合伙人要退股怎么谈条件?', '出让多少股权合适?', '银行贷款还是股权融资?'] },
|
||||
{ title: '转型外包', desc: '转型、外包、调整', samples: ['业务转型方向怎么选?', '哪些业务可以外包?', '转型期间现金流怎么管理?', '外包质量怎么控?'] },
|
||||
]
|
||||
|
||||
/** 经营决策助手:情景推演、方案对比、收益/风险/现金流/推荐程度 */
|
||||
export function Decisions() {
|
||||
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('decision', 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
|
||||
@@ -87,50 +54,68 @@ export function Decisions() {
|
||||
>
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<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>
|
||||
<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>
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{TOPICS.map((t, i) => (
|
||||
<button
|
||||
key={t.title}
|
||||
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',
|
||||
)}
|
||||
>
|
||||
<t.icon 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>
|
||||
{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',
|
||||
)}
|
||||
>
|
||||
<TrendingUp 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>
|
||||
|
||||
<ChatPanel
|
||||
scene="decision"
|
||||
samples={TOPICS[selectedTopic].samples}
|
||||
topic={TOPICS[selectedTopic].title}
|
||||
defaultMode="normal"
|
||||
placeholder={`描述你的${TOPICS[selectedTopic].title}问题…`}
|
||||
showRiskCard={false}
|
||||
enableUpload={false}
|
||||
footer="AI 提供情景推演和利弊分析,最终决策由老板做出。"
|
||||
/>
|
||||
{currentTopic && (
|
||||
<ChatPanel
|
||||
scene="decision"
|
||||
samples={currentTopic.samples}
|
||||
topic={currentTopic.title}
|
||||
defaultMode="normal"
|
||||
placeholder={`描述你的${currentTopic.title}问题…`}
|
||||
showRiskCard={false}
|
||||
enableUpload={false}
|
||||
footer="AI 提供情景推演和利弊分析,最终决策由老板做出。"
|
||||
/>
|
||||
)}
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user