feat: 知识库支持催收文书和合同风险库的结构化预览,新增分类筛选
CI / 后端测试 (push) Has been cancelled
CI / 前端构建 (push) Has been cancelled

This commit is contained in:
selfrelease
2026-09-02 09:29:14 +08:00
parent fbda31b290
commit c1cf41edea
+65 -6
View File
@@ -57,6 +57,8 @@ const DOC_TYPES = [
{ value: 'emotion', label: '情绪疏导', icon: Heart }, { value: 'emotion', label: '情绪疏导', icon: Heart },
{ value: 'life', label: '生活常识', icon: Coffee }, { value: 'life', label: '生活常识', icon: Coffee },
{ value: 'element_template', label: '要素表模板', icon: FileText }, { value: 'element_template', label: '要素表模板', icon: FileText },
{ value: 'debt_collection_template', label: '催收文书', icon: FileText },
{ value: 'contract_risk_clause', label: '合同风险库', icon: FileText },
] ]
/** 来源英文 → 中文映射 */ /** 来源英文 → 中文映射 */
@@ -313,10 +315,13 @@ export function KnowledgeBase() {
<Badge variant="outline">相关度 {doc.score}</Badge> <Badge variant="outline">相关度 {doc.score}</Badge>
)} )}
</div> </div>
{doc.doc_type !== 'element_template' && ( {doc.doc_type !== 'element_template' && doc.doc_type !== 'debt_collection_template' && doc.doc_type !== 'contract_risk_clause' && (
<p className="mt-2 text-sm text-muted-foreground">{doc.content}</p> <p className="mt-2 text-sm text-muted-foreground">{doc.content}</p>
)} )}
{doc.doc_type === 'element_template' && (() => { {(() => {
/** 结构化模板预览:element_template / debt_collection_template / contract_risk_clause */
const STRUCTURED_TYPES = ['element_template', 'debt_collection_template', 'contract_risk_clause']
if (!STRUCTURED_TYPES.includes(doc.doc_type)) return null
try { try {
const tmpl = JSON.parse(doc.content) const tmpl = JSON.parse(doc.content)
const TYPE_LABELS: Record<string, string> = { const TYPE_LABELS: Record<string, string> = {
@@ -328,13 +333,19 @@ export function KnowledgeBase() {
date: '日期', date: '日期',
number: '数字', number: '数字',
} }
// 文书模板(element_template / debt_collection_template)
if (doc.doc_type === 'element_template' || doc.doc_type === 'debt_collection_template') {
const titleField = tmpl.cause_of_action || tmpl.name || tmpl.title
const stageLabel = tmpl.stage ? ` · 阶段${tmpl.stage}:${tmpl.stage_name || ''}` : ''
return ( return (
<div className="mt-2 rounded border bg-muted/30 p-3 text-xs"> <div className="mt-2 rounded border bg-muted/30 p-3 text-xs">
<p className="font-medium">案由:{tmpl.cause_of_action}</p> <p className="font-medium">{doc.doc_type === 'debt_collection_template' ? '文书' : '案由'}:{titleField}{stageLabel}</p>
<p className="mt-0.5 text-muted-foreground">适用:{tmpl.applicable}</p> {tmpl.applicable && <p className="mt-0.5 text-muted-foreground">适用:{tmpl.applicable}</p>}
<p className="mt-2 font-medium">要素清单({tmpl.elements?.length || 0}项)</p> {tmpl.elements?.length > 0 && (
<>
<p className="mt-2 font-medium">要素清单({tmpl.elements.length}项)</p>
<div className="mt-1 grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3"> <div className="mt-1 grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3">
{tmpl.elements?.map((e: Record<string, unknown>, i: number) => ( {tmpl.elements.map((e: Record<string, unknown>, i: number) => (
<div key={i} className="flex items-center justify-between"> <div key={i} className="flex items-center justify-between">
<span className="text-muted-foreground">{e.label}</span> <span className="text-muted-foreground">{e.label}</span>
<span className="font-medium"> <span className="font-medium">
@@ -344,6 +355,8 @@ export function KnowledgeBase() {
</div> </div>
))} ))}
</div> </div>
</>
)}
{tmpl.claims?.length > 0 && ( {tmpl.claims?.length > 0 && (
<> <>
<p className="mt-2 font-medium">诉讼请求</p> <p className="mt-2 font-medium">诉讼请求</p>
@@ -356,6 +369,52 @@ export function KnowledgeBase() {
)} )}
</div> </div>
) )
}
// 合同风险条款库(contract_risk_clause)
if (doc.doc_type === 'contract_risk_clause') {
return (
<div className="mt-2 rounded border bg-muted/30 p-3 text-xs space-y-2">
<p className="font-medium">合同类型:{tmpl.contract_type}</p>
{tmpl.high_risk_clauses?.length > 0 && (
<div>
<p className="font-medium text-red-600">高风险条款({tmpl.high_risk_clauses.length}项)</p>
<div className="mt-1 space-y-1">
{tmpl.high_risk_clauses.map((c: Record<string, string>, i: number) => (
<div key={i} className="rounded bg-red-50 px-2 py-1">
<span className="font-medium">{c.risk}</span>
<span className="text-muted-foreground"> - {c.description}</span>
</div>
))}
</div>
</div>
)}
{tmpl.medium_risk_clauses?.length > 0 && (
<div>
<p className="font-medium text-amber-600">中风险条款({tmpl.medium_risk_clauses.length}项)</p>
<div className="mt-1 space-y-1">
{tmpl.medium_risk_clauses.map((c: Record<string, string>, i: number) => (
<div key={i} className="rounded bg-amber-50 px-2 py-1">
<span className="font-medium">{c.risk}</span>
<span className="text-muted-foreground"> - {c.description}</span>
</div>
))}
</div>
</div>
)}
{tmpl.missing_clauses?.length > 0 && (
<div>
<p className="font-medium">缺失条款提醒</p>
<div className="mt-1 flex flex-wrap gap-1">
{tmpl.missing_clauses.map((c: string, i: number) => (
<Badge key={i} variant="outline" className="text-[10px] text-muted-foreground">{c}</Badge>
))}
</div>
</div>
)}
</div>
)
}
return null
} catch { return null } } catch { return null }
})()} })()}
<div className="mt-2 flex flex-wrap gap-3 text-xs text-muted-foreground"> <div className="mt-2 flex flex-wrap gap-3 text-xs text-muted-foreground">