diff --git a/sundynix-desktop/frontend/src/App.tsx b/sundynix-desktop/frontend/src/App.tsx index 1e1b0f6..d83fe0a 100644 --- a/sundynix-desktop/frontend/src/App.tsx +++ b/sundynix-desktop/frontend/src/App.tsx @@ -303,7 +303,7 @@ export default function App() {
{view === "home" ? ( - + ) : view === "studio" ? ( ) : view === "kb" ? ( diff --git a/sundynix-desktop/frontend/src/views/Home.tsx b/sundynix-desktop/frontend/src/views/Home.tsx index 84d508c..d392215 100644 --- a/sundynix-desktop/frontend/src/views/Home.tsx +++ b/sundynix-desktop/frontend/src/views/Home.tsx @@ -1,62 +1,56 @@ import { useEffect, useState } from "react"; -import { Workflow, Database, Bookmark, FileText, Plus, Upload, Activity, Coins, Gauge, ArrowRight, type LucideIcon } from "lucide-react"; +import { Workflow, Database, Bookmark, FileText, Plus, Upload, Activity, Coins, Gauge, ArrowRight, ArrowUpRight, type LucideIcon } from "lucide-react"; import { statsOverview, type Overview, type RecentRun } from "../lib/api"; import type { ViewKey } from "../shell/LeftNav"; import { Button, cn } from "../ui"; -// 迷你火花线:把一串数值画成 SVG 折线(仪表盘趋势)。 +// 迷你火花线:把一串数值画成 SVG 折线(指标趋势)。 function Spark({ data, stroke }: { data: number[]; stroke: string }) { if (data.length < 2) return null; const max = Math.max(...data, 1); const min = Math.min(...data, 0); const span = max - min || 1; - const w = 60; - const h = 18; - const pts = data - .map((v, i) => `${(i / (data.length - 1)) * w},${h - ((v - min) / span) * (h - 2) - 1}`) - .join(" "); + const w = 64; + const h = 20; + const pts = data.map((v, i) => `${(i / (data.length - 1)) * w},${h - ((v - min) / span) * (h - 2) - 1}`).join(" "); return ( - - + + ); } -function Metric({ icon: Icon, label, value, sub, spark, sparkColor, accent }: { - icon: LucideIcon; label: string; value: string; sub: string; - spark?: number[]; sparkColor?: string; accent?: string; +function Metric({ icon: Icon, label, value, sub, spark }: { + icon: LucideIcon; label: string; value: string; sub: string; spark?: number[]; }) { return ( -
-
- {label} - +
+
+ + {label}
-
{value}
-
+
{value}
+
{sub} - {spark && spark.some((v) => v > 0) ? : null} + {spark && spark.some((v) => v > 0) ? : null}
); } -const CAPS: { icon: LucideIcon; title: string; desc: string; to: ViewKey; color: string }[] = [ - { icon: Workflow, title: "Agent 编排", desc: "画布 → Eino 动态图 → 流式执行", to: "studio", color: "text-brand-400" }, - { icon: Database, title: "RAG 知识库", desc: "向量 / 全文 / 图谱 三路混合检索", to: "kb", color: "text-accent-400" }, - { icon: FileText, title: "报告生成", desc: "规划 → 分章并行 → 渲染 Word", to: "report", color: "text-indigo-300" }, - { icon: Bookmark, title: "偏好记忆", desc: "画像 + 多轮历史", to: "memory", color: "text-success" }, +// 能力入口:用「结果语言」描述,不暴露架构黑话。 +const CAPS: { icon: LucideIcon; title: string; desc: string; to: ViewKey }[] = [ + { icon: Workflow, title: "智能体编排", desc: "把多步任务串成可复用的自动化工作流", to: "studio" }, + { icon: Database, title: "知识库问答", desc: "上传资料,让 AI 基于你的文档精准作答", to: "kb" }, + { icon: FileText, title: "报告生成", desc: "从大纲到成稿,一键产出结构化文档", to: "report" }, + { icon: Bookmark, title: "偏好记忆", desc: "记住你的习惯与历史,越用越懂你", to: "memory" }, ]; -const STATUS_DOT: Record = { - done: "bg-success", - failed: "bg-danger", timeout: "bg-danger", rejected: "bg-danger", - waiting: "bg-warn", - running: "bg-brand", submitted: "bg-brand", -}; - -const SERVICE_LABEL: Record = { - gateway: "网关", nats: "总线", db: "PG", redis: "Redis", milvus: "Milvus", neo4j: "Neo4j", +const STATUS_LABEL: Record = { + done: { text: "完成", cls: "text-success" }, + failed: { text: "失败", cls: "text-danger" }, timeout: { text: "超时", cls: "text-danger" }, rejected: { text: "已拒", cls: "text-danger" }, + waiting: { text: "待审", cls: "text-warn" }, + running: { text: "运行中", cls: "text-brand-400" }, submitted: { text: "已提交", cls: "text-brand-400" }, }; function relTime(iso: string): string { @@ -68,18 +62,24 @@ function relTime(iso: string): string { } function RunRow({ r }: { r: RecentRun }) { + const s = STATUS_LABEL[r.status] ?? { text: r.status, cls: "text-slate-500" }; return ( -
- - {r.task_id} - {r.status} +
+ + {r.task_id} + {s.text} {relTime(r.at)}
); } -// 工作台:实时驾驶舱(指标 + 趋势 + 近期运行 + 服务健康 + 快捷动作)。 -export function Home({ onSelect }: { onSelect: (v: ViewKey) => void }) { +function greet(): string { + const h = new Date().getHours(); + return h < 6 ? "凌晨好" : h < 12 ? "早上好" : h < 14 ? "中午好" : h < 18 ? "下午好" : "晚上好"; +} + +// 工作台:商业级门面 —— 问候 + 主行动 + 关键指标 + 最近任务 + 快速开始(去基建、去黑话)。 +export function Home({ onSelect, userName, spaceName }: { onSelect: (v: ViewKey) => void; userName?: string; spaceName?: string }) { const [ov, setOv] = useState(null); useEffect(() => { let alive = true; @@ -93,92 +93,91 @@ export function Home({ onSelect }: { onSelect: (v: ViewKey) => void }) { const tokenTrend = ov?.token_trend.map((d) => d.count) ?? []; const maxTask = Math.max(...taskTrend, 1); const fmtTokens = (n: number) => (n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : `${n}`); + const hasRuns = !!ov && ov.recent_runs.length > 0; + const quality = ov?.eval_count ? `${Math.round((ov.eval_avg ?? 0) * 100)}%` : "—"; return (
- {/* 头部 */} -
-
S
-
-

