From db5d6763f1d2b03acd77303f81916b0a75fdde95 Mon Sep 17 00:00:00 2001 From: NathanAlex7 <18898599977@163.com> Date: Tue, 1 Sep 2026 17:24:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(xingbang):=20=E5=85=B4=E9=82=A6=E9=87=91?= =?UTF-8?q?=E7=A7=9F=20AI=20=E8=AF=84=E5=AE=A1=E5=B9=B3=E5=8F=B0=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E9=A1=B5=E9=9D=A2(=E6=B7=B1=E8=89=B2=E5=95=86?= =?UTF-8?q?=E5=8A=A1=E9=A3=8E)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 独立路由 /xingbang: 兴邦评审工作台 - 五智能体流水线可视化(状态动画: 排队/运行/成功/失败) - 评审任务表单(客户/金额/租赁物) + SSE 实时进度 - 审批结论卡片(五智能体产出) + 自进化洞察面板 - Navbar 加兴邦评审入口, 支持返回聊天 - 深色商务风 (渐变背景/玻璃拟态/金蓝主题) - api.ts 新增兴邦 API 调用(workflow/evolution/trajectories) 前端已在 next dev 验证渲染 OK, 后端 API 就绪(7条轨迹样本) --- src/app/xingbang/page.tsx | 293 +++++++++++++++++++++++++++ src/components/layout/Navbar.tsx | 8 + src/components/xingbang/xingbang.css | 216 ++++++++++++++++++++ src/lib/api.ts | 77 +++++++ 4 files changed, 594 insertions(+) create mode 100644 src/app/xingbang/page.tsx create mode 100644 src/components/xingbang/xingbang.css diff --git a/src/app/xingbang/page.tsx b/src/app/xingbang/page.tsx new file mode 100644 index 0000000..86998fa --- /dev/null +++ b/src/app/xingbang/page.tsx @@ -0,0 +1,293 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import "../../components/xingbang/xingbang.css"; +import { + streamXingbangWorkflow, + getXingbangEvolution, +} from "@/lib/api"; + +/* ── 五智能体定义(含图标) ── */ +const AGENTS = [ + { key: "data_curator", name: "数据汇聚官", role: "多源数据 → 情报卡", icon: "🗂️", desc: "拉齐 15 类异构数据源" }, + { key: "risk_scout", name: "预审参谋", role: "情报卡 → 尽调初稿", icon: "🔍", desc: "穿透分析风险点" }, + { key: "case_finder", name: "经验检索", role: "案例库 → 参考意见", icon: "📚", desc: "带编号可追溯案例" }, + { key: "approval_advisor", name: "审批顾问", role: "汇总 → 三档审批", icon: "⚖️", desc: "档位判定 + 预填流转" }, + { key: "risk_monitor", name: "风险哨兵", role: "审批前 · 动态复核", icon: "🛡️", desc: "风控信号转变预警" }, +]; +const AGENT_MAP = Object.fromEntries(AGENTS.map((a) => [a.key, a])); + +type AgentStatus = "pending" | "running" | "done" | "error"; +interface PipelineState { + [key: string]: AgentStatus; +} + +interface AgentResult { + success?: boolean; + output?: string; + file?: string | null; +} + +/* ── 主页面 ── */ +export default function XingbangPage() { + const [task, setTask] = useState(""); + const [customer, setCustomer] = useState(""); + const [amount, setAmount] = useState(""); + const [asset, setAsset] = useState(""); + + const [status, setStatus] = useState<"idle" | "running" | "done" | "error">("idle"); + const [pipeline, setPipeline] = useState({}); + const [results, setResults] = useState>({}); + const [finalSummary, setFinalSummary] = useState([]); + const [reward, setReward] = useState(null); + const [trajectoryId, setTrajectoryId] = useState(null); + const [activeOutput, setActiveOutput] = useState(""); + const [evolution, setEvolution] = useState(""); + const [error, setError] = useState(""); + const [runId, setRunId] = useState(0); // 用于重跑 + + const loadEvolution = useCallback(async () => { + try { + const ev = await getXingbangEvolution(); + if (ev && typeof ev.insights === "string") setEvolution(ev.insights); + } catch { + /* 静默 */ + } + }, []); + useEffect(() => { loadEvolution(); }, [loadEvolution]); + + const runPipeline = useCallback(async () => { + setError(""); + setResults({}); + setFinalSummary([]); + setReward(null); + setTrajectoryId(null); + setActiveOutput(""); + setStatus("running"); + setPipeline({ data_curator: "pending", risk_scout: "pending", case_finder: "pending", approval_advisor: "pending", risk_monitor: "pending" }); + + const taskDesc = [ + customer || `客户`, + amount ? `申请${amount}万` : "", + asset ? `购置${asset}` : "", + `融资租赁评审`, + ].filter(Boolean).join(" "); + + try { + for await (const ev of streamXingbangWorkflow(taskDesc, customer)) { + if (ev.event === "agent_start") { + setPipeline((p) => ({ ...p, [ev.data.agent as string]: "running" })); + } else if (ev.event === "agent_end") { + const agent = ev.data.agent as string; + setPipeline((p) => ({ ...p, [agent]: ev.data.success ? "done" : "error" })); + setResults((r) => ({ + ...r, + [agent]: { + success: Boolean(ev.data.success), + output: typeof ev.data.output === "string" ? ev.data.output : undefined, + file: typeof ev.data.file === "string" ? ev.data.file : null, + }, + })); + if (typeof ev.data.output === "string") setActiveOutput(ev.data.output); + } else if (ev.event === "done") { + setReward(Number(ev.data.reward ?? 0)); + setTrajectoryId((ev.data.trajectory_id as string) ?? null); + setStatus("done"); + setPipeline({ data_curator: "done", risk_scout: "done", case_finder: "done", approval_advisor: "done", risk_monitor: "done" }); + loadEvolution(); + } + } + } catch (e: any) { + setStatus("error"); + setError(String(e?.message ?? e)); + } + }, [customer, amount, asset, loadEvolution]); + + const pipelineOrder = Object.keys(pipeline).length + ? AGENTS.filter((a) => a.key in pipeline) + : AGENTS; + + return ( +
+ {/* 顶部品牌 */} +
+
+
+ 🏛️ +
+
+
兴邦金租 · AI 评审平台
+
基于 mini-OpenClaw · 五智能体协同 · 自进化决策引擎
+
+
+
+ + + 引擎在线 + + v0.1.0 + +
+
+ +
+ {/* 左栏:任务输入 + 流水线 */} +
+ {/* 评审任务表单 */} +
+

+ ◆ 新建评审任务 +

+
+
+ + setCustomer(e.target.value)} + placeholder="例:华南远通物流有限公司" /> +
+
+
+ + setAmount(e.target.value)} + placeholder="5000" /> +
+
+ + setAsset(e.target.value)} + placeholder="50台电动重卡" /> +
+
+
+ + +
+

