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
+87 -28
View File
@@ -57,6 +57,8 @@ const DOC_TYPES = [
{ value: 'emotion', label: '情绪疏导', icon: Heart },
{ value: 'life', label: '生活常识', icon: Coffee },
{ 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>
)}
</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>
)}
{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 {
const tmpl = JSON.parse(doc.content)
const TYPE_LABELS: Record<string, string> = {
@@ -328,34 +333,88 @@ export function KnowledgeBase() {
date: '日期',
number: '数字',
}
return (
<div className="mt-2 rounded border bg-muted/30 p-3 text-xs">
<p className="font-medium">案由:{tmpl.cause_of_action}</p>
<p className="mt-0.5 text-muted-foreground">适用:{tmpl.applicable}</p>
<p className="mt-2 font-medium">要素清单({tmpl.elements?.length || 0}项)</p>
<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) => (
<div key={i} className="flex items-center justify-between">
<span className="text-muted-foreground">{e.label}</span>
<span className="font-medium">
{TYPE_LABELS[String(e.type)] || String(e.type)}
{e.required === true || e.required === '是' ? ' ·必填' : ''}
</span>
</div>
))}
// 文书模板(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 (
<div className="mt-2 rounded border bg-muted/30 p-3 text-xs">
<p className="font-medium">{doc.doc_type === 'debt_collection_template' ? '文书' : '案由'}:{titleField}{stageLabel}</p>
{tmpl.applicable && <p className="mt-0.5 text-muted-foreground">适用:{tmpl.applicable}</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">
{tmpl.elements.map((e: Record<string, unknown>, i: number) => (
<div key={i} className="flex items-center justify-between">
<span className="text-muted-foreground">{e.label}</span>
<span className="font-medium">
{TYPE_LABELS[String(e.type)] || String(e.type)}
{e.required === true || e.required === '是' ? ' ·必填' : ''}
</span>
</div>
))}
</div>
</>
)}
{tmpl.claims?.length > 0 && (
<>
<p className="mt-2 font-medium">诉讼请求</p>
<div className="mt-1 flex flex-wrap gap-1">
{tmpl.claims.map((c: string, i: number) => (
<Badge key={i} variant="outline" className="text-[10px]">{c}</Badge>
))}
</div>
</>
)}
</div>
{tmpl.claims?.length > 0 && (
<>
<p className="mt-2 font-medium">诉讼请求</p>
<div className="mt-1 flex flex-wrap gap-1">
{tmpl.claims.map((c: string, i: number) => (
<Badge key={i} variant="outline" className="text-[10px]">{c}</Badge>
))}
)
}
// 合同风险条款库(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>
</>
)}
</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 }
})()}
<div className="mt-2 flex flex-wrap gap-3 text-xs text-muted-foreground">