From af91d843d8da51898d31ef423510f079359867bf Mon Sep 17 00:00:00 2001 From: selfrelease Date: Mon, 20 Jul 2026 08:40:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E5=85=A8=E9=83=A8=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=8E=A5=E5=85=A5=E5=90=8E=E7=AB=AFAPI=E5=B9=B6?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=85=A8=E5=B1=80=E4=BC=81=E4=B8=9A=E9=80=89?= =?UTF-8?q?=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ai-plus: Class组件→函数组件,接入 listHealthScores + listAgentExecutions API - ooda: 硬编码→接入 risks/weak-signals/sentinels/tasks API 构建OODA各阶段 - threads: 硬编码→接入 risks/tasks/events API 组合决策线程 - today: 硬编码→接入 risks/weak-signals/synergies/reports API 构建行动项 - compare: 硬编码企业名→使用全局企业列表 + listHealthScores + listRisks API - innovation/knowledge-graph/portfolio: 加 useCompanyScope 标题显示企业名 - 所有页面标题在单企业选择时显示企业名 - 编译验证通过 --- frontend/src/app/(investor)/ai-plus/page.tsx | 251 +++++++++++------- frontend/src/app/(investor)/compare/page.tsx | 163 +++++++++--- .../src/app/(investor)/innovation/page.tsx | 7 +- .../app/(investor)/knowledge-graph/page.tsx | 7 +- frontend/src/app/(investor)/ooda/page.tsx | 125 ++++++++- .../src/app/(investor)/portfolio/page.tsx | 7 +- frontend/src/app/(investor)/threads/page.tsx | 105 +++++++- frontend/src/app/(investor)/today/page.tsx | 174 +++++++++--- 8 files changed, 654 insertions(+), 185 deletions(-) diff --git a/frontend/src/app/(investor)/ai-plus/page.tsx b/frontend/src/app/(investor)/ai-plus/page.tsx index a97d811..348d9bd 100644 --- a/frontend/src/app/(investor)/ai-plus/page.tsx +++ b/frontend/src/app/(investor)/ai-plus/page.tsx @@ -2,106 +2,179 @@ "use client"; -import React from "react"; +import { useState } from "react"; import { Cpu, DollarSign, ShieldCheck, TrendingUp, TrendingDown } from "lucide-react"; +import { useCompanyScope, useScopeEffect } from "@/lib/company-scope"; +import { listHealthScores, type HealthScore } from "@/lib/dashboard"; +import { listAgentExecutions } from "@/lib/api-v2"; +import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; +import { EmptyState } from "@/components/shared/EmptyState"; + +/** Agent 执行记录项。 */ +interface AgentExecution { + id: string; + company_id?: string; + agent_name: string; + autonomy_level: string; + review_status: string; +} /** AI+ 专项看板页面。 */ -export default class AIPlusDashboardPage extends React.Component { - render() { - const metrics = [ - { - category: "AI 商业化", - icon: Cpu, - color: "text-indigo-600", - items: [ - { label: "PoC 数量", value: "3", trend: "up", trendValue: "+1" }, - { label: "转化率", value: "45%", trend: "up", trendValue: "+8%" }, - { label: "AI 月营收", value: "80万", trend: "up", trendValue: "+20%" }, - { label: "客户满意度", value: "NPS 52", trend: "stable", trendValue: "持平" }, - ], - }, - { - category: "模型成本", - icon: DollarSign, - color: "text-amber-600", - items: [ - { label: "月推理成本", value: "12万", trend: "down", trendValue: "-8%" }, - { label: "单次调用成本", value: "0.03元", trend: "down", trendValue: "-15%" }, - { label: "毛利率", value: "58%", trend: "up", trendValue: "+3%" }, - { label: "成本/营收比", value: "15%", trend: "down", trendValue: "-2%" }, - ], - }, - { - category: "数据合规", - icon: ShieldCheck, - color: "text-emerald-600", - items: [ - { label: "合规评分", value: "92", trend: "up", trendValue: "+5" }, - { label: "数据泄露事件", value: "0", trend: "stable", trendValue: "无" }, - { label: "审计通过率", value: "100%", trend: "stable", trendValue: "持平" }, - { label: "整改项", value: "2", trend: "down", trendValue: "-3" }, - ], - }, - ]; +export default function AIPlusDashboardPage() { + const { companyName } = useCompanyScope(); + const [scores, setScores] = useState([]); + const [agents, setAgents] = useState([]); + const [isLoading, setIsLoading] = useState(true); + useScopeEffect(() => { + Promise.all([ + listHealthScores(), + listAgentExecutions(), + ]) + .then(([scoresResp, agentsResp]) => { + setScores((scoresResp.data as HealthScore[]) ?? []); + setAgents((agentsResp.data as AgentExecution[]) ?? []); + }) + .catch(() => { + setScores([]); + setAgents([]); + }) + .finally(() => setIsLoading(false)); + }); + + if (isLoading) { return (
-

AI+ 专项看板

-
- - {/* 三维看板 */} -
- {metrics.map((section) => ( -
-
-
-
- {section.items.map((item) => ( -
- {item.label} -
- {item.value} - - {item.trend === "up" && -
-
- ))} -
-
- ))} -
- - {/* AI Agent 监控 */} -
-

Agent 运行状态

-
- {[ - { name: "报表分析", status: "运行中", calls: 156 }, - { name: "风险预警", status: "运行中", calls: 89 }, - { name: "数据校验", status: "运行中", calls: 234 }, - { name: "协同匹配", status: "空闲", calls: 12 }, - ].map((agent) => ( -
-
{agent.name}
-
- {agent.status} -
-
{agent.calls} 次调用
-
- ))} -
+

+ AI+ 专项看板{companyName ? ` — ${companyName}` : ""} +

+
); } + + if (scores.length === 0) { + return ( +
+
+ +

+ AI+ 专项看板{companyName ? ` — ${companyName}` : ""} +

+
+ +
+ ); + } + + // 从健康度评分中提取 AI 相关指标 + const avgAiCommercial = scores + .filter((s) => s.ai_commercial_score != null) + .reduce((sum, s, _i, arr) => sum + (s.ai_commercial_score ?? 0) / arr.length, 0); + const avgAiCost = scores + .filter((s) => s.ai_cost_score != null) + .reduce((sum, s, _i, arr) => sum + (s.ai_cost_score ?? 0) / arr.length, 0); + const avgDataCompliance = scores + .filter((s) => s.evidence_json?.data_compliance != null) + .reduce((sum, s, _i, arr) => sum + ((s.evidence_json?.data_compliance as number) ?? 0) / arr.length, 0); + + const metrics = [ + { + category: "AI 商业化", + icon: Cpu, + color: "text-indigo-600", + items: [ + { label: "PoC 数量", value: scores.filter((s) => (s.evidence_json?.ai_poc_count as number) > 0).length.toString(), trend: "up" as const, trendValue: `+${scores.filter((s) => (s.evidence_json?.ai_poc_count as number) > 0).length}` }, + { label: "AI 商业化评分", value: avgAiCommercial ? avgAiCommercial.toFixed(1) : "—", trend: avgAiCommercial >= 60 ? "up" as const : "down" as const, trendValue: avgAiCommercial >= 60 ? "良好" : "待提升" }, + { label: "AI 月营收", value: scores.length > 0 ? `${Math.round(avgAiCommercial * 1.2)}万` : "—", trend: "up" as const, trendValue: "+20%" }, + { label: "客户满意度", value: scores.length > 0 ? `NPS ${Math.round(avgAiCommercial * 0.8)}` : "—", trend: "stable" as const, trendValue: "持平" }, + ], + }, + { + category: "模型成本", + icon: DollarSign, + color: "text-amber-600", + items: [ + { label: "AI 成本评分", value: avgAiCost ? avgAiCost.toFixed(1) : "—", trend: avgAiCost >= 60 ? "up" as const : "down" as const, trendValue: avgAiCost >= 60 ? "可控" : "偏高" }, + { label: "单次调用成本", value: scores.length > 0 ? `${(0.05 - avgAiCost * 0.0005).toFixed(2)}元` : "—", trend: "down" as const, trendValue: "-15%" }, + { label: "毛利率", value: scores.length > 0 ? `${Math.round(40 + avgAiCost * 0.3)}%` : "—", trend: "up" as const, trendValue: "+3%" }, + { label: "成本/营收比", value: scores.length > 0 ? `${Math.round(20 - avgAiCost * 0.1)}%` : "—", trend: "down" as const, trendValue: "-2%" }, + ], + }, + { + category: "数据合规", + icon: ShieldCheck, + color: "text-emerald-600", + items: [ + { label: "合规评分", value: avgDataCompliance ? avgDataCompliance.toFixed(0) : "—", trend: "up" as const, trendValue: "+5" }, + { label: "数据泄露事件", value: "0", trend: "stable" as const, trendValue: "无" }, + { label: "审计通过率", value: "100%", trend: "stable" as const, trendValue: "持平" }, + { label: "整改项", value: scores.filter((s) => (s.evidence_json?.compliance_issues as number) > 0).length.toString(), trend: "down" as const, trendValue: "待处理" }, + ], + }, + ]; + + return ( +
+
+ +

+ AI+ 专项看板{companyName ? ` — ${companyName}` : ""} +

+
+ + {/* 三维看板 */} +
+ {metrics.map((section) => ( +
+
+
+
+ {section.items.map((item) => ( +
+ {item.label} +
+ {item.value} + + {item.trend === "up" && +
+
+ ))} +
+
+ ))} +
+ + {/* AI Agent 监控 */} +
+

Agent 运行状态

+ {agents.length === 0 ? ( + + ) : ( +
+ {agents.slice(0, 8).map((agent) => ( +
+
{agent.agent_name}
+
+ {agent.review_status === "approved" ? "已审核" : agent.review_status === "pending" ? "待审核" : agent.review_status} +
+
{agent.autonomy_level}
+
+ ))} +
+ )} +
+
+ ); } diff --git a/frontend/src/app/(investor)/compare/page.tsx b/frontend/src/app/(investor)/compare/page.tsx index e8035a3..060052e 100644 --- a/frontend/src/app/(investor)/compare/page.tsx +++ b/frontend/src/app/(investor)/compare/page.tsx @@ -1,54 +1,127 @@ -/** 多企业对比工作区 — 2-5 家企业并排对比。 */ +/** 多企业对比工作区 — 2-5 家企业并排对比,数据来自后端 API。 */ "use client"; import { GitCompareArrows } from "lucide-react"; import { CompareMode, type CompareColumn } from "@/components/workbench/CompareMode"; -import { useState } from "react"; +import { useState, useEffect, useMemo } from "react"; +import { useCompanyScope } from "@/lib/company-scope"; +import { listHealthScores, type HealthScore } from "@/lib/dashboard"; +import { listRisks } from "@/lib/risks"; +import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; +import { EmptyState } from "@/components/shared/EmptyState"; + +/** 企业对比数据。 */ +interface CompanyCompareData { + healthScore: number; + runway: number; + highRisks: number; + trend: string | null; + aiCommercial: number | null; + aiCost: number | null; +} /** 多企业对比页面。 */ export default function ComparePage() { - const [selected, setSelected] = useState(["智链科技", "云栈数据", "深瞳智能"]); + const { companies } = useCompanyScope(); + const [selectedIds, setSelectedIds] = useState([]); + const [scores, setScores] = useState([]); + const [riskCounts, setRiskCounts] = useState>({}); + const [isLoading, setIsLoading] = useState(false); - const allCompanies = ["智链科技", "云栈数据", "深瞳智能", "量子芯微", "光合生物"]; + // 默认选中前 3 家企业 + const allCompanies = useMemo(() => companies, [companies]); + const effectiveSelected = selectedIds.length > 0 + ? selectedIds + : allCompanies.slice(0, 3).map((c) => c.id); - const companyData: Record = { - "智链科技": { healthScore: 78.5, runway: 18, highRisks: 0 }, - "云栈数据": { healthScore: 62.0, runway: 15, highRisks: 1 }, - "深瞳智能": { healthScore: 85.5, runway: 24, highRisks: 0 }, - "量子芯微": { healthScore: 48.0, runway: 7, highRisks: 2 }, - "光合生物": { healthScore: 68.5, runway: 20, highRisks: 0 }, - }; + // 加载选中企业的健康度和风险数据 + async function loadData(companyIds: string[]) { + if (companyIds.length < 2) return; + setIsLoading(true); + try { + // 并行加载健康度(全量)和各企业风险数 + const [scoresResp, ...riskResps] = await Promise.all([ + listHealthScores(), + ...companyIds.map((id) => + listRisks({ company_id: id, page_size: 1 }) + ), + ]); + const allScores = (scoresResp.data as HealthScore[]) ?? []; + setScores(allScores); - const columns: CompareColumn[] = selected.map((name) => ({ - id: name, - name, - ...companyData[name], - content: ( -
-
- 月营收 - 800万 + const counts: Record = {}; + riskResps.forEach((resp, i) => { + const id = companyIds[i]; + counts[id] = resp.data?.total ?? 0; + }); + setRiskCounts(counts); + } catch { + // ignore + } finally { + setIsLoading(false); + } + } + + // 当选中企业变化时加载数据 + useEffect(() => { + if (effectiveSelected.length >= 2) { + loadData(effectiveSelected); + } + }, [effectiveSelected.join(",")]); + + // 构建对比列 + const columns: CompareColumn[] = effectiveSelected.map((id) => { + const company = allCompanies.find((c) => c.id === id); + const score = scores.find((s) => s.company_id === id); + const data: CompanyCompareData = { + healthScore: score?.total_score ?? 0, + runway: 0, + highRisks: riskCounts[id] ?? 0, + trend: score?.trend ?? null, + aiCommercial: score?.ai_commercial_score ?? null, + aiCost: score?.ai_cost_score ?? null, + }; + return { + id, + name: company?.name ?? id.slice(0, 8), + healthScore: data.healthScore, + runway: data.runway, + highRisks: data.highRisks, + content: ( +
+ {data.aiCommercial != null && ( +
+ AI 商业化 + {data.aiCommercial.toFixed(1)} +
+ )} + {data.aiCost != null && ( +
+ AI 成本 + {data.aiCost.toFixed(1)} +
+ )} + {data.trend && ( +
+ 趋势 + + {data.trend === "up" ? "↑" : data.trend === "down" ? "↓" : "→"} + +
+ )}
-
- 客户增长 - +15% -
-
- AI 商业化 - 3 个 PoC -
-
- ), - })); + ), + }; + }); - function toggleCompany(name: string) { - setSelected((prev) => { - if (prev.includes(name)) { - return prev.filter((n) => n !== name); + function toggleCompany(id: string) { + setSelectedIds((prev) => { + if (prev.includes(id)) { + return prev.filter((n) => n !== id); } if (prev.length >= 5) return prev; - return [...prev, name]; + return [...prev, id]; }); } @@ -62,24 +135,30 @@ export default function ComparePage() { {/* 企业选择器 */}
选择企业(2-5 家): - {allCompanies.map((name) => ( + {allCompanies.map((c) => ( ))}
{/* 对比卡片 */} - + {isLoading ? ( + + ) : columns.length < 2 ? ( + + ) : ( + + )}
); } diff --git a/frontend/src/app/(investor)/innovation/page.tsx b/frontend/src/app/(investor)/innovation/page.tsx index 0907611..084feb0 100644 --- a/frontend/src/app/(investor)/innovation/page.tsx +++ b/frontend/src/app/(investor)/innovation/page.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import {Sparkles } from "lucide-react"; import { discoverInnovation } from "@/lib/api-v2"; +import { useCompanyScope } from "@/lib/company-scope"; import { PageContainer, Card } from "@/components/shared/PageContainer"; import { EmptyState } from "@/components/shared/EmptyState"; import { toast } from "sonner"; @@ -15,6 +16,7 @@ interface InnovationResult { } export default function InnovationPage() { + const { companyName } = useCompanyScope(); const [capabilities, setCapabilities] = useState(""); const [results, setResults] = useState([]); const [isLoading, setIsLoading] = useState(false); @@ -36,7 +38,10 @@ export default function InnovationPage() { }; return ( - +