五智能体按序执行,全程留痕可回退,约需 3–4 分钟

+
+
+ + {/* 流水线可视化 */} +
+

+ ⚡ 智能体流水线 +

+
+ {pipelineOrder.map((agent, i) => ( +
+
+
{agent.icon}
+
{agent.name}
+
+ {pipeline[agent.key] === "running" && } + {pipeline[agent.key] === "done" && ✓} + {pipeline[agent.key] === "error" && ✕} + {agent.role} +
+
+ {i < pipelineOrder.length - 1 &&
} +
+ ))} +
+ + {/* 状态文字 */} +
+ {status === "running" && ● 智能体协同执行中…} + {status === "done" && ● 评审完成,结论已生成} + {status === "error" && ● 执行出现异常} + {status === "idle" && ● 等待发起评审任务} + {reward !== null && 进化奖励分 {reward}} +
+
+ + {/* 自进化面板 */} +
+

+ 🧬 自进化洞察 + Level 1 · 轨迹学习 +

+
+              {evolution || "尚无轨迹样本。运行审批流水线积累数据后,系统将自动进化。"}
+            
+
+
+ + {/* 右栏:产出结论 */} +
+ {/* 审批结论 */} +
+
+

+ 📋 评审产出 +

+ {trajectoryId && ( + 轨迹 {trajectoryId} + )} +
+ + {/* 各智能体产出卡片 */} +
+ {AGENTS.map((agent) => { + const r = results[agent.key]; + const st = pipeline[agent.key]; + return ( +
+
+ {agent.icon} + {agent.name} + {r?.success && ✓ 完成} + {st === "running" && 运行中…} + {r !== undefined && !r?.success && 失败} +
+
{agent.desc}
+ {r?.output ? ( +
{r.output}
+ ) : st === "pending" ? ( +
待执行
+ ) : st === "running" ? ( +
正在分析数据…
+ ) : null} +
+ ); + })} +
+ + {error && ( +
+ ⚠️ {error} +
+ )} + + {finalSummary.length > 0 && ( +
+ {finalSummary.map((s, i) => {s})} +
+ )} +
+ + {/* 详情预览 */} +
+

+ 📄 最新产出预览 +

