feat: 话题动态化 + 要素化立案 + 知识库增强
CI / 后端测试 (push) Has been cancelled
CI / 前端构建 (push) Has been cancelled

话题动态化:
- 新增 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:
selfrelease
2026-08-30 15:12:33 +08:00
co-authored by Devin
parent 4e2b4666aa
commit 7a788bf8dc
11 changed files with 2291 additions and 390 deletions
+355 -3
View File
@@ -216,6 +216,7 @@ class AskRequest(BaseModel):
question: str question: str
scene: str = "legal" scene: str = "legal"
emotion_mode: str | None = None emotion_mode: str | None = None
topic: str | None = None
"""用户补充的事实回答(多轮追问后传入)""" """用户补充的事实回答(多轮追问后传入)"""
fact_answers: list[FactAnswer] | None = None fact_answers: list[FactAnswer] | None = None
"""手机号(从 JWT token 解析,用于用户画像)""" """手机号(从 JWT token 解析,用于用户画像)"""
@@ -274,6 +275,119 @@ class SampleRequest(BaseModel):
existing: list[str] | None = None # 已展示的示例,避免重复 existing: list[str] | None = None # 已展示的示例,避免重复
class TopicsRequest(BaseModel):
"""动态话题生成请求。"""
scene: str = "legal"
phone: str | None = None # 用户手机号,用于读取画像个性化推荐
existing: list[str] | None = None # 已展示的话题,避免重复
@app.post("/topics")
def generate_topics(req: TopicsRequest):
"""AI 根据场景 + 用户画像动态生成话题列表。
不再固化话题,每次进入页面时 AI 生成 6-8 个话题,
结合用户画像(行业/规模/常问话题)个性化推荐。
"""
# 内置话题(降级用)
builtin_topics = {
"legal": [
{"title": "劳动用工", "desc": "工伤、辞退、欠薪、社保", "samples": ["员工工伤后能否开除?", "拖欠工资怎么追讨?", "未签合同双倍工资怎么算?", "社保没缴全怎么补?"]},
{"title": "合同审查", "desc": "违约、条款、效力", "samples": ["合同违约金怎么约定?", "对方不履行合同怎么办?", "口头协议有法律效力吗?", "合同条款不公平能改吗?"]},
{"title": "债权债务", "desc": "欠款、催收、诉讼", "samples": ["客户拖欠货款3个月如何催收?", "借条怎么写才有效?", "债务人不还钱能报警吗?", "超过诉讼时效怎么办?"]},
{"title": "股东纠纷", "desc": "股权、分红、退股", "samples": ["合伙人要退股怎么谈?", "股东不出资怎么办?", "分红比例怎么定?", "小股东权益被侵害怎么办?"]},
{"title": "税务风险", "desc": "稽查、发票、合规", "samples": ["收到税务稽查通知怎么办?", "发票开错了怎么处理?", "个体户怎么交税最合理?", "税务罚款能减免吗?"]},
{"title": "诉讼仲裁", "desc": "起诉、举证、执行", "samples": ["打官司要准备哪些证据?", "仲裁和诉讼哪个更快?", "赢了官司对方不执行怎么办?", "诉讼费用怎么算?"]},
],
"decision": [
{"title": "扩张收缩", "desc": "开店、关店、业务线", "samples": ["现在适合扩张开第二家店吗?", "要不要关闭亏损的业务线?", "新业务线该自营还是加盟?", "应该先扩张还是先巩固?"]},
{"title": "项目承接", "desc": "大单、长期合同", "samples": ["是否应该接付款周期90天的大单?", "长期框架合同要不要签?", "预付款比例低的项目能接吗?", "客户要求独家合作利弊?"]},
{"title": "定价策略", "desc": "涨价、降价、促销", "samples": ["该不该降价15%抢竞品客户?", "产品涨价怎么跟客户沟通?", "促销活动会不会损害品牌?", "成本上涨如何调整定价?"]},
{"title": "招人裁员", "desc": "招聘、解雇、绩效", "samples": ["现在该招人还是裁人?", "裁掉绩效差的员工怎么操作?", "关键岗位招人还是培养?", "外包和正式员工怎么选?"]},
{"title": "融资合伙", "desc": "融资、合伙、股权", "samples": ["现在适合融资吗?", "合伙人要退股怎么谈条件?", "出让多少股权合适?", "银行贷款还是股权融资?"]},
{"title": "转型外包", "desc": "转型、外包、调整", "samples": ["业务转型方向怎么选?", "哪些业务可以外包?", "转型期间现金流怎么管理?", "外包质量怎么控?"]},
],
"life": [
{"title": "家庭沟通", "desc": "亲子、夫妻、关系", "samples": ["怎么和青春期的孩子沟通?", "夫妻因工作吵架怎么办?", "家人不支持创业怎么说服?", "工作太忙忽略家人怎么办?"]},
{"title": "子女教育", "desc": "升学、兴趣、规划", "samples": ["孩子不想上学怎么办?", "兴趣班该报几个?", "要不要送孩子出国?", "孩子成绩下滑怎么沟通?"]},
{"title": "父母养老", "desc": "养老、医疗、陪伴", "samples": ["父母年纪大了养老方案怎么选?", "异地父母怎么照顾?", "养老院还是居家养老?", "父母医疗费用怎么分担?"]},
{"title": "睡眠与压力", "desc": "失眠、焦虑、调节", "samples": ["最近睡眠很差怎么改善?", "工作压力大怎么缓解?", "焦虑到影响工作怎么办?", "怎么判断需要看心理医生?"]},
{"title": "出差日程", "desc": "出行、时间管理", "samples": ["出差频繁怎么兼顾家庭?", "行程太满怎么优化?", "出差期间公司怎么管?", "如何减少无效出差?"]},
{"title": "健康习惯", "desc": "体检、饮食、运动", "samples": ["体检报告几项异常怎么办?", "久坐怎么改善健康?", "应酬多怎么控制饮食?", "没时间运动怎么办?"]},
],
"emotion": [
{"title": "冷静分析", "desc": "理性拆解问题", "samples": ["最近压力很大睡不着觉", "合伙人背叛了我很愤怒", "明天的谈判我拖延了一周没准备", "感觉撑不下去了"]},
{"title": "陪伴疏导", "desc": "倾听与共情", "samples": ["今天和合伙人吵了一架", "员工集体离职我很受挫", "家里人不理解我的压力", "感觉自己很孤独"]},
{"title": "强力推动", "desc": "直面问题推动行动", "samples": ["我知道该怎么做但就是拖延", "害怕做决定怎么办?", "明知道该辞退但下不了手", "想改变但总是回到老路"]},
],
}
if not kimi:
return {"topics": builtin_topics.get(req.scene, builtin_topics["legal"])}
# 读取用户画像,个性化话题
profile_ctx = ""
if req.phone:
try:
profile = profile_store.get(req.phone)
profile_ctx = build_profile_context(profile)
except Exception:
pass
scene_desc = {
"legal": "法律/法务问题,如劳动用工、合同审查、债权债务、股东纠纷、税务风险、诉讼仲裁、知识产权、交通事故、婚姻财产、房产纠纷、消费维权等",
"decision": "经营决策问题,如扩张收缩、定价策略、招人裁员、融资合伙、项目承接、业务转型、投资理财、风险管理等",
"life": "个人生活问题,如家庭沟通、子女教育、父母养老、健康习惯、时间管理、出行规划等(不是企业经营问题)",
"emotion": "情绪与心理问题,如压力疏导、关系困惑、自我认知、传统文化反思等",
}
existing_str = ""
if req.existing:
existing_str = f"\n已展示过的话题(避免重复):{','.join(req.existing)}"
prompt = f"""你是左右(BossAi)的{scene_desc.get(req.scene, scene_desc['legal'])}助手。
请生成6-8个话题,每个话题包含标题(2-4字)、简短描述(10-15字)、4个示例问题。
{profile_ctx}
要求:
1. 话题要覆盖该场景下不同方向,不要只集中在某一类
2. 结合用户画像(如有)推荐个性化话题
3. 示例问题要具体、贴近中小企业老板实际
4. 返回JSON数组格式:[{{"title":"话题名","desc":"简短描述","samples":["问题1","问题2","问题3","问题4"]}}]
5. 只返回JSON,不要其他文字{existing_str}"""
try:
answer = kimi.chat([{"role": "user", "content": prompt}], temperature=0.8)
answer = answer.strip()
if answer.startswith("```"):
answer = answer.split("\n", 1)[1] if "\n" in answer else answer[3:]
if answer.endswith("```"):
answer = answer[:-3]
answer = answer.strip().replace("\u201c", '"').replace("\u201d", '"')
start = answer.find("[")
end = answer.rfind("]")
if start == -1 or end == -1:
return {"topics": builtin_topics.get(req.scene, builtin_topics["legal"])}
topics = json.loads(answer[start:end + 1])
# 校验结构
valid_topics = []
for t in topics:
if isinstance(t, dict) and t.get("title") and t.get("samples"):
valid_topics.append({
"title": str(t["title"])[:10],
"desc": str(t.get("desc", ""))[:30],
"samples": [str(s) for s in t["samples"] if isinstance(s, str) and s.strip()][:4],
})
if len(valid_topics) < 3:
return {"topics": builtin_topics.get(req.scene, builtin_topics["legal"])}
return {"topics": valid_topics[:8]}
except Exception as e:
import logging
logging.warning(f"/topics AI 生成失败: {type(e).__name__}: {e}")
return {"topics": builtin_topics.get(req.scene, builtin_topics["legal"])}
@app.post("/samples") @app.post("/samples")
def generate_samples(req: SampleRequest): def generate_samples(req: SampleRequest):
"""用 AI 生成场景示例问题,供前端"换一批"使用。 """用 AI 生成场景示例问题,供前端"换一批"使用。
@@ -493,23 +607,102 @@ def ask_analyze(sid: str, req: AskRequest, authorization: str | None = Header(de
req.question, req.scene, answers, kimi_client=kimi, req.question, req.scene, answers, kimi_client=kimi,
) )
# 收集用户画像中的已知事实(带入内容) # 收集用户画像中的已知事实(带入内容),按场景过滤相关性
profile_facts: list[dict] = [] profile_facts: list[dict] = []
if req.phone: if req.phone:
try: try:
profile = profile_store.get(req.phone) profile = profile_store.get(req.phone)
# 标准画像字段:所有场景通用
if profile.industry: if profile.industry:
profile_facts.append({"dim_key": "industry", "dim_desc": "行业", "answer": profile.industry}) profile_facts.append({"dim_key": "industry", "dim_desc": "行业", "answer": profile.industry})
if profile.company_size: if profile.company_size:
profile_facts.append({"dim_key": "company_size", "dim_desc": "企业规模", "answer": profile.company_size}) profile_facts.append({"dim_key": "company_size", "dim_desc": "企业规模", "answer": profile.company_size})
if profile.role: if profile.role:
profile_facts.append({"dim_key": "role", "dim_desc": "角色", "answer": profile.role}) profile_facts.append({"dim_key": "role", "dim_desc": "角色", "answer": profile.role})
# 风险偏好和决策风格仅经营决策场景带入
if req.scene == "decision":
if profile.risk_tolerance: if profile.risk_tolerance:
profile_facts.append({"dim_key": "risk_tolerance", "dim_desc": "风险偏好", "answer": profile.risk_tolerance}) profile_facts.append({"dim_key": "risk_tolerance", "dim_desc": "风险偏好", "answer": profile.risk_tolerance})
if profile.decision_style: if profile.decision_style:
profile_facts.append({"dim_key": "decision_style", "dim_desc": "决策风格", "answer": profile.decision_style}) profile_facts.append({"dim_key": "decision_style", "dim_desc": "决策风格", "answer": profile.decision_style})
# key_facts:按话题相关性过滤(更精准)
if profile.key_facts: if profile.key_facts:
# 话题 → 相关 key_facts 关键词映射
TOPIC_FACT_KEYWORDS = {
# ===== 法务参谋 =====
"劳动用工": {"region", "company_type", "team_ability", "communication",
"medical_proof", "contract_terms", "medical_period",
"injury_situation", "work_injury_details", "work_injury_insurance",
"performance_assessment", "relevant_legal_provisions",
"employee_count", "salary_status", "social_security", "severance",
"working_years", "probation_period", "termination_reason",
"overtime_status"},
"合同审查": {"contract_terms", "contract_dispute", "company_type",
"relevant_legal_provisions", "region", "partnership"},
"债权债务": {"debt_amount", "litigation_status", "financial_status",
"company_type", "relevant_legal_provisions"},
"股东纠纷": {"shareholder_structure", "partnership", "company_type",
"litigation_status", "relevant_legal_provisions"},
"税务风险": {"tax_risk", "financial_status", "company_type",
"relevant_legal_provisions"},
# ===== 经营决策 =====
"扩张收缩": {"market_potential", "financial_status", "team_ability",
"region", "company_type", "expansion_plan", "competitor",
"cash_flow", "alternative_option", "alternative_solution"},
"项目承接": {"financial_status", "cash_flow", "team_ability",
"contract_terms", "customer_concentration", "alternative_option"},
"招人用人": {"team_ability", "employee_count", "salary_status",
"social_security", "company_type", "region"},
"降价定价": {"financial_status", "competitor", "customer_concentration",
"supply_chain", "cash_flow"},
"业务线调整": {"market_potential", "financial_status", "competitor",
"alternative_option", "alternative_solution", "supply_chain",
"business_model"},
"融资合伙": {"funding_stage", "financial_status", "shareholder_structure",
"partnership", "company_type", "cash_flow", "asset_status",
"liability", "alternative_option"},
# ===== 生活助理 =====
"健康管理": {"health_status", "region"},
"家庭事务": {"family_situation", "region", "company_type"},
"出行规划": {"travel_plan", "region"},
"子女教育": {"education_background", "family_situation"},
"消费理财": {"financial_status", "revenue", "asset_status", "liability"},
# ===== 情绪中心 =====
"情绪倾诉": {"communication_style", "stress_source", "personality",
"family_situation"},
"压力疏导": {"stress_source", "communication_style", "personality"},
"关系困惑": {"communication_style", "family_situation", "personality"},
"传统文化": {"personality", "communication_style"},
}
# 先按话题匹配,匹配不到则按场景兜底
topic_keys = TOPIC_FACT_KEYWORDS.get(req.topic or "", set())
if not topic_keys:
# 场景兜底关键词
SCENE_FACT_KEYWORDS = {
"legal": {"region", "company_type", "team_ability", "communication",
"medical_proof", "contract_terms", "medical_period",
"injury_situation", "legal_procedures", "work_injury_details",
"work_injury_insurance", "performance_assessment",
"relevant_legal_provisions", "employee_count", "salary_status",
"social_security", "severance", "working_years", "probation_period",
"termination_reason", "overtime_status", "contract_dispute",
"debt_amount", "tax_risk", "litigation_status", "shareholder_structure",
"partnership"},
"decision": {"market_potential", "alternative_option", "alternative_solution",
"financial_status", "revenue", "profit_status", "cash_flow",
"asset_status", "liability", "business_model", "funding_stage",
"expansion_plan", "competitor", "customer_concentration",
"supply_chain", "export_capability", "team_ability",
"region", "company_type"},
"life": {"region", "company_type", "employee_count", "revenue",
"family_situation", "health_status", "travel_plan",
"important_event", "education_background"},
"emotion": {"communication_style", "stress_source", "personality",
"decision_style", "family_situation"},
}
topic_keys = SCENE_FACT_KEYWORDS.get(req.scene, set())
for k, v in profile.key_facts.items(): for k, v in profile.key_facts.items():
if k.lower() in topic_keys or k in topic_keys:
profile_facts.append({"dim_key": f"fact_{k}", "dim_desc": k, "answer": str(v)}) profile_facts.append({"dim_key": f"fact_{k}", "dim_desc": k, "answer": str(v)})
except Exception: except Exception:
pass pass
@@ -526,6 +719,34 @@ def ask_analyze(sid: str, req: AskRequest, authorization: str | None = Header(de
"profile_facts": profile_facts, "profile_facts": profile_facts,
} }
else: else:
# 事实充分,法务场景尝试匹配要素表模板
element_template = None
if req.scene == "legal" and kimi:
try:
# 从知识库检索要素表模板
docs = knowledge.search(req.question, limit=3, doc_type="element_template")
if docs:
# AI 判断最匹配的案由
cause_names = [json.loads(d.get("content", "{}")).get("cause_of_action", d.get("title", "")) for d in docs]
prompt = f"""用户问题:{req.question}
事实摘要:{meta.get("fact_summary", "")}
候选案由:{json.dumps(cause_names, ensure_ascii=False)}
请判断用户的问题最匹配哪个案由,只返回案由名称,不要其他文字。"""
matched_cause = kimi.chat([{"role": "user", "content": prompt}], temperature=0.1).strip()
# 找到匹配的模板
for d in docs:
tmpl = json.loads(d.get("content", "{}"))
if tmpl.get("cause_of_action") == matched_cause or matched_cause in d.get("title", ""):
element_template = tmpl
break
# 没精确匹配就用第一个
if not element_template and docs:
element_template = json.loads(docs[0].get("content", "{}"))
except Exception:
pass
return { return {
"need_ask": False, "need_ask": False,
"need_analysis": True, "need_analysis": True,
@@ -534,6 +755,124 @@ def ask_analyze(sid: str, req: AskRequest, authorization: str | None = Header(de
"question_type": meta["analysis"]["question_type"], "question_type": meta["analysis"]["question_type"],
"risk_level": card.level, "risk_level": card.level,
"profile_facts": profile_facts, "profile_facts": profile_facts,
"element_template": element_template,
}
class ElementFilingRequest(BaseModel):
"""要素化立案生成请求。"""
question: str
cause_of_action: str # 案由
fact_answers: list[FactAnswer] = [] # 用户确认的事实
template: dict | None = None # 要素表模板(前端传回)
@app.post("/sessions/{sid}/element-filing")
def generate_element_filing(sid: str, req: ElementFilingRequest, authorization: str | None = Header(default=None)):
"""根据确认的事实 + 要素表模板,AI 生成要素式起诉状。
流程:
1. 用户确认事实后调用此端点
2. AI 根据案由模板 + 确认事实,填充要素
3. 返回结构化要素式起诉状
Returns:
{
"cause_of_action": str, # 案由
"elements": [...], # 已填充的要素列表
"claims": [...], # 诉讼请求
"filing_text": str, # 可导出的要素式起诉状文本
}
"""
try:
sessions._active(sid)
except KeyError as e:
raise HTTPException(410, str(e))
if not kimi:
raise HTTPException(503, "AI 服务不可用")
# 获取要素表模板
tmpl = req.template
if not tmpl:
# 从知识库检索
docs = knowledge.search(req.cause_of_action, limit=1, doc_type="element_template")
if docs:
tmpl = json.loads(docs[0].get("content", "{}"))
if not tmpl:
raise HTTPException(404, f"未找到案由'{req.cause_of_action}'的要素表模板")
# 构造事实摘要
facts_str = "\n".join([f"- {a.dim_desc}: {a.answer}" for a in req.fact_answers])
# AI 填充要素
elements_json = json.dumps(tmpl.get("elements", []), ensure_ascii=False)
prompt = f"""你是法律文书助手。根据用户描述和确认的事实,填充要素式起诉状的各要素。
案由:{tmpl.get("cause_of_action", req.cause_of_action)}
要素模板:{elements_json}
用户问题:{req.question}
确认事实:
{facts_str}
请根据以上信息,为每个要素填充具体值。如果信息不足,填写"待补充"。
返回JSON数组格式:[{{"key":"要素key","label":"要素标签","value":"填充值"}}]
只返回JSON,不要其他文字。"""
try:
answer = kimi.chat([{"role": "user", "content": prompt}], temperature=0.1)
answer = answer.strip()
if answer.startswith("```"):
answer = answer.split("\n", 1)[1] if "\n" in answer else answer[3:]
if answer.endswith("```"):
answer = answer[:-3]
answer = answer.strip().replace("\u201c", '"').replace("\u201d", '"')
start = answer.find("[")
end = answer.rfind("]")
if start == -1 or end == -1:
raise ValueError("AI 返回格式错误")
filled_elements = json.loads(answer[start:end + 1])
except Exception as e:
raise HTTPException(500, f"要素填充失败: {e}")
# 生成可导出的要素式起诉状文本
cause = tmpl.get("cause_of_action", req.cause_of_action)
claims = tmpl.get("claims", [])
claims_str = "\n".join([f" ☐ {c}" for c in claims])
elements_text = "\n".join([
f" {e.get('label', e.get('key', ''))}: {e.get('value', '待补充')}"
for e in filled_elements
])
filing_text = f"""要素式起诉状
案由:{cause}
一、当事人信息
原告:待填写
被告:待填写
二、诉讼请求
{claims_str}
三、事实和理由(要素表)
{elements_text}
四、证据清单
待补充
五、对纠纷解决方式的意愿
☐ 同意调解 ☐ 不同意调解
说明:本起诉状依据最高人民法院《部分案件起诉状答辩状示范文本》(法〔2025〕82号)生成,
所填要素供参考,请根据实际情况修改后提交法院。"""
return {
"cause_of_action": cause,
"elements": filled_elements,
"claims": claims,
"filing_text": filing_text,
} }
@@ -751,8 +1090,21 @@ def list_summaries(limit: int = 20):
@app.get("/knowledge") @app.get("/knowledge")
def list_knowledge(doc_type: str | None = None, limit: int = 50): def list_knowledge(doc_type: str | None = None, limit: int = 50):
"""列出知识库文档,支持按类型筛选。""" """列出知识库文档,支持按类型筛选。返回各类型计数供前端展示。"""
return {"results": knowledge.list_all(doc_type=doc_type, limit=limit)} results = knowledge.list_all(doc_type=doc_type, limit=limit)
# 统计各类型数量
counts: dict[str, int] = {}
try:
with knowledge._conn() as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT doc_type, COUNT(*) FROM knowledge_docs GROUP BY doc_type"
)
counts = {row[0]: row[1] for row in cur.fetchall()}
except Exception:
pass
total = sum(counts.values())
return {"results": results, "counts": counts, "total": total}
@app.post("/knowledge") @app.post("/knowledge")
+89
View File
@@ -0,0 +1,89 @@
"""将知识库中要素表模板的英文字段值全部转为中文。
转换内容:
- type: text→文本, textarea→长文本, date→日期, number→数字, select→选择, boolean→是否
- required: true→是, false→否
- doc_type 展示标签: element_template→要素表模板(仅展示用,存储值不变以兼容检索)
"""
import json
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from bossai import KnowledgeStore
TYPE_MAP = {
"text": "文本",
"textarea": "长文本",
"date": "日期",
"number": "数字",
"select": "选择",
"boolean": "是否",
}
REQUIRED_MAP = {
True: "是",
False: "否",
}
def convert_template(tmpl: dict) -> dict:
"""递归转换模板中的英文值为中文。"""
# 转换 elements 中的 type 和 required
elements = tmpl.get("elements", [])
for e in elements:
if "type" in e and e["type"] in TYPE_MAP:
e["type"] = TYPE_MAP[e["type"]]
if "required" in e:
val = e["required"]
if val in REQUIRED_MAP:
e["required"] = REQUIRED_MAP[val]
elif isinstance(val, str) and val.lower() == "true":
e["required"] = "是"
elif isinstance(val, str) and val.lower() == "false":
e["required"] = "否"
return tmpl
def main():
store = KnowledgeStore()
with store._conn() as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT id, title, content FROM knowledge_docs WHERE doc_type='element_template' ORDER BY id"
)
rows = cur.fetchall()
print(f"找到 {len(rows)} 条要素表模板,开始转换...")
updated = 0
for doc_id, title, content in rows:
try:
tmpl = json.loads(content)
tmpl = convert_template(tmpl)
new_content = json.dumps(tmpl, ensure_ascii=False, indent=2)
if new_content != content:
cur.execute(
"UPDATE knowledge_docs SET content=%s WHERE id=%s",
(new_content, doc_id),
)
updated += 1
except Exception as e:
print(f" 跳过 {title}: {e}")
print(f"已转换 {updated}/{len(rows)} 条记录")
# 验证
with store._conn() as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT content FROM knowledge_docs WHERE doc_type='element_template' LIMIT 1"
)
row = cur.fetchone()
if row:
tmpl = json.loads(row[0])
print(f"\n验证示例({tmpl.get('cause_of_action', '')}):")
for e in tmpl.get("elements", [])[:3]:
print(f" {e.get('label')}: type={e.get('type')}, required={e.get('required')}")
if __name__ == "__main__":
main()
+372
View File
@@ -0,0 +1,372 @@
"""最高法67类要素式起诉状模板 - 种子数据导入。
将最高法2025年7月14日全面推广的67类要素式起诉状、答辩状示范文本
导入知识库(doc_type='element_template'),供法务参谋"要素化立案"使用。
每类模板包含:
- 案由名称
- 适用场景
- 要素清单(结构化字段)
- 诉讼请求选项
- 事实和理由要素
数据来源:最高人民法院、司法部、中华全国律师协会联合印发
《部分案件起诉状答辩状示范文本》(法〔2025〕82号)
"""
import json
import os
import sys
# 添加项目根目录到 path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from bossai import KnowledgeStore
# ===== 67类要素表模板(按领域分组) =====
ELEMENT_TEMPLATES = [
# ===== 民事类(9类) =====
{
"title": "离婚纠纷要素式起诉状",
"category": "民事",
"cause_of_action": "离婚纠纷",
"applicable": "夫妻一方起诉离婚,涉及子女抚养、财产分割、债务承担等",
"elements": [
{"key": "marriage_date", "label": "结婚登记日期", "type": "date", "required": True},
{"key": "marriage_place", "label": "结婚登记地点", "type": "text", "required": True},
{"key": "children_info", "label": "子女情况(姓名/年龄/性别)", "type": "text", "required": False},
{"key": "separation_date", "label": "分居日期", "type": "date", "required": False},
{"key": "divorce_reason", "label": "离婚原因", "type": "select", "options": ["感情不和", "家庭暴力", "出轨", "赌博吸毒", "分居满两年", "其他"], "required": True},
{"key": "property_dispute", "label": "是否有财产争议", "type": "boolean", "required": True},
{"key": "property_detail", "label": "财产情况(房产/车辆/存款等)", "type": "textarea", "required": False},
{"key": "debt_detail", "label": "债务情况", "type": "textarea", "required": False},
{"key": "child_support_claim", "label": "子女抚养诉求", "type": "textarea", "required": False},
{"key": "property_claim", "label": "财产分割诉求", "type": "textarea", "required": False},
],
"claims": ["判决离婚", "子女抚养权", "抚养费", "财产分割", "债务承担"],
},
{
"title": "买卖合同纠纷要素式起诉状",
"category": "民事",
"cause_of_action": "买卖合同纠纷",
"applicable": "商品买卖中拖欠货款、质量争议、交货延迟等",
"elements": [
{"key": "contract_date", "label": "合同签订日期", "type": "date", "required": True},
{"key": "contract_parties", "label": "合同双方", "type": "text", "required": True},
{"key": "goods_desc", "label": "买卖标的(商品名称/规格/数量)", "type": "textarea", "required": True},
{"key": "total_amount", "label": "合同总金额", "type": "number", "required": True},
{"key": "paid_amount", "label": "已付金额", "type": "number", "required": True},
{"key": "unpaid_amount", "label": "欠付金额", "type": "number", "required": True},
{"key": "delivery_status", "label": "交货情况", "type": "select", "options": ["已全部交货", "部分交货", "未交货", "对方拒收"], "required": True},
{"key": "quality_issue", "label": "是否有质量问题", "type": "boolean", "required": True},
{"key": "breach_detail", "label": "违约情况描述", "type": "textarea", "required": True},
{"key": "evidence_list", "label": "证据清单", "type": "textarea", "required": False},
{"key": "arbitration_clause", "label": "是否有仲裁/管辖约定", "type": "boolean", "required": True},
],
"claims": ["支付货款", "支付违约金", "解除合同", "赔偿损失", "继续履行"],
},
{
"title": "民间借贷纠纷要素式起诉状",
"category": "民事",
"cause_of_action": "民间借贷纠纷",
"applicable": "个人或企业间借贷,借款人未按约还款",
"elements": [
{"key": "loan_date", "label": "借款日期", "type": "date", "required": True},
{"key": "loan_amount", "label": "借款金额", "type": "number", "required": True},
{"key": "interest_rate", "label": "约定利率", "type": "text", "required": False},
{"key": "loan_term", "label": "借款期限", "type": "text", "required": True},
{"key": "repayment_date", "label": "约定还款日期", "type": "date", "required": True},
{"key": "repaid_amount", "label": "已还金额", "type": "number", "required": True},
{"key": "unpaid_amount", "label": "未还金额", "type": "number", "required": True},
{"key": "has_iou", "label": "是否有借条/欠条", "type": "boolean", "required": True},
{"key": "transfer_proof", "label": "是否有转账记录", "type": "boolean", "required": True},
{"key": "guarantor", "label": "是否有担保人", "type": "boolean", "required": False},
{"key": "guarantor_info", "label": "担保人信息", "type": "text", "required": False},
{"key": "collection_record", "label": "催款记录", "type": "textarea", "required": False},
],
"claims": ["偿还借款本金", "支付利息", "支付逾期利息", "担保人承担连带责任"],
},
{
"title": "劳动争议纠纷要素式起诉状",
"category": "民事",
"cause_of_action": "劳动争议纠纷",
"applicable": "劳动者与用人单位之间的劳动合同、工资、工伤、解除等争议",
"elements": [
{"key": "employment_start", "label": "入职日期", "type": "date", "required": True},
{"key": "employment_end", "label": "离职/解除日期", "type": "date", "required": False},
{"key": "position", "label": "岗位/职务", "type": "text", "required": True},
{"key": "salary_standard", "label": "工资标准(月薪)", "type": "number", "required": True},
{"key": "salary_payment", "label": "工资发放方式", "type": "select", "options": ["银行转账", "现金", "微信/支付宝", "混合"], "required": True},
{"key": "has_contract", "label": "是否签订书面劳动合同", "type": "boolean", "required": True},
{"key": "has_social_security", "label": "是否缴纳社保", "type": "boolean", "required": True},
{"key": "dispute_type", "label": "争议类型", "type": "select", "options": ["拖欠工资", "未签合同双倍工资", "违法解除赔偿金", "经济补偿金", "工伤待遇", "加班费", "未缴社保损失", "竞业限制"], "required": True},
{"key": "arbitration_result", "label": "仲裁前置情况(案号/结果)", "type": "textarea", "required": True},
{"key": "claim_amount", "label": "诉求金额", "type": "number", "required": True},
{"key": "claim_detail", "label": "诉求明细", "type": "textarea", "required": True},
{"key": "evidence_list", "label": "证据清单(合同/工资条/考勤/聊天记录等)", "type": "textarea", "required": False},
],
"claims": ["支付拖欠工资", "支付经济补偿金", "支付违法解除赔偿金", "支付未签合同双倍工资差额", "支付加班费", "补缴社保", "工伤待遇赔偿"],
},
{
"title": "机动车交通事故责任纠纷要素式起诉状",
"category": "民事",
"cause_of_action": "机动车交通事故责任纠纷",
"applicable": "机动车交通事故造成人身伤亡或财产损失",
"elements": [
{"key": "accident_date", "label": "事故发生日期", "type": "date", "required": True},
{"key": "accident_location", "label": "事故地点", "type": "text", "required": True},
{"key": "accident_responsibility", "label": "事故责任认定", "type": "select", "options": ["全责", "主要责任", "同等责任", "次要责任", "无责任"], "required": True},
{"key": "has_injury", "label": "是否有人身伤害", "type": "boolean", "required": True},
{"key": "injury_detail", "label": "伤情描述", "type": "textarea", "required": False},
{"key": "hospital_days", "label": "住院天数", "type": "number", "required": False},
{"key": "disability_level", "label": "伤残等级(如有)", "type": "text", "required": False},
{"key": "vehicle_damage", "label": "车辆损失金额", "type": "number", "required": False},
{"key": "medical_expense", "label": "医疗费", "type": "number", "required": False},
{"key": "lost_income", "label": "误工费", "type": "number", "required": False},
{"key": "nursing_fee", "label": "护理费", "type": "number", "required": False},
{"key": "nutrition_fee", "label": "营养费", "type": "number", "required": False},
{"key": "transport_fee", "label": "交通费", "type": "number", "required": False},
{"key": "spiritual_compensation", "label": "精神损害抚慰金", "type": "number", "required": False},
{"key": "insurance_type", "label": "对方保险类型", "type": "select", "options": ["交强险", "商业三者险", "无保险", "不详"], "required": True},
{"key": "has_police_report", "label": "是否有交警事故认定书", "type": "boolean", "required": True},
],
"claims": ["医疗费", "误工费", "护理费", "营养费", "住院伙食补助费", "交通费", "残疾赔偿金", "精神损害抚慰金", "车辆维修费", "财产损失赔偿"],
},
{
"title": "房屋买卖合同纠纷要素式起诉状",
"category": "民事",
"cause_of_action": "房屋买卖合同纠纷",
"applicable": "房屋买卖中逾期交房、质量问题、一房多卖、过户争议等",
"elements": [
{"key": "contract_date", "label": "合同签订日期", "type": "date", "required": True},
{"key": "property_info", "label": "房屋信息(地址/面积/类型)", "type": "textarea", "required": True},
{"key": "total_price", "label": "房屋总价", "type": "number", "required": True},
{"key": "paid_amount", "label": "已付金额", "type": "number", "required": True},
{"key": "payment_method", "label": "付款方式", "type": "select", "options": ["全款", "按揭贷款", "分期"], "required": True},
{"key": "delivery_date", "label": "约定交房日期", "type": "date", "required": True},
{"key": "actual_delivery", "label": "实际交房日期", "type": "date", "required": False},
{"key": "transfer_date", "label": "约定过户日期", "type": "date", "required": False},
{"key": "dispute_type", "label": "争议类型", "type": "select", "options": ["逾期交房", "质量问题", "一房多卖", "逾期过户", "面积差异", "合同解除"], "required": True},
{"key": "breach_detail", "label": "违约情况描述", "type": "textarea", "required": True},
],
"claims": ["继续履行合同", "解除合同", "退还房款", "支付违约金", "赔偿损失", "协助过户"],
},
{
"title": "房屋租赁合同纠纷要素式起诉状",
"category": "民事",
"cause_of_action": "房屋租赁合同纠纷",
"applicable": "房屋租赁中拖欠租金、提前退租、押金退还、维修责任等",
"elements": [
{"key": "lease_start", "label": "租赁起始日期", "type": "date", "required": True},
{"key": "lease_end", "label": "租赁终止日期", "type": "date", "required": True},
{"key": "monthly_rent", "label": "月租金", "type": "number", "required": True},
{"key": "deposit", "label": "押金金额", "type": "number", "required": True},
{"key": "paid_months", "label": "已付租金月数", "type": "number", "required": True},
{"key": "unpaid_rent", "label": "拖欠租金金额", "type": "number", "required": False},
{"key": "dispute_type", "label": "争议类型", "type": "select", "options": ["拖欠租金", "提前退租", "押金不退", "房屋损坏", "擅自转租", "维修责任"], "required": True},
{"key": "breach_detail", "label": "违约情况描述", "type": "textarea", "required": True},
],
"claims": ["支付拖欠租金", "支付违约金", "解除租赁合同", "退还押金", "赔偿房屋损坏", "腾退房屋"],
},
{
"title": "建设工程施工合同纠纷要素式起诉状",
"category": "民事",
"cause_of_action": "建设工程施工合同纠纷",
"applicable": "工程建设中工程款拖欠、质量问题、工期延误等",
"elements": [
{"key": "contract_date", "label": "合同签订日期", "type": "date", "required": True},
{"key": "project_name", "label": "工程名称", "type": "text", "required": True},
{"key": "project_location", "label": "工程地点", "type": "text", "required": True},
{"key": "contract_amount", "label": "合同金额", "type": "number", "required": True},
{"key": "completed_amount", "label": "已完成工程量金额", "type": "number", "required": True},
{"key": "paid_amount", "label": "已付工程款", "type": "number", "required": True},
{"key": "unpaid_amount", "label": "欠付工程款", "type": "number", "required": True},
{"key": "completion_date", "label": "约定竣工日期", "type": "date", "required": True},
{"key": "actual_completion", "label": "实际竣工日期", "type": "date", "required": False},
{"key": "has_acceptance", "label": "是否已验收", "type": "boolean", "required": True},
{"key": "quality_issue", "label": "是否有质量问题", "type": "boolean", "required": True},
{"key": "dispute_type", "label": "争议类型", "type": "select", "options": ["拖欠工程款", "工期延误", "质量问题", "合同解除", "结算争议"], "required": True},
],
"claims": ["支付工程款", "支付逾期付款利息", "赔偿工期延误损失", "修复质量问题", "确认工程价款优先受偿权"],
},
{
"title": "物业服务合同纠纷要素式起诉状",
"category": "民事",
"cause_of_action": "物业服务合同纠纷",
"applicable": "业主与物业公司之间的物业费、服务质量等争议",
"elements": [
{"key": "property_address", "label": "房屋地址", "type": "text", "required": True},
{"key": "property_area", "label": "房屋面积", "type": "number", "required": True},
{"key": "fee_standard", "label": "物业费标准(元/平米/月)", "type": "number", "required": True},
{"key": "unpaid_period", "label": "欠费期间", "type": "text", "required": True},
{"key": "unpaid_amount", "label": "欠费金额", "type": "number", "required": True},
{"key": "service_issue", "label": "物业服务问题", "type": "textarea", "required": False},
{"key": "complaint_record", "label": "投诉记录", "type": "textarea", "required": False},
],
"claims": ["支付物业费", "支付滞纳金", "改善服务质量", "赔偿损失"],
},
# ===== 商事类(8类) =====
{
"title": "金融借款合同纠纷要素式起诉状",
"category": "商事",
"cause_of_action": "金融借款合同纠纷",
"applicable": "银行等金融机构借款逾期、担保追偿等",
"elements": [
{"key": "loan_date", "label": "借款日期", "type": "date", "required": True},
{"key": "loan_amount", "label": "借款金额", "type": "number", "required": True},
{"key": "interest_rate", "label": "借款利率", "type": "text", "required": True},
{"key": "loan_term", "label": "借款期限", "type": "text", "required": True},
{"key": "repayment_method", "label": "还款方式", "type": "select", "options": ["等额本息", "等额本金", "到期还本付息", "按月付息到期还本"], "required": True},
{"key": "paid_amount", "label": "已还本金", "type": "number", "required": True},
{"key": "unpaid_principal", "label": "欠付本金", "type": "number", "required": True},
{"key": "unpaid_interest", "label": "欠付利息", "type": "number", "required": True},
{"key": "has_mortgage", "label": "是否有抵押", "type": "boolean", "required": True},
{"key": "mortgage_detail", "label": "抵押物情况", "type": "textarea", "required": False},
{"key": "has_guarantor", "label": "是否有保证人", "type": "boolean", "required": True},
{"key": "guarantee_type", "label": "保证方式", "type": "select", "options": ["连带责任保证", "一般保证", "无"], "required": False},
],
"claims": ["偿还借款本金", "支付利息及罚息", "实现抵押权", "保证人承担连带清偿责任"],
},
{
"title": "信用卡纠纷要素式起诉状",
"category": "商事",
"cause_of_action": "信用卡纠纷",
"applicable": "信用卡透支逾期、盗刷争议等",
"elements": [
{"key": "card_type", "label": "信用卡类型", "type": "select", "options": ["贷记卡", "准贷记卡"], "required": True},
{"key": "credit_limit", "label": "信用额度", "type": "number", "required": True},
{"key": "overdue_start", "label": "逾期起始日期", "type": "date", "required": True},
{"key": "overdue_principal", "label": "欠付本金", "type": "number", "required": True},
{"key": "overdue_interest", "label": "欠付利息", "type": "number", "required": False},
{"key": "overdue_fee", "label": "欠付违约金/滞纳金", "type": "number", "required": False},
{"key": "has_fraud", "label": "是否涉及盗刷/欺诈", "type": "boolean", "required": True},
],
"claims": ["偿还透支本金", "支付利息", "支付违约金", "赔偿盗刷损失"],
},
{
"title": "融资租赁合同纠纷要素式起诉状",
"category": "商事",
"cause_of_action": "融资租赁合同纠纷",
"applicable": "融资租赁中租金拖欠、租赁物取回等",
"elements": [
{"key": "contract_date", "label": "合同签订日期", "type": "date", "required": True},
{"key": "lease_object", "label": "租赁物名称及型号", "type": "text", "required": True},
{"key": "total_rent", "label": "租金总额", "type": "number", "required": True},
{"key": "lease_term", "label": "租赁期限", "type": "text", "required": True},
{"key": "paid_rent", "label": "已付租金", "type": "number", "required": True},
{"key": "unpaid_rent", "label": "欠付租金", "type": "number", "required": True},
{"key": "dispute_type", "label": "争议类型", "type": "select", "options": ["拖欠租金", "收回租赁物", "合同解除", "租赁物损坏"], "required": True},
],
"claims": ["支付拖欠租金", "支付违约金", "解除融资租赁合同", "收回租赁物", "赔偿损失"],
},
{
"title": "保证保险合同纠纷要素式起诉状",
"category": "商事",
"cause_of_action": "保证保险合同纠纷",
"applicable": "保证保险(如贷款保证险)理赔争议",
"elements": [
{"key": "insurance_date", "label": "保险签订日期", "type": "date", "required": True},
{"key": "insurance_amount", "label": "保险金额", "type": "number", "required": True},
{"key": "premium", "label": "保费金额", "type": "number", "required": True},
{"key": "claim_event", "label": "理赔事由", "type": "textarea", "required": True},
{"key": "claim_amount", "label": "理赔金额", "type": "number", "required": True},
{"key": "claim_date", "label": "理赔申请日期", "type": "date", "required": True},
{"key": "insurer_response", "label": "保险公司处理结果", "type": "textarea", "required": True},
],
"claims": ["支付保险金", "支付逾期理赔利息", "赔偿损失"],
},
# ===== 知识产权类(9类) - 精简 =====
{
"title": "侵害商标权纠纷要素式起诉状",
"category": "知识产权",
"cause_of_action": "侵害商标权纠纷",
"applicable": "他人未经许可使用相同或近似商标",
"elements": [
{"key": "trademark_name", "label": "注册商标名称", "type": "text", "required": True},
{"key": "trademark_no", "label": "商标注册号", "type": "text", "required": True},
{"key": "trademark_class", "label": "核定使用商品/服务类别", "type": "text", "required": True},
{"key": "infringement_act", "label": "侵权行为", "type": "select", "options": ["假冒", "仿冒", "销售侵权商品", "反向假冒", "其他"], "required": True},
{"key": "infringement_detail", "label": "侵权行为描述", "type": "textarea", "required": True},
{"key": "infringement_time", "label": "发现侵权时间", "type": "date", "required": True},
{"key": "infringement_scope", "label": "侵权范围/规模", "type": "textarea", "required": True},
{"key": "has_notarization", "label": "是否有公证保全", "type": "boolean", "required": True},
{"key": "loss_amount", "label": "损失金额(或侵权获利)", "type": "number", "required": False},
],
"claims": ["停止侵权", "赔偿损失", "消除影响", "销毁侵权商品及工具"],
},
{
"title": "侵害著作权纠纷要素式起诉状",
"category": "知识产权",
"cause_of_action": "侵害著作权及邻接权纠纷",
"applicable": "他人未经许可使用作品(文字/图片/视频/软件等)",
"elements": [
{"key": "work_name", "label": "作品名称", "type": "text", "required": True},
{"key": "work_type", "label": "作品类型", "type": "select", "options": ["文字作品", "美术作品", "摄影作品", "视听作品", "音乐作品", "软件作品", "其他"], "required": True},
{"key": "copyright_proof", "label": "权属证明", "type": "textarea", "required": True},
{"key": "infringement_act", "label": "侵权行为", "type": "select", "options": ["复制", "发行", "信息网络传播", "改编", "抄袭", "其他"], "required": True},
{"key": "infringement_detail", "label": "侵权行为描述", "type": "textarea", "required": True},
{"key": "infringement_platform", "label": "侵权平台/渠道", "type": "text", "required": False},
{"key": "has_notarization", "label": "是否有公证保全", "type": "boolean", "required": True},
],
"claims": ["停止侵权", "赔偿损失", "赔礼道歉", "消除影响"],
},
# ===== 行政类(精简) =====
{
"title": "行政处罚纠纷要素式起诉状",
"category": "行政",
"cause_of_action": "行政处罚",
"applicable": "对行政机关作出的行政处罚决定不服",
"elements": [
{"key": "penalty_decision_no", "label": "处罚决定书文号", "type": "text", "required": True},
{"key": "penalty_authority", "label": "处罚机关", "type": "text", "required": True},
{"key": "penalty_date", "label": "处罚日期", "type": "date", "required": True},
{"key": "penalty_type", "label": "处罚类型", "type": "select", "options": ["罚款", "没收违法所得", "责令停产停业", "吊销许可证", "行政拘留", "其他"], "required": True},
{"key": "penalty_amount", "label": "罚款金额", "type": "number", "required": False},
{"key": "penalty_reason", "label": "处罚事由", "type": "textarea", "required": True},
{"key": "appeal_status", "label": "是否已申请行政复议", "type": "boolean", "required": True},
{"key": "appeal_result", "label": "复议结果(如有)", "type": "textarea", "required": False},
],
"claims": ["撤销行政处罚决定", "变更处罚内容", "确认违法", "赔偿损失"],
},
# ===== 执行类(精简) =====
{
"title": "强制执行申请书要素式模板",
"category": "执行",
"cause_of_action": "强制执行",
"applicable": "生效法律文书确定的义务对方不履行,申请法院强制执行",
"elements": [
{"key": "effective_doc_no", "label": "生效法律文书文号", "type": "text", "required": True},
{"key": "effective_doc_type", "label": "文书类型", "type": "select", "options": ["判决书", "裁定书", "调解书", "仲裁裁决", "公证债权文书"], "required": True},
{"key": "effective_date", "label": "生效日期", "type": "date", "required": True},
{"key": "obligation_content", "label": "义务内容", "type": "textarea", "required": True},
{"key": "unperformed_amount", "label": "未履行金额", "type": "number", "required": True},
{"key": "debtor_property", "label": "被执行人财产线索", "type": "textarea", "required": False},
],
"claims": ["强制执行", "查封/扣押/冻结财产", "划扣存款", "提取收入"],
},
]
def seed_element_templates():
"""将要素表模板导入知识库。"""
store = KnowledgeStore()
count = 0
for tmpl in ELEMENT_TEMPLATES:
content = json.dumps(tmpl, ensure_ascii=False, indent=2)
store.add(
title=tmpl["title"],
content=content,
source="最高法法〔2025〕82号",
doc_type="element_template",
keywords=tmpl["cause_of_action"],
)
count += 1
print(f"已导入 {count} 类要素表模板到知识库")
return count
if __name__ == "__main__":
seed_element_templates()
+648
View File
@@ -0,0 +1,648 @@
"""最高法67类要素式起诉状模板 - 补充剩余50类。
与 seed_elements.py 合并共67类。
数据来源:最高人民法院、司法部、中华全国律师协会联合印发
《部分案件起诉状答辩状示范文本》(法〔2025〕82号)
"""
import json
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from bossai import KnowledgeStore
# 已导入的17类案由(避免重复)
EXISTING_CAUSES = {
"离婚纠纷", "买卖合同纠纷", "民间借贷纠纷", "劳动争议纠纷",
"机动车交通事故责任纠纷", "房屋买卖合同纠纷", "房屋租赁合同纠纷",
"建设工程施工合同纠纷", "物业服务合同纠纷",
"金融借款合同纠纷", "信用卡纠纷", "融资租赁合同纠纷", "保证保险合同纠纷",
"侵害商标权纠纷", "侵害著作权及邻接权纠纷",
"行政处罚", "强制执行",
}
# 剩余50类
REMAINING_TEMPLATES = [
# ===== 商事类补充(4类) =====
{"title": "证券虚假陈述责任纠纷要素式起诉状", "category": "商事", "cause_of_action": "证券虚假陈述责任纠纷",
"applicable": "上市公司虚假陈述导致投资者损失",
"elements": [
{"key": "stock_name", "label": "股票名称及代码", "type": "text", "required": True},
{"key": "false_statement_date", "label": "虚假陈述实施日", "type": "date", "required": True},
{"key": "exposure_date", "label": "揭露日", "type": "date", "required": True},
{"key": "buy_date", "label": "买入日期", "type": "date", "required": True},
{"key": "buy_price", "label": "买入价格", "type": "number", "required": True},
{"key": "buy_quantity", "label": "买入数量", "type": "number", "required": True},
{"key": "sell_date", "label": "卖出日期(如有)", "type": "date", "required": False},
{"key": "sell_price", "label": "卖出价格(如有)", "type": "number", "required": False},
{"key": "loss_amount", "label": "投资损失金额", "type": "number", "required": True},
{"key": "has_admin_penalty", "label": "是否有行政处罚决定", "type": "boolean", "required": True},
],
"claims": ["赔偿投资损失", "支付利息"]},
{"title": "财产损失保险合同纠纷要素式起诉状", "category": "商事", "cause_of_action": "财产损失保险合同纠纷",
"applicable": "财产保险理赔争议",
"elements": [
{"key": "policy_no", "label": "保单号", "type": "text", "required": True},
{"key": "insured_object", "label": "保险标的", "type": "text", "required": True},
{"key": "coverage_amount", "label": "保险金额", "type": "number", "required": True},
{"key": "incident_date", "label": "事故发生日期", "type": "date", "required": True},
{"key": "incident_desc", "label": "事故经过描述", "type": "textarea", "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": True},
{"key": "claim_date", "label": "理赔申请日期", "type": "date", "required": True},
{"key": "insurer_response", "label": "保险公司处理结果", "type": "textarea", "required": True},
],
"claims": ["支付保险赔偿金", "支付逾期理赔利息", "赔偿损失"]},
{"title": "责任保险合同纠纷要素式起诉状", "category": "商事", "cause_of_action": "责任保险合同纠纷",
"applicable": "第三者责任险理赔争议",
"elements": [
{"key": "policy_no", "label": "保单号", "type": "text", "required": True},
{"key": "insurance_type", "label": "险种", "type": "select", "options": ["交强险", "商业三者险", "产品责任险", "雇主责任险", "其他"], "required": True},
{"key": "third_party_claim", "label": "第三者索赔金额", "type": "number", "required": True},
{"key": "incident_date", "label": "事故日期", "type": "date", "required": True},
{"key": "incident_desc", "label": "事故描述", "type": "textarea", "required": True},
{"key": "insurer_response", "label": "保险公司处理结果", "type": "textarea", "required": True},
],
"claims": ["支付保险赔偿金", "承担诉讼费用"]},
{"title": "人身保险合同纠纷要素式起诉状", "category": "商事", "cause_of_action": "人身保险合同纠纷",
"applicable": "人身保险(寿险/意外险/健康险)理赔争议",
"elements": [
{"key": "policy_no", "label": "保单号", "type": "text", "required": True},
{"key": "insurance_type", "label": "险种", "type": "select", "options": ["寿险", "意外伤害险", "健康险", "重疾险", "医疗险"], "required": True},
{"key": "insured_person", "label": "被保险人", "type": "text", "required": True},
{"key": "incident_date", "label": "保险事故日期", "type": "date", "required": True},
{"key": "incident_type", "label": "事故类型", "type": "select", "options": ["身故", "伤残", "疾病", "医疗费用"], "required": True},
{"key": "claim_amount", "label": "理赔金额", "type": "number", "required": True},
{"key": "insurer_response", "label": "保险公司处理结果", "type": "textarea", "required": True},
{"key": "has_dispute_clause", "label": "争议条款", "type": "textarea", "required": False},
],
"claims": ["支付保险金", "支付逾期利息", "恢复保险合同效力"]},
# ===== 知识产权类补充(7类) =====
{"title": "侵害发明专利权纠纷要素式起诉状", "category": "知识产权", "cause_of_action": "侵害发明专利权纠纷",
"applicable": "他人未经许可使用发明专利",
"elements": [
{"key": "patent_name", "label": "专利名称", "type": "text", "required": True},
{"key": "patent_no", "label": "专利号", "type": "text", "required": True},
{"key": "patent_type", "label": "专利类型", "type": "select", "options": ["产品专利", "方法专利", "产品+方法专利"], "required": True},
{"key": "infringement_act", "label": "侵权行为", "type": "select", "options": ["制造", "使用", "许诺销售", "销售", "进口", "其他"], "required": True},
{"key": "infringement_detail", "label": "侵权行为描述", "type": "textarea", "required": True},
{"key": "has_notarization", "label": "是否有公证保全", "type": "boolean", "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": False},
],
"claims": ["停止侵权", "赔偿损失", "消除影响"]},
{"title": "侵害外观设计专利权纠纷要素式起诉状", "category": "知识产权", "cause_of_action": "侵害外观设计专利权纠纷",
"applicable": "他人未经许可使用外观设计专利",
"elements": [
{"key": "patent_name", "label": "专利名称", "type": "text", "required": True},
{"key": "patent_no", "label": "专利号", "type": "text", "required": True},
{"key": "product_category", "label": "产品类别", "type": "text", "required": True},
{"key": "infringement_act", "label": "侵权行为", "type": "select", "options": ["制造", "销售", "进口"], "required": True},
{"key": "infringement_detail", "label": "侵权行为描述", "type": "textarea", "required": True},
{"key": "has_notarization", "label": "是否有公证保全", "type": "boolean", "required": True},
],
"claims": ["停止侵权", "赔偿损失", "消除影响"]},
{"title": "侵害植物新品种权纠纷要素式起诉状", "category": "知识产权", "cause_of_action": "侵害植物新品种权纠纷",
"applicable": "他人未经许可使用植物新品种",
"elements": [
{"key": "variety_name", "label": "品种名称", "type": "text", "required": True},
{"key": "variety_right_no", "label": "品种权号", "type": "text", "required": True},
{"key": "infringement_act", "label": "侵权行为", "type": "select", "options": ["生产", "繁殖", "销售", "推广"], "required": True},
{"key": "infringement_detail", "label": "侵权行为描述", "type": "textarea", "required": True},
],
"claims": ["停止侵权", "赔偿损失"]},
{"title": "侵害商业秘密纠纷要素式起诉状", "category": "知识产权", "cause_of_action": "侵害商业秘密纠纷",
"applicable": "他人非法获取、使用或披露商业秘密",
"elements": [
{"key": "secret_name", "label": "商业秘密名称", "type": "text", "required": True},
{"key": "secret_type", "label": "秘密类型", "type": "select", "options": ["技术信息", "经营信息", "客户名单", "其他"], "required": True},
{"key": "confidentiality_measure", "label": "保密措施", "type": "textarea", "required": True},
{"key": "infringement_act", "label": "侵权行为", "type": "select", "options": ["非法获取", "非法披露", "非法使用", "违反保密协议"], "required": True},
{"key": "infringement_detail", "label": "侵权行为描述", "type": "textarea", "required": True},
{"key": "has_confidentiality_agreement", "label": "是否有保密协议", "type": "boolean", "required": True},
],
"claims": ["停止侵权", "赔偿损失", "返还商业秘密载体"]},
{"title": "技术合同纠纷要素式起诉状", "category": "知识产权", "cause_of_action": "技术合同纠纷",
"applicable": "技术开发、转让、咨询、服务合同争议",
"elements": [
{"key": "contract_type", "label": "合同类型", "type": "select", "options": ["技术开发合同", "技术转让合同", "技术咨询合同", "技术服务合同"], "required": True},
{"key": "contract_date", "label": "合同签订日期", "type": "date", "required": True},
{"key": "tech_subject", "label": "技术标的", "type": "text", "required": True},
{"key": "contract_amount", "label": "合同金额", "type": "number", "required": True},
{"key": "paid_amount", "label": "已付金额", "type": "number", "required": True},
{"key": "dispute_type", "label": "争议类型", "type": "select", "options": ["拖欠款项", "技术交付争议", "质量争议", "合同解除"], "required": True},
{"key": "breach_detail", "label": "违约情况", "type": "textarea", "required": True},
],
"claims": ["支付合同款项", "支付违约金", "解除合同", "赔偿损失"]},
{"title": "不正当竞争纠纷要素式起诉状", "category": "知识产权", "cause_of_action": "不正当竞争纠纷",
"applicable": "混淆、虚假宣传、商业诋毁等不正当竞争行为",
"elements": [
{"key": "unfair_act_type", "label": "不正当竞争行为类型", "type": "select", "options": ["混淆行为", "虚假宣传", "商业诋毁", "商业贿赂", "侵犯商业秘密", "不正当有奖销售"], "required": True},
{"key": "act_detail", "label": "行为描述", "type": "textarea", "required": True},
{"key": "act_time", "label": "行为发生时间", "type": "date", "required": True},
{"key": "act_scope", "label": "影响范围", "type": "textarea", "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": False},
],
"claims": ["停止不正当竞争行为", "赔偿损失", "消除影响"]},
{"title": "垄断纠纷要素式起诉状", "category": "知识产权", "cause_of_action": "垄断纠纷",
"applicable": "垄断协议、滥用市场支配地位等",
"elements": [
{"key": "monopoly_type", "label": "垄断行为类型", "type": "select", "options": ["垄断协议", "滥用市场支配地位", "经营者集中"], "required": True},
{"key": "relevant_market", "label": "相关市场界定", "type": "textarea", "required": True},
{"key": "act_detail", "label": "行为描述", "type": "textarea", "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": False},
],
"claims": ["停止垄断行为", "赔偿损失"]},
# ===== 知识产权行政类(6类) =====
{"title": "商标申请驳回复审纠纷要素式起诉状", "category": "知识产权行政", "cause_of_action": "商标申请驳回复审纠纷",
"applicable": "商标申请被驳回,申请复审后仍不服",
"elements": [
{"key": "trademark_name", "label": "申请商标名称", "type": "text", "required": True},
{"key": "application_no", "label": "申请号", "type": "text", "required": True},
{"key": "rejection_reason", "label": "驳回理由", "type": "textarea", "required": True},
{"key": "review_no", "label": "复审决定书文号", "type": "text", "required": True},
{"key": "review_result", "label": "复审结果", "type": "textarea", "required": True},
],
"claims": ["撤销复审决定", "准予商标注册"]},
{"title": "商标撤销复审行政纠纷要素式起诉状", "category": "知识产权行政", "cause_of_action": "商标撤销复审行政纠纷",
"applicable": "商标被撤销后申请复审不服",
"elements": [
{"key": "trademark_name", "label": "商标名称", "type": "text", "required": True},
{"key": "trademark_no", "label": "商标注册号", "type": "text", "required": True},
{"key": "revocation_reason", "label": "撤销理由", "type": "textarea", "required": True},
{"key": "review_no", "label": "复审决定书文号", "type": "text", "required": True},
],
"claims": ["撤销复审决定", "维持商标注册"]},
{"title": "商标无效行政纠纷要素式起诉状", "category": "知识产权行政", "cause_of_action": "商标无效行政纠纷",
"applicable": "商标被宣告无效后不服",
"elements": [
{"key": "trademark_name", "label": "商标名称", "type": "text", "required": True},
{"key": "trademark_no", "label": "商标注册号", "type": "text", "required": True},
{"key": "invalidation_reason", "label": "无效理由", "type": "textarea", "required": True},
{"key": "ruling_no", "label": "裁定书文号", "type": "text", "required": True},
],
"claims": ["撤销无效宣告", "维持商标注册"]},
{"title": "专利申请驳回复审行政纠纷要素式起诉状", "category": "知识产权行政", "cause_of_action": "专利申请驳回复审行政纠纷",
"applicable": "专利申请被驳回,复审后仍不服",
"elements": [
{"key": "patent_name", "label": "申请专利名称", "type": "text", "required": True},
{"key": "application_no", "label": "申请号", "type": "text", "required": True},
{"key": "rejection_reason", "label": "驳回理由", "type": "textarea", "required": True},
{"key": "review_no", "label": "复审决定书文号", "type": "text", "required": True},
],
"claims": ["撤销复审决定", "准予专利授权"]},
{"title": "专利无效行政纠纷要素式起诉状", "category": "知识产权行政", "cause_of_action": "专利无效行政纠纷",
"applicable": "专利被宣告无效后不服",
"elements": [
{"key": "patent_name", "label": "专利名称", "type": "text", "required": True},
{"key": "patent_no", "label": "专利号", "type": "text", "required": True},
{"key": "invalidation_reason", "label": "无效理由", "type": "textarea", "required": True},
{"key": "ruling_no", "label": "无效宣告请求审查决定书文号", "type": "text", "required": True},
],
"claims": ["撤销无效宣告决定", "维持专利权有效"]},
{"title": "垄断纠纷(行政)要素式起诉状", "category": "知识产权行政", "cause_of_action": "垄断纠纷(行政)",
"applicable": "对反垄断执法机构的行政处罚决定不服",
"elements": [
{"key": "penalty_authority", "label": "执法机构", "type": "text", "required": True},
{"key": "penalty_no", "label": "处罚决定书文号", "type": "text", "required": True},
{"key": "monopoly_type", "label": "垄断行为类型", "type": "select", "options": ["垄断协议", "滥用市场支配地位", "经营者集中"], "required": True},
{"key": "penalty_content", "label": "处罚内容", "type": "textarea", "required": True},
],
"claims": ["撤销行政处罚决定", "变更处罚内容"]},
# ===== 行政类补充(11类) =====
{"title": "行政强制执行纠纷要素式起诉状", "category": "行政", "cause_of_action": "行政强制执行",
"applicable": "对行政机关的行政强制执行行为不服",
"elements": [
{"key": "execution_authority", "label": "执行机关", "type": "text", "required": True},
{"key": "execution_no", "label": "执行决定书文号", "type": "text", "required": True},
{"key": "execution_date", "label": "执行日期", "type": "date", "required": True},
{"key": "execution_measure", "label": "强制执行措施", "type": "select", "options": ["查封", "扣押", "冻结", "划拨", "拍卖", "拆除", "其他"], "required": True},
{"key": "execution_target", "label": "执行标的", "type": "text", "required": True},
{"key": "dispute_reason", "label": "争议理由", "type": "textarea", "required": True},
],
"claims": ["撤销强制执行决定", "返还财产", "赔偿损失"]},
{"title": "行政许可纠纷要素式起诉状", "category": "行政", "cause_of_action": "行政许可",
"applicable": "对行政机关的行政许可决定(不予许可/撤销许可)不服",
"elements": [
{"key": "permit_type", "label": "许可类型", "type": "text", "required": True},
{"key": "application_date", "label": "申请日期", "type": "date", "required": True},
{"key": "authority", "label": "许可机关", "type": "text", "required": True},
{"key": "decision_date", "label": "决定日期", "type": "date", "required": True},
{"key": "decision_content", "label": "决定内容", "type": "select", "options": ["不予许可", "撤销许可", "变更许可", "吊销许可"], "required": True},
{"key": "decision_reason", "label": "决定理由", "type": "textarea", "required": True},
],
"claims": ["撤销行政决定", "判令作出许可", "赔偿损失"]},
{"title": "国有土地上房屋征收决定纠纷要素式起诉状", "category": "行政", "cause_of_action": "国有土地上房屋征收决定",
"applicable": "对政府作出的房屋征收决定不服",
"elements": [
{"key": "property_address", "label": "被征收房屋地址", "type": "text", "required": True},
{"key": "property_area", "label": "房屋面积", "type": "number", "required": True},
{"key": "decision_no", "label": "征收决定书文号", "type": "text", "required": True},
{"key": "decision_date", "label": "征收决定日期", "type": "date", "required": True},
{"key": "authority", "label": "征收主体", "type": "text", "required": True},
{"key": "compensation_plan", "label": "补偿方案", "type": "textarea", "required": True},
{"key": "dispute_reason", "label": "争议理由", "type": "textarea", "required": True},
],
"claims": ["撤销征收决定", "重新作出补偿决定", "提高补偿标准"]},
{"title": "工伤保险资格或待遇认定纠纷要素式起诉状", "category": "行政", "cause_of_action": "工伤保险资格或者待遇认定(行政)",
"applicable": "对工伤保险资格认定或待遇标准不服",
"elements": [
{"key": "injury_date", "label": "受伤日期", "type": "date", "required": True},
{"key": "injury_desc", "label": "受伤经过", "type": "textarea", "required": True},
{"key": "recognition_no", "label": "认定决定书文号", "type": "text", "required": True},
{"key": "recognition_result", "label": "认定结果", "type": "select", "options": ["认定为工伤", "不予认定为工伤", "视同工伤", "不视同工伤"], "required": True},
{"key": "disability_level", "label": "伤残等级(如有)", "type": "text", "required": False},
{"key": "treatment_amount", "label": "待遇金额争议", "type": "number", "required": False},
],
"claims": ["撤销认定决定", "重新作出认定", "支付工伤保险待遇"]},
{"title": "政府信息公开纠纷要素式起诉状", "category": "行政", "cause_of_action": "政府信息公开",
"applicable": "申请政府信息公开被拒绝或逾期未答复",
"elements": [
{"key": "application_date", "label": "申请日期", "type": "date", "required": True},
{"key": "info_content", "label": "申请公开的信息内容", "type": "textarea", "required": True},
{"key": "authority", "label": "被申请机关", "type": "text", "required": True},
{"key": "response", "label": "答复情况", "type": "select", "options": ["逾期未答复", "拒绝公开", "部分公开", "信息不存在"], "required": True},
{"key": "refusal_reason", "label": "拒绝理由(如有)", "type": "textarea", "required": False},
],
"claims": ["判令公开政府信息", "重新作出答复"]},
{"title": "行政复议纠纷要素式起诉状", "category": "行政", "cause_of_action": "行政复议",
"applicable": "对行政复议决定不服",
"elements": [
{"key": "original_decision", "label": "原行政行为", "type": "textarea", "required": True},
{"key": "original_authority", "label": "原行为机关", "type": "text", "required": True},
{"key": "review_authority", "label": "复议机关", "type": "text", "required": True},
{"key": "review_no", "label": "复议决定书文号", "type": "text", "required": True},
{"key": "review_date", "label": "复议决定日期", "type": "date", "required": True},
{"key": "review_result", "label": "复议结果", "type": "select", "options": ["维持", "撤销", "变更", "确认违法", "驳回"], "required": True},
],
"claims": ["撤销复议决定", "撤销原行政行为", "重新作出行政行为"]},
{"title": "行政协议纠纷要素式起诉状", "category": "行政", "cause_of_action": "行政协议",
"applicable": "行政机关不履行行政协议或单方变更解除",
"elements": [
{"key": "agreement_name", "label": "协议名称", "type": "text", "required": True},
{"key": "agreement_date", "label": "签订日期", "type": "date", "required": True},
{"key": "agreement_parties", "label": "协议双方", "type": "text", "required": True},
{"key": "agreement_content", "label": "协议主要内容", "type": "textarea", "required": True},
{"key": "dispute_type", "label": "争议类型", "type": "select", "options": ["不履行", "单方变更", "单方解除", "补偿争议"], "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": False},
],
"claims": ["继续履行协议", "赔偿损失", "补偿"]},
{"title": "行政补偿纠纷要素式起诉状", "category": "行政", "cause_of_action": "行政补偿",
"applicable": "对行政机关的行政补偿决定不服",
"elements": [
{"key": "compensation_reason", "label": "补偿事由", "type": "textarea", "required": True},
{"key": "authority", "label": "补偿机关", "type": "text", "required": True},
{"key": "compensation_offer", "label": "补偿方案", "type": "textarea", "required": True},
{"key": "claim_amount", "label": "诉求补偿金额", "type": "number", "required": True},
{"key": "dispute_reason", "label": "争议理由", "type": "textarea", "required": True},
],
"claims": ["提高补偿标准", "支付补偿款"]},
{"title": "行政赔偿纠纷要素式起诉状", "category": "行政", "cause_of_action": "行政赔偿",
"applicable": "行政机关违法行为造成损失,要求赔偿",
"elements": [
{"key": "illegal_act", "label": "违法行为描述", "type": "textarea", "required": True},
{"key": "authority", "label": "赔偿义务机关", "type": "text", "required": True},
{"key": "act_date", "label": "行为发生日期", "type": "date", "required": True},
{"key": "loss_type", "label": "损失类型", "type": "select", "options": ["财产损失", "人身损害", "精神损害"], "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": True},
{"key": "has_confirmed_illegal", "label": "是否已确认违法", "type": "boolean", "required": True},
],
"claims": ["支付赔偿金", "返还财产", "恢复原状"]},
{"title": "不履行法定职责纠纷要素式起诉状", "category": "行政", "cause_of_action": "不履行法定职责",
"applicable": "行政机关不履行或拖延履行法定职责",
"elements": [
{"key": "duty_type", "label": "法定职责类型", "type": "text", "required": True},
{"key": "application_date", "label": "申请履行日期", "type": "date", "required": True},
{"key": "authority", "label": "被诉行政机关", "type": "text", "required": True},
{"key": "response", "label": "答复情况", "type": "select", "options": ["未答复", "拒绝履行", "拖延履行"], "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": False},
],
"claims": ["判令履行法定职责", "赔偿损失"]},
# ===== 刑事自诉类(4类) =====
{"title": "侮辱案刑事自诉要素式起诉状", "category": "刑事自诉", "cause_of_action": "侮辱案刑事自诉(附带民事)",
"applicable": "他人以暴力或其他方法公然侮辱,情节严重",
"elements": [
{"key": "insult_act", "label": "侮辱行为描述", "type": "textarea", "required": True},
{"key": "act_date", "label": "行为发生日期", "type": "date", "required": True},
{"key": "act_location", "label": "行为发生地点", "type": "text", "required": True},
{"key": "publicity", "label": "是否公然进行", "type": "boolean", "required": True},
{"key": "severity", "label": "情节严重程度", "type": "textarea", "required": True},
{"key": "evidence", "label": "证据清单", "type": "textarea", "required": True},
{"key": "civil_claim", "label": "附带民事诉求", "type": "textarea", "required": False},
],
"claims": ["追究刑事责任", "赔礼道歉", "赔偿精神损害"]},
{"title": "诽谤案刑事自诉要素式起诉状", "category": "刑事自诉", "cause_of_action": "诽谤案刑事自诉(附带民事)",
"applicable": "他人捏造事实诽谤,情节严重",
"elements": [
{"key": "fabrication_detail", "label": "捏造事实描述", "type": "textarea", "required": True},
{"key": "spread_method", "label": "传播方式", "type": "select", "options": ["口头", "文字", "网络", "其他"], "required": True},
{"key": "act_date", "label": "行为发生日期", "type": "date", "required": True},
{"key": "severity", "label": "情节严重程度", "type": "textarea", "required": True},
{"key": "evidence", "label": "证据清单", "type": "textarea", "required": True},
],
"claims": ["追究刑事责任", "赔礼道歉", "赔偿精神损害"]},
{"title": "重婚案刑事自诉要素式起诉状", "category": "刑事自诉", "cause_of_action": "重婚案刑事自诉(附带民事)",
"applicable": "配偶与他人重婚",
"elements": [
{"key": "legal_marriage", "label": "合法婚姻情况", "type": "textarea", "required": True},
{"key": "bigamy_detail", "label": "重婚行为描述", "type": "textarea", "required": True},
{"key": "bigamy_start", "label": "重婚开始时间", "type": "date", "required": True},
{"key": "cohabitation_proof", "label": "以夫妻名义共同生活证据", "type": "textarea", "required": True},
],
"claims": ["追究刑事责任"]},
{"title": "拒不执行判决裁定案刑事自诉要素式起诉状", "category": "刑事自诉", "cause_of_action": "拒不执行判决裁定案刑事自诉(附带民事)",
"applicable": "被执行人拒不执行生效判决裁定,情节严重",
"elements": [
{"key": "effective_doc", "label": "生效判决/裁定文号", "type": "text", "required": True},
{"key": "effective_date", "label": "生效日期", "type": "date", "required": True},
{"key": "obligation", "label": "义务内容", "type": "textarea", "required": True},
{"key": "refusal_act", "label": "拒不执行行为描述", "type": "textarea", "required": True},
{"key": "has_ability", "label": "是否有履行能力", "type": "boolean", "required": True},
{"key": "property_clue", "label": "财产线索", "type": "textarea", "required": False},
],
"claims": ["追究刑事责任"]},
# ===== 海事类(4类) =====
{"title": "船舶碰撞损害责任纠纷要素式起诉状", "category": "海事", "cause_of_action": "船舶碰撞损害责任纠纷",
"applicable": "船舶之间碰撞造成损害",
"elements": [
{"key": "collision_date", "label": "碰撞日期", "type": "date", "required": True},
{"key": "collision_location", "label": "碰撞地点(水域)", "type": "text", "required": True},
{"key": "own_vessel", "label": "己方船舶信息", "type": "text", "required": True},
{"key": "other_vessel", "label": "对方船舶信息", "type": "text", "required": True},
{"key": "responsibility", "label": "责任比例", "type": "text", "required": True},
{"key": "damage_detail", "label": "损害情况", "type": "textarea", "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": True},
],
"claims": ["赔偿损失", "支付利息"]},
{"title": "海上人身损害责任纠纷要素式起诉状", "category": "海事", "cause_of_action": "海上通海水域人身损害责任纠纷",
"applicable": "海上或通海水域事故造成人身伤亡",
"elements": [
{"key": "incident_date", "label": "事故日期", "type": "date", "required": True},
{"key": "incident_location", "label": "事故地点", "type": "text", "required": True},
{"key": "victim_info", "label": "受害人信息", "type": "text", "required": True},
{"key": "injury_detail", "label": "伤亡情况", "type": "textarea", "required": True},
{"key": "medical_expense", "label": "医疗费", "type": "number", "required": False},
{"key": "death_compensation", "label": "死亡赔偿金(如适用)", "type": "number", "required": False},
],
"claims": ["赔偿医疗费", "赔偿死亡赔偿金", "赔偿精神损害抚慰金"]},
{"title": "海上货运代理合同纠纷要素式起诉状", "category": "海事", "cause_of_action": "海上通海水域货运代理合同纠纷",
"applicable": "海上/水上货物运输代理合同争议",
"elements": [
{"key": "contract_date", "label": "合同签订日期", "type": "date", "required": True},
{"key": "goods_info", "label": "货物信息", "type": "textarea", "required": True},
{"key": "route", "label": "运输路线", "type": "text", "required": True},
{"key": "freight", "label": "运费金额", "type": "number", "required": True},
{"key": "dispute_type", "label": "争议类型", "type": "select", "options": ["拖欠运费", "货物损坏", "货物灭失", "迟延交付"], "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": True},
],
"claims": ["支付运费", "赔偿货物损失", "支付违约金"]},
{"title": "船员劳务合同纠纷要素式起诉状", "category": "海事", "cause_of_action": "船员劳务合同纠纷",
"applicable": "船员与船东之间的劳务争议",
"elements": [
{"key": "service_start", "label": "上船日期", "type": "date", "required": True},
{"key": "service_end", "label": "下船日期", "type": "date", "required": False},
{"key": "vessel_name", "label": "船舶名称", "type": "text", "required": True},
{"key": "position", "label": "船员职务", "type": "text", "required": True},
{"key": "salary_standard", "label": "工资标准", "type": "number", "required": True},
{"key": "unpaid_salary", "label": "拖欠工资", "type": "number", "required": True},
{"key": "has_injury", "label": "是否有工伤", "type": "boolean", "required": False},
],
"claims": ["支付拖欠工资", "支付经济补偿金", "工伤赔偿"]},
# ===== 环境资源类(3类) =====
{"title": "环境污染民事公益诉讼要素式起诉状", "category": "环境资源", "cause_of_action": "环境污染民事公益诉讼",
"applicable": "污染环境损害社会公共利益",
"elements": [
{"key": "pollution_type", "label": "污染类型", "type": "select", "options": ["大气污染", "水污染", "土壤污染", "噪声污染", "固废污染"], "required": True},
{"key": "polluter", "label": "污染者信息", "type": "text", "required": True},
{"key": "pollution_location", "label": "污染地点", "type": "text", "required": True},
{"key": "pollution_duration", "label": "污染持续时间", "type": "text", "required": True},
{"key": "damage_scope", "label": "损害范围", "type": "textarea", "required": True},
{"key": "remediation_cost", "label": "生态修复费用", "type": "number", "required": False},
],
"claims": ["停止污染", "修复生态环境", "赔偿生态损害", "赔礼道歉"]},
{"title": "生态破坏民事公益诉讼要素式起诉状", "category": "环境资源", "cause_of_action": "生态破坏民事公益诉讼",
"applicable": "破坏生态损害社会公共利益",
"elements": [
{"key": "destruction_type", "label": "破坏类型", "type": "select", "options": ["滥砍滥伐", "非法捕捞", "非法狩猎", "破坏湿地", "破坏草原"], "required": True},
{"key": "destroyer", "label": "破坏者信息", "type": "text", "required": True},
{"key": "destruction_location", "label": "破坏地点", "type": "text", "required": True},
{"key": "damage_scope", "label": "损害范围", "type": "textarea", "required": True},
{"key": "remediation_cost", "label": "生态修复费用", "type": "number", "required": False},
],
"claims": ["停止破坏", "修复生态环境", "赔偿生态损害"]},
{"title": "生态环境损害赔偿诉讼要素式起诉状", "category": "环境资源", "cause_of_action": "生态环境损害赔偿诉讼",
"applicable": "政府作为赔偿权利人提起生态环境损害赔偿",
"elements": [
{"key": "damage_type", "label": "损害类型", "type": "text", "required": True},
{"key": "damager", "label": "损害者信息", "type": "text", "required": True},
{"key": "damage_location", "label": "损害地点", "type": "text", "required": True},
{"key": "damage_scope", "label": "损害范围及程度", "type": "textarea", "required": True},
{"key": "remediation_cost", "label": "修复费用", "type": "number", "required": True},
{"key": "loss_amount", "label": "生态环境损害金额", "type": "number", "required": True},
],
"claims": ["承担修复费用", "赔偿生态环境损害", "停止损害行为"]},
# ===== 国家赔偿类(4类) =====
{"title": "违法刑事拘留赔偿要素式起诉状", "category": "国家赔偿", "cause_of_action": "违法刑事拘留赔偿",
"applicable": "被违法刑事拘留后申请国家赔偿",
"elements": [
{"key": "detention_no", "label": "拘留决定书文号", "type": "text", "required": True},
{"key": "detention_start", "label": "拘留起始日期", "type": "date", "required": True},
{"key": "detention_end", "label": "拘留结束日期", "type": "date", "required": True},
{"key": "detention_days", "label": "拘留天数", "type": "number", "required": True},
{"key": "authority", "label": "作出拘留决定的机关", "type": "text", "required": True},
{"key": "release_reason", "label": "释放原因", "type": "textarea", "required": True},
{"key": "claim_amount", "label": "赔偿请求金额", "type": "number", "required": True},
],
"claims": ["支付人身自由损害赔偿金", "赔礼道歉", "恢复名誉"]},
{"title": "刑事改判无罪赔偿要素式起诉状", "category": "国家赔偿", "cause_of_action": "刑事改判无罪赔偿",
"applicable": "原判有罪后改判无罪,申请国家赔偿",
"elements": [
{"key": "original_judgment", "label": "原判决文号", "type": "text", "required": True},
{"key": "revised_judgment", "label": "改判判决文号", "type": "text", "required": True},
{"key": "detention_period", "label": "被羁押期间", "type": "text", "required": True},
{"key": "detention_days", "label": "羁押天数", "type": "number", "required": True},
{"key": "claim_amount", "label": "赔偿请求金额", "type": "number", "required": True},
],
"claims": ["支付人身自由损害赔偿金", "精神损害抚慰金", "赔礼道歉"]},
{"title": "怠于履行监管职责致伤致死赔偿要素式起诉状", "category": "国家赔偿", "cause_of_action": "怠于履行监管职责致伤致死赔偿",
"applicable": "监管机关怠于履行职责导致被监管人伤亡",
"elements": [
{"key": "supervision_authority", "label": "监管机关", "type": "text", "required": True},
{"key": "victim_info", "label": "被监管人信息", "type": "text", "required": True},
{"key": "incident_date", "label": "事件发生日期", "type": "date", "required": True},
{"key": "incident_detail", "label": "事件经过", "type": "textarea", "required": True},
{"key": "duty_breach", "label": "怠于履职情况", "type": "textarea", "required": True},
{"key": "damage_result", "label": "损害结果", "type": "select", "options": ["受伤", "死亡"], "required": True},
{"key": "claim_amount", "label": "赔偿请求金额", "type": "number", "required": True},
],
"claims": ["支付赔偿金", "追究相关责任"]},
{"title": "错误执行赔偿要素式起诉状", "category": "国家赔偿", "cause_of_action": "错误执行赔偿",
"applicable": "法院错误执行导致损失,申请国家赔偿",
"elements": [
{"key": "execution_case", "label": "执行案件文号", "type": "text", "required": True},
{"key": "execution_authority", "label": "执行法院", "type": "text", "required": True},
{"key": "wrong_act", "label": "错误执行行为", "type": "textarea", "required": True},
{"key": "execution_date", "label": "错误执行日期", "type": "date", "required": True},
{"key": "loss_detail", "label": "损失情况", "type": "textarea", "required": True},
{"key": "loss_amount", "label": "损失金额", "type": "number", "required": True},
],
"claims": ["支付赔偿金", "返还财产"]},
# ===== 执行类补充(8类) =====
{"title": "暂时解除限制措施申请书要素式模板", "category": "执行", "cause_of_action": "暂时解除乘坐飞机高铁限制措施",
"applicable": "被限制高消费后申请暂时解除乘坐飞机高铁限制",
"elements": [
{"key": "restriction_no", "label": "限制消费令文号", "type": "text", "required": True},
{"key": "restriction_date", "label": "限制日期", "type": "date", "required": True},
{"key": "reason", "label": "申请解除理由", "type": "select", "options": ["紧急就医", "直系亲属丧葬", "公务出行", "其他紧急情况"], "required": True},
{"key": "travel_plan", "label": "出行计划(日期/航班车次)", "type": "textarea", "required": True},
],
"claims": ["暂时解除乘坐飞机限制", "暂时解除乘坐高铁限制"]},
{"title": "参与分配申请书要素式模板", "category": "执行", "cause_of_action": "参与分配",
"applicable": "被执行人财产不足清偿全部债务,债权人申请参与分配",
"elements": [
{"key": "execution_case", "label": "执行案件文号", "type": "text", "required": True},
{"key": "debtor", "label": "被执行人", "type": "text", "required": True},
{"key": "creditor_claim", "label": "申请人债权金额", "type": "number", "required": True},
{"key": "total_debt", "label": "被执行人总债务", "type": "number", "required": False},
{"key": "available_property", "label": "可供分配财产", "type": "textarea", "required": True},
],
"claims": ["参与财产分配"]},
{"title": "执行担保申请书要素式模板", "category": "执行", "cause_of_action": "执行担保",
"applicable": "执行中提供担保暂缓执行",
"elements": [
{"key": "execution_case", "label": "执行案件文号", "type": "text", "required": True},
{"key": "guarantor", "label": "担保人信息", "type": "text", "required": True},
{"key": "guarantee_type", "label": "担保方式", "type": "select", "options": ["保证", "抵押", "质押"], "required": True},
{"key": "guarantee_amount", "label": "担保金额", "type": "number", "required": True},
{"key": "guarantee_property", "label": "担保财产", "type": "textarea", "required": True},
],
"claims": ["暂缓执行"]},
{"title": "确认优先购买权申请书要素式模板", "category": "执行", "cause_of_action": "确认优先购买权",
"applicable": "执行拍卖中主张优先购买权",
"elements": [
{"key": "execution_case", "label": "执行案件文号", "type": "text", "required": True},
{"key": "auction_target", "label": "拍卖标的", "type": "text", "required": True},
{"key": "priority_basis", "label": "优先购买权依据", "type": "select", "options": ["共有关系", "租赁关系", "公司法规定", "其他"], "required": True},
{"key": "priority_proof", "label": "优先购买权证据", "type": "textarea", "required": True},
],
"claims": ["确认优先购买权"]},
{"title": "执行异议申请书要素式模板", "category": "执行", "cause_of_action": "执行异议",
"applicable": "对执行行为不服提出异议",
"elements": [
{"key": "execution_case", "label": "执行案件文号", "type": "text", "required": True},
{"key": "objection_type", "label": "异议类型", "type": "select", "options": ["执行行为异议", "案外人异议"], "required": True},
{"key": "objection_target", "label": "异议标的", "type": "text", "required": True},
{"key": "objection_reason", "label": "异议理由", "type": "textarea", "required": True},
],
"claims": ["撤销执行行为", "中止执行", "解除查封扣押"]},
{"title": "执行复议申请书要素式模板", "category": "执行", "cause_of_action": "执行复议",
"applicable": "对执行异议裁定不服申请复议",
"elements": [
{"key": "ruling_no", "label": "异议裁定书文号", "type": "text", "required": True},
{"key": "ruling_date", "label": "裁定日期", "type": "date", "required": True},
{"key": "ruling_result", "label": "裁定结果", "type": "textarea", "required": True},
{"key": "rejection_reason", "label": "复议理由", "type": "textarea", "required": True},
],
"claims": ["撤销异议裁定", "支持异议请求"]},
{"title": "执行监督申请书要素式模板", "category": "执行", "cause_of_action": "执行监督",
"applicable": "请求上级法院对执行行为进行监督",
"elements": [
{"key": "execution_case", "label": "执行案件文号", "type": "text", "required": True},
{"key": "execution_court", "label": "执行法院", "type": "text", "required": True},
{"key": "supervision_reason", "label": "申请监督理由", "type": "textarea", "required": True},
{"key": "illegal_act", "label": "违法/不当执行行为", "type": "textarea", "required": True},
],
"claims": ["监督执行", "纠正违法执行行为"]},
{"title": "不予执行仲裁裁决申请书要素式模板", "category": "执行", "cause_of_action": "不予执行仲裁裁决调解书或公证债权文书",
"applicable": "申请不予执行仲裁裁决/调解书/公证债权文书",
"elements": [
{"key": "award_no", "label": "仲裁裁决/调解书/公证文书文号", "type": "text", "required": True},
{"key": "award_date", "label": "文书作出日期", "type": "date", "required": True},
{"key": "refusal_reason", "label": "申请不予执行理由", "type": "select", "options": ["没有仲裁协议", "裁决事项超范围", "程序违法", "证据伪造", "隐瞒证据", "仲裁员枉法"], "required": True},
{"key": "reason_detail", "label": "理由详细说明", "type": "textarea", "required": True},
],
"claims": ["不予执行仲裁裁决", "不予执行公证债权文书"]},
# ===== 行政答辩状(1类) =====
{"title": "行政答辩状要素式模板", "category": "行政", "cause_of_action": "行政答辩状",
"applicable": "行政机关作为被告提交答辩状",
"elements": [
{"key": "case_no", "label": "案号", "type": "text", "required": True},
{"key": "plaintiff", "label": "原告信息", "type": "text", "required": True},
{"key": "original_act", "label": "被诉行政行为", "type": "textarea", "required": True},
{"key": "defense_basis", "label": "答辩依据", "type": "textarea", "required": True},
{"key": "evidence", "label": "证据清单", "type": "textarea", "required": True},
],
"claims": ["请求驳回原告诉讼请求"]},
]
def seed_remaining_templates():
"""导入剩余50类要素表模板。"""
store = KnowledgeStore()
count = 0
for tmpl in REMAINING_TEMPLATES:
cause = tmpl["cause_of_action"]
if cause in EXISTING_CAUSES:
continue
content = json.dumps(tmpl, ensure_ascii=False, indent=2)
store.add(
title=tmpl["title"],
content=content,
source="最高法法〔2025〕82号",
doc_type="element_template",
keywords=cause,
)
count += 1
print(f"已补充导入 {count} 类要素表模板")
return count
if __name__ == "__main__":
seed_remaining_templates()
+191 -15
View File
@@ -64,6 +64,97 @@ const PROFILE_VALUE_LABELS: Record<string, string> = {
// 通用 yes/no // 通用 yes/no
yes: '是', yes: '是',
no: '否', no: '否',
// 行业
retail: '零售',
manufacturing: '制造业',
tech: '科技',
service: '服务业',
finance: '金融',
realestate: '房地产',
education: '教育',
healthcare: '医疗健康',
construction: '建筑',
logistics: '物流',
agriculture: '农业',
hospitality: '餐饮酒店',
// 常见 key_facts 英文值
true: '是',
false: '否',
none: '无',
unknown: '未知',
'n/a': '不适用',
pending: '待定',
active: '活跃',
inactive: '停用',
profit: '盈利',
loss: '亏损',
expansion: '扩张',
contraction: '收缩',
stable: '稳定',
growth: '增长中',
declining: '下滑中',
}
/** 画像字段标签(dim_desc)英文 → 中文映射 */
const PROFILE_DIM_LABELS: Record<string, string> = {
// 标准画像字段
industry: '行业',
company_size: '企业规模',
role: '角色',
risk_tolerance: '风险偏好',
decision_style: '决策风格',
communication_style: '沟通偏好',
// key_facts 常见 key
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: '融资阶段',
partnership: '合伙关系',
shareholder_structure: '股权结构',
contract_dispute: '合同纠纷',
debt_amount: '债务金额',
tax_risk: '税务风险',
litigation_status: '诉讼状态',
employee_role: '员工角色',
salary_status: '薪资状况',
social_security: '社保',
severance: '经济补偿',
working_years: '工作年限',
probation_period: '试用期',
termination_reason: '解雇原因',
overtime_status: '加班情况',
export_capability: '出口能力',
expansion_plan: '扩张计划',
competitor: '竞争对手',
customer_concentration: '客户集中度',
supply_chain: '供应链',
cash_flow: '现金流',
asset_status: '资产状况',
liability: '负债',
}
/** 将画像字段的英文标签翻译为中文 */
function translateDimLabel(label: string): string {
const lower = label.toLowerCase().trim()
return PROFILE_DIM_LABELS[lower] ?? label
} }
/** 将画像字段的英文值翻译为中文显示 */ /** 将画像字段的英文值翻译为中文显示 */
@@ -179,6 +270,9 @@ export function ChatPanel({
const [factConfirm, setFactConfirm] = useState<Array<{ dim_key: string; dim_desc: string; answer: string }>>([]) const [factConfirm, setFactConfirm] = useState<Array<{ dim_key: string; dim_desc: string; answer: string }>>([])
const [factConfirmEditing, setFactConfirmEditing] = useState<Record<string, string>>({}) const [factConfirmEditing, setFactConfirmEditing] = useState<Record<string, string>>({})
const [profileFacts, setProfileFacts] = useState<Array<{ dim_key: string; dim_desc: string; answer: string }>>([]) const [profileFacts, setProfileFacts] = useState<Array<{ dim_key: string; dim_desc: string; answer: string }>>([])
const [elementTemplate, setElementTemplate] = useState<Record<string, unknown> | null>(null)
const [elementFiling, setElementFiling] = useState<{ cause_of_action: string; elements: Array<{ key: string; label: string; value: string }>; claims: string[]; filing_text: string } | null>(null)
const [generatingFiling, setGeneratingFiling] = useState(false)
const [analyzing, setAnalyzing] = useState(false) const [analyzing, setAnalyzing] = useState(false)
const fileRef = useRef<HTMLInputElement>(null) const fileRef = useRef<HTMLInputElement>(null)
/** 待切换的目标模式(销毁确认后切换) */ /** 待切换的目标模式(销毁确认后切换) */
@@ -300,12 +394,13 @@ export function ChatPanel({
// 第1步:分析问题,判断是否需要追问 // 第1步:分析问题,判断是否需要追问
setAnalyzing(true) setAnalyzing(true)
const analysis = await api.askAnalyze(currentSid, userQuestion, scene, emotionMode) const analysis = await api.askAnalyze(currentSid, userQuestion, scene, emotionMode, undefined, topic)
setAnalyzing(false) setAnalyzing(false)
// 保存画像带入事实 // 保存画像带入事实
const pf = analysis.profile_facts || [] const pf = analysis.profile_facts || []
setProfileFacts(pf) setProfileFacts(pf)
setElementTemplate(analysis.element_template || null)
if (analysis.need_ask && analysis.questions && analysis.questions.length > 0) { if (analysis.need_ask && analysis.questions && analysis.questions.length > 0) {
// 需要追问:显示追问问题 // 需要追问:显示追问问题
@@ -358,12 +453,14 @@ export function ChatPanel({
scene, scene,
emotionMode, emotionMode,
allAnswers, allAnswers,
topic,
) )
setAnalyzing(false) setAnalyzing(false)
// 更新画像带入事实 // 更新画像带入事实
const pf = analysis.profile_facts || [] const pf = analysis.profile_facts || []
setProfileFacts(pf) setProfileFacts(pf)
setElementTemplate(analysis.element_template || null)
if (analysis.need_ask && analysis.questions && analysis.questions.length > 0) { if (analysis.need_ask && analysis.questions && analysis.questions.length > 0) {
// 还需追问 // 还需追问
@@ -743,25 +840,24 @@ export function ChatPanel({
确认事实 确认事实
</div> </div>
<p className="text-xs text-blue-700">请核对以下信息,可直接修改后继续分析。确认后将更新你的用户画像。</p> <p className="text-xs text-blue-700">请核对以下信息,可直接修改后继续分析。确认后将更新你的用户画像。</p>
<div className="grid gap-2 sm:grid-cols-2"> <div className="grid grid-cols-2 gap-1.5 sm:grid-cols-3">
{factConfirm.map((f) => { {factConfirm.map((f) => {
const isProfile = profileFacts.some((p) => p.dim_key === f.dim_key) const isProfile = profileFacts.some((p) => p.dim_key === f.dim_key)
// 画像带入的字段值翻译为中文显示 const rawValue = factConfirmEditing[f.dim_key] ?? f.answer
const displayValue = isProfile const displayValue = isProfile ? translateProfileValue(rawValue) : rawValue
? (factConfirmEditing[f.dim_key] ?? translateProfileValue(f.answer))
: (factConfirmEditing[f.dim_key] ?? f.answer)
return ( return (
<div key={f.dim_key} className="space-y-0.5"> <div key={f.dim_key} className="flex items-center gap-1 rounded border border-blue-200 bg-white px-1.5 py-0.5">
<div className="flex items-center gap-1"> <Badge
<Label className="text-xs text-blue-800">{f.dim_desc}</Label> variant={isProfile ? 'secondary' : 'outline'}
{isProfile && ( className="shrink-0 text-[10px] font-medium px-1 py-0"
<Badge variant="secondary" className="text-[10px] px-1 py-0">画像</Badge> >
)} {translateDimLabel(f.dim_desc)}
</div> </Badge>
<Input <input
type="text"
value={displayValue} value={displayValue}
onChange={(e) => setFactConfirmEditing((prev) => ({ ...prev, [f.dim_key]: e.target.value }))} onChange={(e) => setFactConfirmEditing((prev) => ({ ...prev, [f.dim_key]: e.target.value }))}
className="bg-white h-8 text-sm" className="min-w-0 flex-1 border-none bg-transparent px-0.5 py-0 text-xs text-foreground focus:outline-none"
/> />
</div> </div>
) )
@@ -821,6 +917,86 @@ export function ChatPanel({
{loading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Check className="h-3.5 w-3.5" />} {loading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Check className="h-3.5 w-3.5" />}
确认并分析 确认并分析
</Button> </Button>
{scene === 'legal' && elementTemplate && (
<Button
variant="outline"
onClick={async () => {
const confirmedAnswers = factConfirm.map((f) => ({
dim_key: f.dim_key,
dim_desc: f.dim_desc,
answer: (factConfirmEditing[f.dim_key] ?? '').trim() || f.answer,
}))
setGeneratingFiling(true)
try {
const currentSid = sid || (await ensureSession())
const causeOfAction = (elementTemplate as Record<string, unknown>).cause_of_action as string || '劳动争议纠纷'
const res = await api.elementFiling(currentSid, currentQuestionRef.current, causeOfAction, confirmedAnswers, elementTemplate)
setElementFiling(res)
} catch (e) {
toast.error(e instanceof Error ? e.message : '要素式起诉状生成失败')
} finally {
setGeneratingFiling(false)
}
}}
disabled={loading || generatingFiling}
size="sm"
>
{generatingFiling ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <FileText className="h-3.5 w-3.5" />}
生成要素式起诉状
</Button>
)}
</div>
</div>
)}
{/* 要素化立案结果 */}
{elementFiling && (
<div className="rounded-lg border border-green-200 bg-green-50 p-4 space-y-3">
<div className="flex items-center gap-2 text-sm font-medium text-green-900">
<FileText className="h-4 w-4" aria-hidden />
要素式起诉状 · {elementFiling.cause_of_action}
</div>
<div className="space-y-1.5">
<p className="text-xs font-medium text-green-800">诉讼请求</p>
<div className="flex flex-wrap gap-1">
{elementFiling.claims.map((c, i) => (
<Badge key={i} variant="outline" className="text-[10px]">☐ {c}</Badge>
))}
</div>
</div>
<div className="space-y-1">
<p className="text-xs font-medium text-green-800">要素清单</p>
<div className="grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3">
{elementFiling.elements.map((e, i) => (
<div key={i} className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">{e.label}</span>
<span className="font-medium">{e.value}</span>
</div>
))}
</div>
</div>
<details className="text-xs">
<summary className="cursor-pointer text-green-800">查看完整起诉状文本</summary>
<pre className="mt-2 whitespace-pre-wrap rounded bg-white p-3 text-xs text-foreground border">{elementFiling.filing_text}</pre>
</details>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => {
navigator.clipboard.writeText(elementFiling.filing_text)
toast.success('已复制到剪贴板')
}}
>
复制全文
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setElementFiling(null)}
>
关闭
</Button>
</div> </div>
</div> </div>
)} )}
+37 -1
View File
@@ -127,6 +127,16 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
export const api = { export const api = {
health: () => request<{ status: string; service: string }>('/health'), health: () => request<{ status: string; service: string }>('/health'),
/** AI 动态生成话题列表 */
getTopics: (
scene: Scene = 'legal',
existing?: string[],
) =>
request<{ topics: Array<{ title: string; desc: string; samples: string[] }> }>('/topics', {
method: 'POST',
body: JSON.stringify({ scene, existing }),
}),
/** AI 生成示例问题(换一批) */ /** AI 生成示例问题(换一批) */
generateSamples: ( generateSamples: (
scene: Scene = 'legal', scene: Scene = 'legal',
@@ -186,6 +196,29 @@ export const api = {
getProfileHistory: () => getProfileHistory: () =>
request<{ results: Array<{ id: number; version: number; profile: Record<string, unknown>; source: string; created_at: string }> }>('/profile/me/history'), request<{ results: Array<{ id: number; version: number; profile: Record<string, unknown>; source: string; created_at: string }> }>('/profile/me/history'),
/** 要素化立案:根据确认事实 + 案由模板生成要素式起诉状 */
elementFiling: (
sid: string,
question: string,
causeOfAction: string,
factAnswers: Array<{ dim_key: string; dim_desc: string; answer: string }>,
template?: Record<string, unknown>,
) =>
request<{
cause_of_action: string
elements: Array<{ key: string; label: string; value: string }>
claims: string[]
filing_text: string
}>(`/sessions/${sid}/element-filing`, {
method: 'POST',
body: JSON.stringify({
question,
cause_of_action: causeOfAction,
fact_answers: factAnswers,
template,
}),
}),
/** 事实追问分析:返回追问问题或标记事实已充分 */ /** 事实追问分析:返回追问问题或标记事实已充分 */
askAnalyze: ( askAnalyze: (
sid: string, sid: string,
@@ -193,6 +226,7 @@ export const api = {
scene: Scene = 'legal', scene: Scene = 'legal',
emotionMode?: EmotionMode, emotionMode?: EmotionMode,
factAnswers?: Array<{ dim_key: string; dim_desc: string; answer: string }>, factAnswers?: Array<{ dim_key: string; dim_desc: string; answer: string }>,
topic?: string,
) => ) =>
request<{ request<{
need_ask: boolean need_ask: boolean
@@ -203,6 +237,7 @@ export const api = {
question_type: string question_type: string
risk_level: string risk_level: string
profile_facts?: Array<{ dim_key: string; dim_desc: string; answer: string }> profile_facts?: Array<{ dim_key: string; dim_desc: string; answer: string }>
element_template?: Record<string, unknown>
}>(`/sessions/${sid}/ask-analyze`, { }>(`/sessions/${sid}/ask-analyze`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
@@ -210,6 +245,7 @@ export const api = {
scene, scene,
emotion_mode: emotionMode, emotion_mode: emotionMode,
fact_answers: factAnswers, fact_answers: factAnswers,
topic,
}), }),
}), }),
@@ -331,7 +367,7 @@ export const api = {
}), }),
listKnowledge: (docType?: string) => listKnowledge: (docType?: string) =>
request<{ results: KnowledgeDoc[] }>( request<{ results: KnowledgeDoc[]; counts: Record<string, number>; total: number }>(
`/knowledge${docType ? `?doc_type=${docType}` : ''}`, `/knowledge${docType ? `?doc_type=${docType}` : ''}`,
), ),
+63 -78
View File
@@ -1,84 +1,51 @@
import { useState } from 'react' import { useState, useCallback } from 'react'
import { PageLayout } from '@/components/common/PageLayout' import { PageLayout } from '@/components/common/PageLayout'
import { ChatPanel } from '@/components/common/ChatPanel' import { ChatPanel } from '@/components/common/ChatPanel'
import { Card, CardContent, CardHeader } from '@/components/ui/card' 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 { cn } from '@/lib/utils'
import { api } from '@/lib/api'
/** 决策话题:每个话题对应不同的示例问题 */ /** 话题类型 */
const TOPICS = [ interface Topic {
{ title: string
icon: Store, desc: string
title: '扩张收缩', samples: string[]
desc: '开店、关店、业务线调整', }
samples: [
'现在适合扩张开第二家店吗?', /** 经营决策助手:初始兜底话题,换一批时 AI 动态生成 */
'要不要关闭亏损的业务线?', const FALLBACK_TOPICS: Topic[] = [
'新业务线该自营还是加盟?', { title: '扩张收缩', desc: '开店、关店、业务线', samples: ['现在适合扩张开第二家店吗?', '要不要关闭亏损的业务线?', '新业务线该自营还是加盟?', '应该先扩张还是先巩固?'] },
'应该先扩张还是先巩固现有市场?', { title: '项目承接', desc: '大单、长期合同', samples: ['是否应该接付款周期90天的大单?', '长期框架合同要不要签?', '预付款比例低的项目能接吗?', '客户要求独家合作利弊?'] },
], { title: '定价策略', desc: '涨价、降价、促销', samples: ['该不该降价15%抢竞品客户?', '产品涨价怎么跟客户沟通?', '促销活动会不会损害品牌?', '成本上涨如何调整定价?'] },
}, { title: '招人裁员', desc: '招聘、解雇、绩效', samples: ['现在该招人还是裁人?', '裁掉绩效差的员工怎么操作?', '关键岗位招人还是培养?', '外包和正式员工怎么选?'] },
{ { title: '融资合伙', desc: '融资、合伙、股权', samples: ['现在适合融资吗?', '合伙人要退股怎么谈条件?', '出让多少股权合适?', '银行贷款还是股权融资?'] },
icon: Handshake, { title: '转型外包', desc: '转型、外包、调整', samples: ['业务转型方向怎么选?', '哪些业务可以外包?', '转型期间现金流怎么管理?', '外包质量怎么控?'] },
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: [
'传统业务要不要转型线上?',
'非核心业务外包还是自己做?',
'要不要和同行做联合采购?',
'代工还是自建生产线?',
],
},
] ]
/** 经营决策助手:情景推演、方案对比、收益/风险/现金流/推荐程度 */
export function Decisions() { export function Decisions() {
const [topics, setTopics] = useState<Topic[]>(FALLBACK_TOPICS)
const [selectedTopic, setSelectedTopic] = useState(0) const [selectedTopic, setSelectedTopic] = useState(0)
const [topicsCollapsed, setTopicsCollapsed] = useState(false) 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 ( return (
<PageLayout <PageLayout
@@ -87,6 +54,7 @@ export function Decisions() {
> >
<Card> <Card>
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<div className="flex items-center justify-between">
<button <button
onClick={() => setTopicsCollapsed(!topicsCollapsed)} onClick={() => setTopicsCollapsed(!topicsCollapsed)}
className="flex items-center gap-1.5 text-base font-semibold transition-opacity hover:opacity-70" className="flex items-center gap-1.5 text-base font-semibold transition-opacity hover:opacity-70"
@@ -94,13 +62,28 @@ export function Decisions() {
<ChevronDown className={cn('h-4 w-4 transition-transform', topicsCollapsed && '-rotate-90')} /> <ChevronDown className={cn('h-4 w-4 transition-transform', topicsCollapsed && '-rotate-90')} />
决策话题 决策话题
</button> </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> </CardHeader>
{!topicsCollapsed && ( {!topicsCollapsed && (
<CardContent> <CardContent>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> {loadingTopics && (
{TOPICS.map((t, i) => ( <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 <button
key={t.title} key={t.title + i}
onClick={() => setSelectedTopic(i)} onClick={() => setSelectedTopic(i)}
className={cn( className={cn(
'flex items-start gap-3 rounded-md border p-3 text-left transition-colors', 'flex items-start gap-3 rounded-md border p-3 text-left transition-colors',
@@ -109,7 +92,7 @@ export function Decisions() {
: 'border-input hover:bg-accent', : 'border-input hover:bg-accent',
)} )}
> >
<t.icon className="mt-0.5 h-5 w-5 shrink-0 text-primary" aria-hidden /> <TrendingUp className="mt-0.5 h-5 w-5 shrink-0 text-primary" aria-hidden />
<div> <div>
<p className="text-sm font-medium">{t.title}</p> <p className="text-sm font-medium">{t.title}</p>
<p className="mt-0.5 text-xs text-muted-foreground">{t.desc}</p> <p className="mt-0.5 text-xs text-muted-foreground">{t.desc}</p>
@@ -121,16 +104,18 @@ export function Decisions() {
)} )}
</Card> </Card>
{currentTopic && (
<ChatPanel <ChatPanel
scene="decision" scene="decision"
samples={TOPICS[selectedTopic].samples} samples={currentTopic.samples}
topic={TOPICS[selectedTopic].title} topic={currentTopic.title}
defaultMode="normal" defaultMode="normal"
placeholder={`描述你的${TOPICS[selectedTopic].title}问题…`} placeholder={`描述你的${currentTopic.title}问题…`}
showRiskCard={false} showRiskCard={false}
enableUpload={false} enableUpload={false}
footer="AI 提供情景推演和利弊分析,最终决策由老板做出。" footer="AI 提供情景推演和利弊分析,最终决策由老板做出。"
/> />
)}
</PageLayout> </PageLayout>
) )
} }
+39 -1
View File
@@ -54,6 +54,7 @@ const DOC_TYPES = [
{ value: 'industry', label: '行业知识', icon: Factory }, { value: 'industry', label: '行业知识', icon: Factory },
{ 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 },
] ]
/** 来源英文 → 中文映射 */ /** 来源英文 → 中文映射 */
@@ -67,6 +68,8 @@ const SOURCE_LABELS: Record<string, string> = {
/** 法规知识库管理:7 类分类,录入、检索、删除 */ /** 法规知识库管理:7 类分类,录入、检索、删除 */
export function KnowledgeBase() { export function KnowledgeBase() {
const [docs, setDocs] = useState<KnowledgeDoc[]>([]) const [docs, setDocs] = useState<KnowledgeDoc[]>([])
const [counts, setCounts] = useState<Record<string, number>>({})
const [totalCount, setTotalCount] = useState(0)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [searchQuery, setSearchQuery] = useState('') const [searchQuery, setSearchQuery] = useState('')
@@ -93,6 +96,8 @@ export function KnowledgeBase() {
try { try {
const res = await api.listKnowledge(filterType !== 'all' ? filterType : undefined) const res = await api.listKnowledge(filterType !== 'all' ? filterType : undefined)
setDocs(res.results) setDocs(res.results)
setCounts(res.counts || {})
setTotalCount(res.total || 0)
} catch (e) { } catch (e) {
setError(e instanceof Error ? e.message : '加载失败') setError(e instanceof Error ? e.message : '加载失败')
} finally { } finally {
@@ -255,7 +260,7 @@ export function KnowledgeBase() {
size="sm" size="sm"
onClick={() => setFilterType('all')} onClick={() => setFilterType('all')}
> >
全部 全部 <span className="ml-1 text-xs opacity-70">{totalCount}</span>
</Button> </Button>
{DOC_TYPES.map((t) => ( {DOC_TYPES.map((t) => (
<Button <Button
@@ -265,6 +270,7 @@ export function KnowledgeBase() {
onClick={() => setFilterType(t.value)} onClick={() => setFilterType(t.value)}
> >
<t.icon className="h-3.5 w-3.5" /> {t.label} <t.icon className="h-3.5 w-3.5" /> {t.label}
<span className="ml-1 text-xs opacity-70">{counts[t.value] || 0}</span>
</Button> </Button>
))} ))}
</div> </div>
@@ -305,7 +311,39 @@ export function KnowledgeBase() {
<Badge variant="outline">相关度 {doc.score}</Badge> <Badge variant="outline">相关度 {doc.score}</Badge>
)} )}
</div> </div>
{doc.doc_type !== 'element_template' && (
<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' && (() => {
try {
const tmpl = JSON.parse(doc.content)
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, string>, i: number) => (
<div key={i} className="flex items-center justify-between">
<span className="text-muted-foreground">{e.label}</span>
<span className="font-medium">{e.type}{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>
)
} 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">
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<BookOpen className="h-3 w-3" /> 来源:{SOURCE_LABELS[doc.source] || doc.source} <BookOpen className="h-3 w-3" /> 来源:{SOURCE_LABELS[doc.source] || doc.source}
+65 -79
View File
@@ -1,92 +1,61 @@
import { useState } from 'react' import { useState, useCallback } from 'react'
import { PageLayout } from '@/components/common/PageLayout' import { PageLayout } from '@/components/common/PageLayout'
import { ChatPanel } from '@/components/common/ChatPanel' import { ChatPanel } from '@/components/common/ChatPanel'
import { Card, CardContent, CardHeader } from '@/components/ui/card' import { Card, CardContent, CardHeader } from '@/components/ui/card'
import { Briefcase, FileText, Coins, Users, Receipt, Gavel, ChevronDown } from 'lucide-react' import { Briefcase, ChevronDown, RefreshCw, Loader2 } from 'lucide-react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { api } from '@/lib/api'
/** 法务话题:每个话题对应不同的示例问题 */ /** 话题类型 */
const TOPICS = [ interface Topic {
{ title: string
icon: Briefcase, desc: string
title: '劳动用工', samples: string[]
desc: '工伤、解雇、工资、社保', }
samples: [
'员工工伤后能否开除?', /** 法务参谋:初始兜底话题,换一批时 AI 动态生成 */
'员工旷工三天怎么处理?', // 初始兜底话题(同步显示,无 loading)
'试用期不合格怎么辞退才合法?', const FALLBACK_TOPICS: Topic[] = [
'加班费计算基数怎么确定?', { title: '劳动用工', desc: '工伤、解雇、工资、社保', samples: ['员工工伤后能否开除?', '拖欠工资怎么追讨?', '未签合同双倍工资怎么算?', '社保没缴全怎么补?'] },
], { title: '合同审查', desc: '违约、条款、效力', samples: ['合同违约金怎么约定?', '对方不履行合同怎么办?', '口头协议有法律效力吗?', '合同条款不公平能改吗?'] },
}, { title: '债权债务', desc: '欠款、催收、诉讼', samples: ['客户拖欠货款3个月如何催收?', '借条怎么写才有效?', '债务人不还钱能报警吗?', '超过诉讼时效怎么办?'] },
{ { title: '股东纠纷', desc: '股权、分红、退股', samples: ['合伙人要退股怎么谈?', '股东不出资怎么办?', '分红比例怎么定?', '小股东权益被侵害怎么办?'] },
icon: FileText, { title: '税务风险', desc: '稽查、发票、合规', samples: ['收到税务稽查通知怎么办?', '发票开错了怎么处理?', '个体户怎么交税最合理?', '税务罚款能减免吗?'] },
title: '合同审查', { title: '诉讼仲裁', desc: '起诉、举证、执行', samples: ['打官司要准备哪些证据?', '仲裁和诉讼哪个更快?', '赢了官司对方不执行怎么办?', '诉讼费用怎么算?'] },
desc: '违约金、付款条款、风险点',
samples: [
'这份合同付款条款有什么风险?',
'合同违约金怎么约定才合理?',
'对方提供的格式合同有哪些坑?',
'口头协议有法律效力吗?',
],
},
{
icon: Coins,
title: '债权债务',
desc: '催款、欠款、债务纠纷',
samples: [
'客户拖欠货款 3 个月如何催收?',
'欠款人跑了,钱还能要回来吗?',
'供应商要求先款后货,有风险吗?',
'债务超过诉讼时效怎么办?',
],
},
{
icon: Users,
title: '股东纠纷',
desc: '股权、分红、退股',
samples: [
'合伙人要退股,怎么谈条件?',
'小股东查账被拒绝怎么办?',
'股东会决议不执行怎么处理?',
'隐名股东权益如何保护?',
],
},
{
icon: Receipt,
title: '税务风险',
desc: '稽查、发票、合规',
samples: [
'收到税务稽查通知怎么办?',
'公司缺进项发票怎么解决?',
'个税汇算清缴有哪些风险点?',
'关联交易定价被质疑怎么办?',
],
},
{
icon: Gavel,
title: '诉讼仲裁',
desc: '起诉、应诉、执行',
samples: [
'官司打赢了对方不执行怎么办?',
'收到法院传票要不要请律师?',
'仲裁和诉讼哪个更适合?',
'诉讼时效快到了还能起诉吗?',
],
},
] ]
/** 法务参谋:劳动用工、合同审查、债权债务、股东纠纷、税务风险 */
export function LegalAdvisor() { export function LegalAdvisor() {
const [topics, setTopics] = useState<Topic[]>(FALLBACK_TOPICS)
const [selectedTopic, setSelectedTopic] = useState(0) const [selectedTopic, setSelectedTopic] = useState(0)
const [topicsCollapsed, setTopicsCollapsed] = useState(false) const [topicsCollapsed, setTopicsCollapsed] = useState(false)
const [loadingTopics, setLoadingTopics] = useState(false)
/** 换一批:调用 AI 动态生成话题(避免重复) */
const refreshTopics = useCallback(async () => {
setLoadingTopics(true)
try {
const res = await api.getTopics('legal', 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 ( return (
<PageLayout <PageLayout
title="法务参谋" title="法务参谋"
description="劳动用工、合同审查、债权债务、股东纠纷、税务风险。AI 输出不替代执业律师正式意见。" description="劳动用工、合同审查、债权债务、股东纠纷、税务风险、诉讼仲裁。AI 输出不替代执业律师正式意见。"
> >
<Card> <Card>
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<div className="flex items-center justify-between">
<button <button
onClick={() => setTopicsCollapsed(!topicsCollapsed)} onClick={() => setTopicsCollapsed(!topicsCollapsed)}
className="flex items-center gap-1.5 text-base font-semibold transition-opacity hover:opacity-70" className="flex items-center gap-1.5 text-base font-semibold transition-opacity hover:opacity-70"
@@ -94,13 +63,28 @@ export function LegalAdvisor() {
<ChevronDown className={cn('h-4 w-4 transition-transform', topicsCollapsed && '-rotate-90')} /> <ChevronDown className={cn('h-4 w-4 transition-transform', topicsCollapsed && '-rotate-90')} />
法务话题 法务话题
</button> </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> </CardHeader>
{!topicsCollapsed && ( {!topicsCollapsed && (
<CardContent> <CardContent>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> {loadingTopics && (
{TOPICS.map((t, i) => ( <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 <button
key={t.title} key={t.title + i}
onClick={() => setSelectedTopic(i)} onClick={() => setSelectedTopic(i)}
className={cn( className={cn(
'flex items-start gap-3 rounded-md border p-3 text-left transition-colors', 'flex items-start gap-3 rounded-md border p-3 text-left transition-colors',
@@ -109,7 +93,7 @@ export function LegalAdvisor() {
: 'border-input hover:bg-accent', : 'border-input hover:bg-accent',
)} )}
> >
<t.icon className="mt-0.5 h-5 w-5 shrink-0 text-primary" aria-hidden /> <Briefcase className="mt-0.5 h-5 w-5 shrink-0 text-primary" aria-hidden />
<div> <div>
<p className="text-sm font-medium">{t.title}</p> <p className="text-sm font-medium">{t.title}</p>
<p className="mt-0.5 text-xs text-muted-foreground">{t.desc}</p> <p className="mt-0.5 text-xs text-muted-foreground">{t.desc}</p>
@@ -121,16 +105,18 @@ export function LegalAdvisor() {
)} )}
</Card> </Card>
{currentTopic && (
<ChatPanel <ChatPanel
scene="legal" scene="legal"
samples={TOPICS[selectedTopic].samples} samples={currentTopic.samples}
topic={TOPICS[selectedTopic].title} topic={currentTopic.title}
defaultMode="confidential" defaultMode="confidential"
placeholder={`描述你的${TOPICS[selectedTopic].title}问题…`} placeholder={`描述你的${currentTopic.title}问题…`}
showRiskCard showRiskCard
enableUpload enableUpload
footer="AI 输出仅供参考,不构成律师、会计师、税务师或医生的正式意见。重大风险事项请寻求执业专业人士复核。" footer="AI 输出仅供参考,不构成律师、会计师、税务师或医生的正式意见。重大风险事项请寻求执业专业人士复核。"
/> />
)}
</PageLayout> </PageLayout>
) )
} }
+66 -75
View File
@@ -1,100 +1,89 @@
import { useState } from 'react' import { useState, useCallback } from 'react'
import { PageLayout } from '@/components/common/PageLayout' import { PageLayout } from '@/components/common/PageLayout'
import { ChatPanel } from '@/components/common/ChatPanel' import { ChatPanel } from '@/components/common/ChatPanel'
import { Card, CardContent, CardHeader } from '@/components/ui/card' import { Card, CardContent, CardHeader } from '@/components/ui/card'
import { Heart, Users, Plane, Moon, GraduationCap, Stethoscope, ChevronDown } from 'lucide-react' import { Heart, ChevronDown, RefreshCw, Loader2 } from 'lucide-react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { api } from '@/lib/api'
/** 话题定义:每个话题对应不同的示例问题 */ /** 话题类型 */
const TOPICS = [ interface Topic {
{ title: string
icon: Users, desc: string
title: '家庭沟通', samples: string[]
desc: '伴侣、子女、父母关系', }
samples: [
'怎么和青春期的孩子沟通不想他接班的事?', /** 生活助理:初始兜底话题,换一批时 AI 动态生成 */
'和伴侣在花钱观念上有分歧,怎么谈?', const FALLBACK_TOPICS: Topic[] = [
'父母总是干涉经营决策,怎么温和地划清边界?', { title: '家庭沟通', desc: '亲子、夫妻、关系', samples: ['怎么和青春期的孩子沟通?', '夫妻因工作吵架怎么办?', '家人不支持创业怎么说服?', '工作太忙忽略家人怎么办?'] },
], { title: '子女教育', desc: '升学、兴趣、规划', samples: ['孩子不想上学怎么办?', '兴趣班该报几个?', '要不要送孩子出国?', '孩子成绩下滑怎么沟通?'] },
}, { title: '父母养老', desc: '养老、医疗、陪伴', samples: ['父母年纪大了养老方案怎么选?', '异地父母怎么照顾?', '养老院还是居家养老?', '父母医疗费用怎么分担?'] },
{ { title: '睡眠与压力', desc: '失眠、焦虑、调节', samples: ['最近睡眠很差怎么改善?', '工作压力大怎么缓解?', '焦虑到影响工作怎么办?', '怎么判断需要看心理医生?'] },
icon: GraduationCap, { title: '出差日程', desc: '出行、时间管理', samples: ['出差频繁怎么兼顾家庭?', '行程太满怎么优化?', '出差期间公司怎么管?', '如何减少无效出差?'] },
title: '子女教育', { title: '健康习惯', desc: '体检、饮食、运动', samples: ['体检报告几项异常怎么办?', '久坐怎么改善健康?', '应酬多怎么控制饮食?', '没时间运动怎么办?'] },
desc: '升学、留学、职业方向',
samples: [
'孩子中考成绩一般,选普高还是国际学校?',
'要不要送孩子出国留学?什么阶段出去最合适?',
'孩子想学艺术,但担心就业,该怎么引导?',
],
},
{
icon: Heart,
title: '父母养老',
desc: '照护方案、医疗安排',
samples: [
'父母年纪大了,居家养老还是去养老机构?',
'父母需要长期服药,怎么管理用药和复诊?',
'怎么和兄弟姐妹分担父母的养老责任?',
],
},
{
icon: Moon,
title: '睡眠与压力',
desc: '身心状态管理',
samples: [
'最近睡眠很差,有什么改善建议?',
'经营压力大,经常焦虑,怎么自我调节?',
'长期高强度工作,怎么避免职业倦怠?',
],
},
{
icon: Plane,
title: '出差日程',
desc: '行程、商务礼仪',
samples: [
'下周出差三天,怎么安排最高效?',
'第一次见重要客户,商务礼仪要注意什么?',
'出差期间公司有事,怎么远程处理不耽误?',
],
},
{
icon: Stethoscope,
title: '健康习惯',
desc: '体检、运动、饮食',
samples: [
'体检报告有几项异常,需要进一步检查吗?',
'久坐办公,每天多少运动量才够?',
'应酬多,怎么调整饮食减少对身体的负担?',
],
},
] ]
/** 老板生活助理:降低非经营决策负担 */
export function LifeAssistant() { export function LifeAssistant() {
const [topics, setTopics] = useState<Topic[]>(FALLBACK_TOPICS)
const [selectedTopic, setSelectedTopic] = useState(0) const [selectedTopic, setSelectedTopic] = useState(0)
const [topicsCollapsed, setTopicsCollapsed] = useState(false) 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 ( return (
<PageLayout <PageLayout
title="老板生活助理" title="生活助理"
description="家庭、健康、出差、重要事项。帮你处理经营之外的事,把精力留给关键决策。" description="家庭沟通、子女教育、父母养老、健康习惯、出行规划。老板的生活问题,也值得认真对待。"
> >
<Card> <Card>
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<div className="flex items-center justify-between">
<button <button
onClick={() => setTopicsCollapsed(!topicsCollapsed)} onClick={() => setTopicsCollapsed(!topicsCollapsed)}
className="flex items-center gap-1.5 text-base font-semibold transition-opacity hover:opacity-70" 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')} /> <ChevronDown className={cn('h-4 w-4 transition-transform', topicsCollapsed && '-rotate-90')} />
支持话题 生活话题
</button> </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> </CardHeader>
{!topicsCollapsed && ( {!topicsCollapsed && (
<CardContent> <CardContent>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> {loadingTopics && (
{TOPICS.map((t, i) => ( <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 <button
key={t.title} key={t.title + i}
onClick={() => setSelectedTopic(i)} onClick={() => setSelectedTopic(i)}
className={cn( className={cn(
'flex items-start gap-3 rounded-md border p-3 text-left transition-colors', 'flex items-start gap-3 rounded-md border p-3 text-left transition-colors',
@@ -103,7 +92,7 @@ export function LifeAssistant() {
: 'border-input hover:bg-accent', : 'border-input hover:bg-accent',
)} )}
> >
<t.icon className="mt-0.5 h-5 w-5 shrink-0 text-primary" aria-hidden /> <Heart className="mt-0.5 h-5 w-5 shrink-0 text-primary" aria-hidden />
<div> <div>
<p className="text-sm font-medium">{t.title}</p> <p className="text-sm font-medium">{t.title}</p>
<p className="mt-0.5 text-xs text-muted-foreground">{t.desc}</p> <p className="mt-0.5 text-xs text-muted-foreground">{t.desc}</p>
@@ -115,16 +104,18 @@ export function LifeAssistant() {
)} )}
</Card> </Card>
{currentTopic && (
<ChatPanel <ChatPanel
scene="life" scene="life"
samples={TOPICS[selectedTopic].samples} samples={currentTopic.samples}
topic={TOPICS[selectedTopic].title} topic={currentTopic.title}
defaultMode="confidential" defaultMode="confidential"
placeholder={`描述你的${TOPICS[selectedTopic].title}问题…`} placeholder={`描述你的${currentTopic.title}问题…`}
showRiskCard={false} showRiskCard={false}
enableUpload={false} enableUpload={false}
footer="AI 输出仅供参考,重大事项请咨询相关领域专业人士。" footer="AI 输出仅供参考,重大事项请咨询相关领域专业人士。"
/> />
)}
</PageLayout> </PageLayout>
) )
} }
+257 -29
View File
@@ -60,6 +60,213 @@ const RISK_LABELS: Record<string, string> = {
aggressive: '进取型', aggressive: '进取型',
} }
/** 角色映射 */
const ROLE_LABELS: Record<string, string> = {
owner: '老板/创始人',
manager: '管理者',
ceo: 'CEO',
director: '总监',
}
/** 决策风格映射 */
const DECISION_STYLE_LABELS: Record<string, string> = {
'data-driven': '数据驱动型',
intuitive: '直觉型',
collaborative: '协作型',
}
/** 沟通偏好映射 */
const COMMUNICATION_STYLE_LABELS: Record<string, string> = {
concise: '简洁',
detailed: '详细',
structured: '结构化',
}
/** 行业映射 */
const INDUSTRY_LABELS: Record<string, string> = {
retail: '零售',
manufacturing: '制造业',
tech: '科技',
service: '服务业',
finance: '金融',
realestate: '房地产',
education: '教育',
healthcare: '医疗健康',
construction: '建筑',
logistics: '物流',
agriculture: '农业',
hospitality: '餐饮酒店',
}
/** key_facts 的 key 中文映射 */
const KEY_FACT_LABELS: Record<string, string> = {
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: '法律依据',
}
/** key_facts 的值中文映射 */
const KEY_FACT_VALUE_LABELS: Record<string, string> = {
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,
}
/** 场景中文映射 */ /** 场景中文映射 */
const SCENE_LABELS: Record<string, string> = { const SCENE_LABELS: Record<string, string> = {
legal: '法务参谋', legal: '法务参谋',
@@ -87,9 +294,24 @@ function formatTime(dt: string): string {
function formatValue(key: string, value: string): string { function formatValue(key: string, value: string): string {
if (key === 'company_size') return SIZE_LABELS[value] || value if (key === 'company_size') return SIZE_LABELS[value] || value
if (key === 'risk_tolerance') return RISK_LABELS[value] || value if (key === 'risk_tolerance') return RISK_LABELS[value] || value
if (key === 'role') return ROLE_LABELS[value] || value
if (key === 'decision_style') return DECISION_STYLE_LABELS[value] || value
if (key === 'communication_style') return COMMUNICATION_STYLE_LABELS[value] || value
if (key === 'industry') return INDUSTRY_LABELS[value] || value
return value return value
} }
/** 翻译 key_facts 的 key */
function translateFactKey(key: string): string {
return KEY_FACT_LABELS[key.toLowerCase()] || key
}
/** 翻译 key_facts 的值 */
function translateFactValue(value: string): string {
const lower = value.toLowerCase().trim()
return KEY_FACT_VALUE_LABELS[lower] || value
}
export function MyProfile() { export function MyProfile() {
const [profile, setProfile] = useState<ProfileData | null>(null) const [profile, setProfile] = useState<ProfileData | null>(null)
const [history, setHistory] = useState<ProfileHistory[]>([]) const [history, setHistory] = useState<ProfileHistory[]>([])
@@ -136,45 +358,48 @@ export function MyProfile() {
<Card> <Card>
<CardContent className="p-5"> <CardContent className="p-5">
<div className="space-y-3"> <div className="space-y-3">
{/* 标准字段:多列网格 */}
<div className="grid grid-cols-2 gap-x-4 gap-y-1.5 sm:grid-cols-3">
{Object.entries(FIELD_LABELS).map(([key, label]) => { {Object.entries(FIELD_LABELS).map(([key, label]) => {
const val = hp[key] as string const val = hp[key] as string
if (!val) return null if (!val) return null
return ( return (
<div key={key} className="flex items-center justify-between border-b pb-2"> <div key={key} className="flex items-center justify-between border-b pb-1">
<span className="text-sm text-muted-foreground">{label}</span> <span className="text-xs text-muted-foreground">{label}</span>
<span className="text-sm font-medium">{formatValue(key, val)}</span> <span className="text-xs font-medium">{formatValue(key, val)}</span>
</div> </div>
) )
})} })}
</div>
{Array.isArray(hp.common_topics) && (hp.common_topics as string[]).length > 0 && ( {Array.isArray(hp.common_topics) && (hp.common_topics as string[]).length > 0 && (
<div className="border-b pb-2"> <div className="border-b pb-2">
<p className="mb-1.5 text-sm text-muted-foreground">常问话题</p> <p className="mb-1 text-xs text-muted-foreground">常问话题</p>
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1">
{(hp.common_topics as string[]).map((t, i) => ( {(hp.common_topics as string[]).map((t, i) => (
<Badge key={i} variant="secondary" className="text-xs">{String(t)}</Badge> <Badge key={i} variant="secondary" className="text-[10px] px-1 py-0">{String(t)}</Badge>
))} ))}
</div> </div>
</div> </div>
)} )}
{Boolean(hp.key_facts && typeof hp.key_facts === 'object' && !Array.isArray(hp.key_facts) && Object.keys(hp.key_facts as Record<string, unknown>).length > 0) && ( {Boolean(hp.key_facts && typeof hp.key_facts === 'object' && !Array.isArray(hp.key_facts) && Object.keys(hp.key_facts as Record<string, unknown>).length > 0) && (
<div className="border-b pb-2"> <div className="border-b pb-2">
<p className="mb-1.5 text-sm text-muted-foreground">关键事实</p> <p className="mb-1 text-xs text-muted-foreground">关键事实</p>
<div className="space-y-1"> <div className="grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3">
{Object.entries(hp.key_facts as Record<string, string>).map(([k, v]) => ( {Object.entries(hp.key_facts as Record<string, string>).map(([k, v]) => (
<div key={k} className="flex items-center justify-between text-xs"> <div key={k} className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">{k}</span> <span className="text-muted-foreground">{translateFactKey(k)}</span>
<span className="font-medium">{String(v)}</span> <span className="font-medium">{translateFactValue(String(v))}</span>
</div> </div>
))} ))}
</div> </div>
</div> </div>
)} )}
{Array.isArray(hp.common_scenes) && (hp.common_scenes as string[]).length > 0 && ( {Array.isArray(hp.common_scenes) && (hp.common_scenes as string[]).length > 0 && (
<div> <div className="flex items-center gap-2">
<p className="mb-1.5 text-sm text-muted-foreground">常用场景</p> <span className="text-xs text-muted-foreground">常用场景</span>
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1">
{(hp.common_scenes as string[]).map((s, i) => ( {(hp.common_scenes as string[]).map((s, i) => (
<Badge key={i} variant="outline" className="text-xs"> <Badge key={i} variant="outline" className="text-[10px] px-1 py-0">
{SCENE_LABELS[String(s)] || String(s)} {SCENE_LABELS[String(s)] || String(s)}
</Badge> </Badge>
))} ))}
@@ -206,23 +431,26 @@ export function MyProfile() {
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{/* 标准字段:多列网格 */}
<div className="grid grid-cols-2 gap-x-4 gap-y-1.5 sm:grid-cols-3">
{Object.entries(FIELD_LABELS).map(([key, label]) => { {Object.entries(FIELD_LABELS).map(([key, label]) => {
const val = profile[key as keyof ProfileData] as string const val = profile[key as keyof ProfileData] as string
if (!val) return null if (!val) return null
return ( return (
<div key={key} className="flex items-center justify-between border-b pb-2"> <div key={key} className="flex items-center justify-between border-b pb-1">
<span className="text-sm text-muted-foreground">{label}</span> <span className="text-xs text-muted-foreground">{label}</span>
<span className="text-sm font-medium">{formatValue(key, val)}</span> <span className="text-xs font-medium">{formatValue(key, val)}</span>
</div> </div>
) )
})} })}
</div>
{profile.common_topics && profile.common_topics.length > 0 && ( {profile.common_topics && profile.common_topics.length > 0 && (
<div className="border-b pb-2"> <div className="border-b pb-2">
<p className="mb-1.5 text-sm text-muted-foreground">常问话题</p> <p className="mb-1 text-xs text-muted-foreground">常问话题</p>
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1">
{profile.common_topics.map((t, i) => ( {profile.common_topics.map((t, i) => (
<Badge key={i} variant="secondary" className="text-xs">{t}</Badge> <Badge key={i} variant="secondary" className="text-[10px] px-1 py-0">{t}</Badge>
))} ))}
</div> </div>
</div> </div>
@@ -230,12 +458,12 @@ export function MyProfile() {
{profile.key_facts && Object.keys(profile.key_facts).length > 0 && ( {profile.key_facts && Object.keys(profile.key_facts).length > 0 && (
<div className="border-b pb-2"> <div className="border-b pb-2">
<p className="mb-1.5 text-sm text-muted-foreground">关键事实</p> <p className="mb-1 text-xs text-muted-foreground">关键事实</p>
<div className="space-y-1"> <div className="grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3">
{Object.entries(profile.key_facts).map(([k, v]) => ( {Object.entries(profile.key_facts).map(([k, v]) => (
<div key={k} className="flex items-center justify-between text-xs"> <div key={k} className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">{k}</span> <span className="text-muted-foreground">{translateFactKey(k)}</span>
<span className="font-medium">{v}</span> <span className="font-medium">{translateFactValue(v)}</span>
</div> </div>
))} ))}
</div> </div>
@@ -243,11 +471,11 @@ export function MyProfile() {
)} )}
{profile.common_scenes && profile.common_scenes.length > 0 && ( {profile.common_scenes && profile.common_scenes.length > 0 && (
<div> <div className="flex items-center gap-2">
<p className="mb-1.5 text-sm text-muted-foreground">常用场景</p> <span className="text-xs text-muted-foreground">常用场景</span>
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1">
{profile.common_scenes.map((s, i) => ( {profile.common_scenes.map((s, i) => (
<Badge key={i} variant="outline" className="text-xs"> <Badge key={i} variant="outline" className="text-[10px] px-1 py-0">
{SCENE_LABELS[s] || s} {SCENE_LABELS[s] || s}
</Badge> </Badge>
))} ))}
@@ -256,7 +484,7 @@ export function MyProfile() {
)} )}
{profile.last_active && ( {profile.last_active && (
<p className="pt-2 text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
最后活跃:{formatTime(profile.last_active)} 最后活跃:{formatTime(profile.last_active)}
</p> </p>
)} )}