feat: 老板台2.0 + 手机端适配 + 专家库 + 智囊入口
- 老板台: AI执行摘要卡 + 待办优先级排序 + 时间线视图 + 环形健康度 - 手机端: 事项看板折叠列表 + 7个页面响应式修复 + 底部安全边距 - 专家库: 新增用户端专家浏览页(参考管理端卡片风格) - 智囊入口: 底部Tab加"智囊" + 4个智囊页场景切换栏 + "我"页分组入口 - FAB弹窗: 场景快速入口 + 指令卡片优化 + 输入区sticky - 后端: /dashboard/summary + /dashboard/timeline API - 后端: life场景事实维度扩充(2→5) + 17个生活关键词优先级 - 前端: 抑制React DevTools startTime噪音错误 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -2410,6 +2410,172 @@ def dashboard_today(authorization: str | None = Header(default=None)):
|
||||
conn.close()
|
||||
|
||||
|
||||
@app.get("/dashboard/summary")
|
||||
def dashboard_summary(authorization: str | None = Header(default=None)):
|
||||
"""老板台执行摘要:AI 用一句话总结当前状态和建议行动。
|
||||
降级策略:Kimi 不可用时返回规则摘要。
|
||||
"""
|
||||
phone = get_current_phone(authorization)
|
||||
if not phone:
|
||||
raise HTTPException(401, "未登录")
|
||||
conn = _matter_dsn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"SELECT id, title, risk_level, scene, status FROM matters "
|
||||
"WHERE user_phone = %s AND status != 'closed' ORDER BY updated_at DESC LIMIT 10",
|
||||
(phone,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
open_matters = [{"id": r[0], "title": r[1], "risk_level": r[2], "scene": r[3], "status": r[4]} for r in rows]
|
||||
cur.execute(
|
||||
"SELECT COUNT(*) FROM matters WHERE user_phone = %s AND status = 'pending_confirm'",
|
||||
(phone,),
|
||||
)
|
||||
pending_count = cur.fetchone()[0]
|
||||
cur.execute(
|
||||
"SELECT COUNT(*) FROM expert_consultations WHERE user_phone = %s AND status = 'replied'",
|
||||
(phone,),
|
||||
)
|
||||
reply_count = cur.fetchone()[0]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
red_count = sum(1 for m in open_matters if m["risk_level"] == "red")
|
||||
orange_count = sum(1 for m in open_matters if m["risk_level"] == "orange")
|
||||
|
||||
# 规则摘要(兜底)
|
||||
parts = []
|
||||
if red_count > 0:
|
||||
parts.append(f"有 {red_count} 项高风险事项需立即处理")
|
||||
if orange_count > 0:
|
||||
parts.append(f"{orange_count} 项中风险事项需关注")
|
||||
if pending_count > 0:
|
||||
parts.append(f"{pending_count} 个事项待你确认")
|
||||
if reply_count > 0:
|
||||
parts.append(f"{reply_count} 条专家回复待查看")
|
||||
if not parts:
|
||||
rule_summary = "当前无待办事项,经营状况良好。可主动发起咨询或查看经营健康度。"
|
||||
rule_action = "浏览智囊或查看事项看板"
|
||||
else:
|
||||
rule_summary = "、".join(parts) + "。"
|
||||
if red_count > 0:
|
||||
rule_action = "建议优先处理高风险事项"
|
||||
elif pending_count > 0:
|
||||
rule_action = "建议先确认待办事项"
|
||||
elif reply_count > 0:
|
||||
rule_action = "建议查看专家回复"
|
||||
else:
|
||||
rule_action = "建议关注中风险事项"
|
||||
|
||||
# 尝试 AI 生成更自然的摘要
|
||||
try:
|
||||
if kimi and open_matters:
|
||||
matter_desc = "\n".join(
|
||||
f"- {m['title']}(风险:{m['risk_level']},场景:{m['scene']},状态:{m['status']})"
|
||||
for m in open_matters[:5]
|
||||
)
|
||||
prompt = (
|
||||
f"你是中小企业老板的AI助手。请根据以下数据,用一句话(不超过50字)总结当前经营状态,"
|
||||
f"并给出一个最优先的行动建议(不超过20字)。\n\n"
|
||||
f"待办事项:\n{matter_desc}\n"
|
||||
f"待确认:{pending_count}个,专家回复:{reply_count}条\n\n"
|
||||
f"返回 JSON 格式:{{\"summary\": \"...\", \"action\": \"...\"}}"
|
||||
)
|
||||
answer = kimi.chat(
|
||||
[{"role": "system", "content": "你是经营顾问助手,只返回JSON。"},
|
||||
{"role": "user", "content": prompt}],
|
||||
temperature=0.3, timeout=15,
|
||||
)
|
||||
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.replace("\u201c", '"').replace("\u201d", '"').strip()
|
||||
start = answer.find("{")
|
||||
end = answer.rfind("}")
|
||||
if start != -1 and end != -1:
|
||||
import json as _json
|
||||
parsed = _json.loads(answer[start:end + 1])
|
||||
return {
|
||||
"summary": parsed.get("summary", rule_summary),
|
||||
"action": parsed.get("action", rule_action),
|
||||
"ai_generated": True,
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {"summary": rule_summary, "action": rule_action, "ai_generated": False}
|
||||
|
||||
|
||||
@app.get("/dashboard/timeline")
|
||||
def dashboard_timeline(authorization: str | None = Header(default=None)):
|
||||
"""老板台时间线:按时间倒序聚合最近活动(案卡变更、专家回复、风险推送)。"""
|
||||
phone = get_current_phone(authorization)
|
||||
if not phone:
|
||||
raise HTTPException(401, "未登录")
|
||||
conn = _matter_dsn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
items = []
|
||||
# 案卡最近更新
|
||||
cur.execute(
|
||||
"SELECT id, title, scene, status, risk_level, updated_at "
|
||||
"FROM matters WHERE user_phone = %s ORDER BY updated_at DESC LIMIT 8",
|
||||
(phone,),
|
||||
)
|
||||
for r in cur.fetchall():
|
||||
items.append({
|
||||
"type": "matter",
|
||||
"id": r[0],
|
||||
"title": r[1],
|
||||
"scene": r[2],
|
||||
"status": r[3],
|
||||
"risk_level": r[4],
|
||||
"time": r[5].isoformat() if r[5] else None,
|
||||
})
|
||||
# 专家回复
|
||||
cur.execute(
|
||||
"SELECT c.id, e.name, e.title, c.reply, c.replied_at "
|
||||
"FROM expert_consultations c JOIN experts e ON c.expert_id = e.id "
|
||||
"WHERE c.user_phone = %s AND c.status = 'replied' "
|
||||
"ORDER BY c.replied_at DESC LIMIT 5",
|
||||
(phone,),
|
||||
)
|
||||
for r in cur.fetchall():
|
||||
items.append({
|
||||
"type": "expert_reply",
|
||||
"id": r[0],
|
||||
"expert_name": r[1],
|
||||
"expert_title": r[2],
|
||||
"snippet": (r[3] or "")[:60],
|
||||
"time": r[4].isoformat() if r[4] else None,
|
||||
})
|
||||
# 风险推送
|
||||
try:
|
||||
cur.execute(
|
||||
"SELECT id, title, level, created_at FROM risk_alerts "
|
||||
"WHERE user_phone = %s ORDER BY created_at DESC LIMIT 5",
|
||||
(phone,),
|
||||
)
|
||||
for r in cur.fetchall():
|
||||
items.append({
|
||||
"type": "risk_alert",
|
||||
"id": r[0],
|
||||
"title": r[1],
|
||||
"level": r[2],
|
||||
"time": r[3].isoformat() if r[3] else None,
|
||||
})
|
||||
except Exception:
|
||||
pass # risk_alerts 表可能不存在
|
||||
# 按时间倒序
|
||||
items.sort(key=lambda x: x.get("time") or "", reverse=True)
|
||||
return {"results": items[:15]}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@app.get("/team")
|
||||
def my_team(authorization: str | None = Header(default=None)):
|
||||
"""我的团队:咨询过的专家自动沉淀"""
|
||||
|
||||
Reference in New Issue
Block a user