+ {activeOutput ? ( +
{activeOutput}
+ ) : ( +
+ 五智能体执行完毕后,此处显示最终审批结论… +
+ )} +
+
+
+
+ ); +} diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index 45de8d9..5c7ac3d 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -35,6 +35,14 @@ export default function Navbar() { mini OpenClaw + {/* 兴邦评审入口 */} + {/* Right — Inspector toggle */} diff --git a/src/components/xingbang/xingbang.css b/src/components/xingbang/xingbang.css new file mode 100644 index 0000000..feea038 --- /dev/null +++ b/src/components/xingbang/xingbang.css @@ -0,0 +1,216 @@ +/* ═══ 兴邦金租 AI 评审平台 · 深色商务风专用样式 ═══ */ + +.xb-root { + --xb-bg-0: #05070f; + --xb-bg-1: #0a1020; + --xb-bg-2: #101a33; + --xb-line: rgba(120, 160, 255, 0.14); + --xb-line-strong: rgba(120, 160, 255, 0.28); + --xb-text: #e8eefc; + --xb-muted: #8fa3c8; + --xb-blue: #38bdf8; + --xb-deep: #2563eb; + --xb-gold: #f5b942; + --xb-green: #34d399; + --xb-red: #f87171; + --xb-card: rgba(15, 24, 46, 0.72); + + background: + radial-gradient(1200px 600px at 15% -10%, rgba(37, 99, 235, 0.18), transparent 60%), + radial-gradient(1000px 500px at 90% 0%, rgba(56, 189, 248, 0.12), transparent 55%), + radial-gradient(1200px 800px at 50% 110%, rgba(15, 40, 90, 0.5), transparent 60%), + linear-gradient(180deg, var(--xb-bg-1), var(--xb-bg-0)); + color: var(--xb-text); + min-height: 100vh; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", + "Microsoft YaHei", sans-serif; +} + +.xb-glass { + background: var(--xb-card); + border: 1px solid var(--xb-line); + backdrop-filter: blur(14px); + border-radius: 16px; + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +.xb-glass-hover { + transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.3s ease; +} +.xb-glass-hover:hover { + transform: translateY(-2px); + border-color: var(--xb-line-strong); + box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.07); +} + +/* 顶部渐进标题 */ +.xb-title-grad { + background: linear-gradient(120deg, #ffffff 0%, var(--xb-blue) 55%, var(--xb-gold) 130%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.xb-muted { color: var(--xb-muted); } + +/* 流水线步骤连线 */ +.xb-pipe-connector { + height: 2px; + flex: 1; + margin: 0 6px; + background: linear-gradient(90deg, var(--xb-line), var(--xb-line)); + border-radius: 2px; +} + +/* 徽章 */ +.xb-badge { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 3px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 600; + border: 1px solid var(--xb-line); + background: rgba(255, 255, 255, 0.04); +} + +/* 按钮 */ +.xb-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 22px; + border-radius: 12px; + font-weight: 600; + font-size: 14px; + border: 1px solid transparent; + cursor: pointer; + transition: all 0.2s ease; + background: linear-gradient(135deg, var(--xb-deep), var(--xb-blue)); + color: #fff; + box-shadow: 0 8px 24px rgba(37, 99, 235, 0.35); +} +.xb-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 12px 30px rgba(37, 99, 235, 0.5); +} +.xb-btn:disabled { opacity: 0.5; cursor: not-allowed; } + +.xb-btn-ghost { + background: transparent; + border: 1px solid var(--xb-line-strong); + color: var(--xb-text); + box-shadow: none; +} +.xb-btn-ghost:hover:not(:disabled) { background: rgba(255, 255, 255, 0.05); } + +/* 输入 */ +.xb-input { + width: 100%; + background: rgba(0, 0, 0, 0.28); + border: 1px solid var(--xb-line); + border-radius: 12px; + padding: 11px 14px; + color: var(--xb-text); + font-size: 14px; + outline: none; + transition: border-color 0.2s; +} +.xb-input:focus { border-color: var(--xb-blue); } +.xb-input::placeholder { color: #5c6f96; } + +.xb-label { + display: block; + font-size: 12px; + font-weight: 600; + color: var(--xb-muted); + margin-bottom: 6px; + letter-spacing: 0.02em; +} + +/* 流水线步骤节点状态 */ +.xb-node { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + min-width: 92px; +} + +.xb-node-chip { + width: 46px; + height: 46px; + border-radius: 14px; + display: flex; + align-items: center; + justify-content: center; + border: 1.5px solid var(--xb-line-strong); + background: rgba(20, 32, 60, 0.8); + color: var(--xb-muted); + transition: all 0.3s ease; +} +.xb-node.running .xb-node-chip { + border-color: var(--xb-blue); + color: var(--xb-blue); + box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.15), 0 0 24px rgba(56, 189, 248, 0.3); + animation: xb-pulse 1.6s ease-in-out infinite; +} +.xb-node.done .xb-node-chip { + border-color: var(--xb-green); + color: var(--xb-green); + box-shadow: 0 0 0 4px rgba(52, 211, 153, 0.12); +} +.xb-node.error .xb-node-chip { + border-color: var(--xb-red); + color: var(--xb-red); +} +.xb-node.pending .xb-node-chip { opacity: 0.55; } + +.xb-node-name { font-size: 12px; font-weight: 600; } +.xb-node-role { font-size: 10.5px; color: var(--xb-muted); } + +@keyframes xb-pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.07); } +} + +.xb-spinner { + width: 16px; height: 16px; + border: 2px solid rgba(56, 189, 248, 0.25); + border-top-color: var(--xb-blue); + border-radius: 50%; + animation: xb-spin 0.8s linear infinite; +} +@keyframes xb-spin { to { transform: rotate(360deg); } } + +/* 表格 */ +.xb-table { width: 100%; border-collapse: collapse; font-size: 13px; } +.xb-table th { + text-align: left; + padding: 8px 12px; + color: var(--xb-muted); + font-weight: 600; + font-size: 11.5px; + border-bottom: 1px solid var(--xb-line); + letter-spacing: 0.03em; +} +.xb-table td { + padding: 9px 12px; + border-bottom: 1px solid rgba(120, 160, 255, 0.08); + vertical-align: top; +} + +/* 风险等级徽章 */ +.xb-risk-hi { color: var(--xb-red); font-weight: 700; } +.xb-risk-mid { color: var(--xb-gold); font-weight: 700; } +.xb-risk-low { color: var(--xb-green); font-weight: 700; } + +/* 滚动条 */ +.xb-scroll::-webkit-scrollbar { width: 8px; height: 8px; } +.xb-scroll::-webkit-scrollbar-thumb { + background: rgba(56, 189, 248, 0.25); + border-radius: 4px; +} +.xb-scroll::-webkit-scrollbar-track { background: transparent; } diff --git a/src/lib/api.ts b/src/lib/api.ts index 8ce402f..b879a1e 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -254,3 +254,80 @@ export async function setRagMode( if (!resp.ok) throw new Error(`Failed to set RAG mode: ${resp.status}`); return resp.json(); } + +/* ── 兴邦金租评审平台 API ───────────────────────────── */ + +/** 兴邦项目配置(数据源/五智能体/档位)。 */ +export async function getXingbangConfig(): Promise { + const resp = await fetch(`${API_BASE}/xingbang/config`); + if (!resp.ok) throw new Error(`Xingbang config error: ${resp.status}`); + return resp.json(); +} + +/** + * 流式运行兴邦五智能体审批流水线(SSE)。 + * 逐步 yield: agent_start / agent_end / done。 + */ +export async function* streamXingbangWorkflow( + task: string, + customer: string +): AsyncGenerator { + const response = await fetch(`${API_BASE}/xingbang/workflow/stream`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ task, customer }), + }); + if (!response.ok) throw new Error(`Xingbang workflow error: ${response.status}`); + const reader = response.body?.getReader(); + if (!reader) throw new Error("No response body"); + const decoder = new TextDecoder(); + let buffer = ""; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() || ""; + let currentEvent = "message"; + for (const line of lines) { + if (line.startsWith("event:")) { + currentEvent = line.slice(6).trim(); + } else if (line.startsWith("data:")) { + const dataStr = line.slice(5).trim(); + if (dataStr) { + try { + yield { event: currentEvent, data: JSON.parse(dataStr) }; + } catch { + /* skip */ + } + } + } + if (line === "") currentEvent = "message"; + } + } +} + +/** 兴邦自进化洞察。 */ +export async function getXingbangEvolution(): Promise { + const resp = await fetch(`${API_BASE}/xingbang/evolution`); + if (!resp.ok) throw new Error(`Xingbang evolution error: ${resp.status}`); + return resp.json(); +} + +/** 兴邦历史轨迹列表。 */ +export async function listXingbangTrajectories(): Promise { + const resp = await fetch(`${API_BASE}/xingbang/trajectories`); + if (!resp.ok) throw new Error(`Xingbang trajectories error: ${resp.status}`); + return resp.json(); +} + +/** 读取单条兴邦轨迹。 */ +export async function getXingbangTrajectory(id: string): Promise { + const resp = await fetch( + `${API_BASE}/xingbang/trajectories/${encodeURIComponent(id)}` + ); + if (!resp.ok) throw new Error(`Xingbang trajectory error: ${resp.status}`); + return resp.json(); +} +