工作台

-

实时概览 · 编排 / 知识库 / 报告 / 记忆

+ {/* 问候 + 主行动 */} +
+
+

+ {greet()}{userName ? `,${userName}` : ""} +

+

+ {spaceName ? <>工作区 · {spaceName} · : null}今天想做点什么? +

- - - {ov?.services.gateway ? "实时" : "连接中"} - +
- {/* 指标卡 */} -
- - - - + {/* 关键指标 */} +
+ + + +
- {/* 近期运行 + 7 日趋势 */} -
-
-
- 近期运行 - + {/* 最近任务 + 7 日趋势 */} +
+
+
+ 最近任务 + {hasRuns && ( + + )}
- {ov && ov.recent_runs.length > 0 ? ( - ov.recent_runs.map((r) => ) + {hasRuns ? ( +
{ov!.recent_runs.map((r) => )}
) : ( -
暂无运行记录
+
+
还没有运行记录
+
搭一张编排、或上传资料建个知识库,任务跑起来后会在这里出现。
+ +
)}
-
-
7 日任务量
-
+
+
近 7 日任务量
+
{(ov?.task_trend ?? []).map((d, i) => (
-
- {d.key.slice(-2)} +
+ {d.key.slice(-2)}
))} - {!ov &&
加载中…
} + {!ov &&
加载中…
}
- {/* 服务健康灯带 */} -
- 服务 - {Object.entries(SERVICE_LABEL).map(([k, label]) => ( - - - {label} - - ))} + {/* 快速开始 */} +
+
快速开始
+
+ {CAPS.map((c) => { + const Icon = c.icon; + return ( + + ); + })} +
- {/* 能力入口 */} -
- {CAPS.map((c) => { - const Icon = c.icon; - return ( - - ); - })} -
- - {/* 快捷动作 */} + {/* 次要动作 */}
-