diff --git a/sundynix-admin/index.html b/sundynix-admin/index.html index 9e63cd1..1d7db00 100644 --- a/sundynix-admin/index.html +++ b/sundynix-admin/index.html @@ -3,7 +3,9 @@ - sundynix-agentix · 运维控制台 + + + sundynix agentix · 事件驱动的 AI Agent 工作台
diff --git a/sundynix-admin/package-lock.json b/sundynix-admin/package-lock.json index 4236ccb..6e761d8 100644 --- a/sundynix-admin/package-lock.json +++ b/sundynix-admin/package-lock.json @@ -8,9 +8,12 @@ "name": "sundynix-admin", "version": "0.1.0", "dependencies": { + "clsx": "^2.1.1", + "lucide-react": "^1.25.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router-dom": "^7.1.0" + "react-router-dom": "^7.1.0", + "tailwind-merge": "^3.6.0" }, "devDependencies": { "@testing-library/jest-dom": "^6.9.1", @@ -2357,6 +2360,15 @@ "node": ">= 6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -3220,6 +3232,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.25.0.tgz", + "integrity": "sha512-/mdJTRbiwcLOQ1NZZK1amZF9rIZyvO18D6r9TngE6TG1NmqHgFuT4eE7Xrkm9UsXMbBJD1NlfwHVltCDWHrOTw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -4040,6 +4061,16 @@ "dev": true, "license": "MIT" }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.19", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", diff --git a/sundynix-admin/package.json b/sundynix-admin/package.json index 9b3e1cf..c2b4646 100644 --- a/sundynix-admin/package.json +++ b/sundynix-admin/package.json @@ -11,9 +11,12 @@ "test:watch": "vitest" }, "dependencies": { + "clsx": "^2.1.1", + "lucide-react": "^1.25.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router-dom": "^7.1.0" + "react-router-dom": "^7.1.0", + "tailwind-merge": "^3.6.0" }, "devDependencies": { "@testing-library/jest-dom": "^6.9.1", diff --git a/sundynix-admin/public/apple-touch-icon.png b/sundynix-admin/public/apple-touch-icon.png new file mode 100644 index 0000000..11be7af Binary files /dev/null and b/sundynix-admin/public/apple-touch-icon.png differ diff --git a/sundynix-admin/public/brand/logo-full.jpg b/sundynix-admin/public/brand/logo-full.jpg new file mode 100644 index 0000000..02e39e6 Binary files /dev/null and b/sundynix-admin/public/brand/logo-full.jpg differ diff --git a/sundynix-admin/public/brand/logo-mark.webp b/sundynix-admin/public/brand/logo-mark.webp new file mode 100644 index 0000000..4c26031 Binary files /dev/null and b/sundynix-admin/public/brand/logo-mark.webp differ diff --git a/sundynix-admin/public/favicon-32.png b/sundynix-admin/public/favicon-32.png new file mode 100644 index 0000000..d0825f0 Binary files /dev/null and b/sundynix-admin/public/favicon-32.png differ diff --git a/sundynix-admin/public/favicon.png b/sundynix-admin/public/favicon.png new file mode 100644 index 0000000..776e242 Binary files /dev/null and b/sundynix-admin/public/favicon.png differ diff --git a/sundynix-admin/src/App.tsx b/sundynix-admin/src/App.tsx index dfff3f9..f47b0e6 100644 --- a/sundynix-admin/src/App.tsx +++ b/sundynix-admin/src/App.tsx @@ -1,11 +1,18 @@ import { useEffect, useState } from "react"; -import { HashRouter } from "react-router-dom"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import { AppShell } from "./shell/AppShell"; import { Login } from "./Login"; import { me, clearToken, type AuthUser } from "./api"; -// 用 HashRouter:纯静态托管/桌面内嵌都能深链,无需服务端路由配置。 -export default function App() { +// 官网落地页(融进本工程,src/site/),挂根 /。 +import { SiteLayout } from "./site/components/layout/site-layout"; +import HomePage from "./site/pages/home"; +import DownloadPage from "./site/pages/download"; +import NotFoundPage from "./site/pages/not-found"; + +// 后台鉴权门:只在访问 /admin 时跑 me()(官网公开、不打鉴权)。 +// 未登录 → Login;登录后 → AppShell(其内部再渲染 /admin/* 子路由)。 +function AdminGate() { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); @@ -26,14 +33,31 @@ export default function App() { return ; } return ( - - { - clearToken(); - setUser(null); - }} - /> - + { + clearToken(); + setUser(null); + }} + /> + ); +} + +// 用 BrowserRouter(路径式):/ = 官网、/admin = 运维后台。 +// gateway 的 NoRoute 对非 /api、非文件路径回退 index.html,故深链/刷新都能工作。 +export default function App() { + return ( + + + {/* 后台:/admin 前缀,最具体,优先匹配 */} + } /> + {/* 官网:根 / + /download,公开;未知路径落官网 404(保留 header/footer) */} + }> + } /> + } /> + } /> + + + ); } diff --git a/sundynix-admin/src/index.css b/sundynix-admin/src/index.css index ad8a360..b9e5d9d 100644 --- a/sundynix-admin/src/index.css +++ b/sundynix-admin/src/index.css @@ -8,3 +8,111 @@ body, height: 100%; margin: 0; } + +/* ══ 官网落地页设计 token(跟随 logo:青→蓝→紫)══ + 仅 src/site/ 的落地页组件用这些;admin 控制台用默认 gray/violet 调色板,互不影响。 + bg-ground/text-ink 等只作用在 SiteLayout 的容器上(见 site-layout.tsx),不改 body。 */ +:root { + --ground: #f5f7f9; + --surface: #ffffff; + --ink: #151c24; + --ink-2: #526069; + --ink-3: #8595a0; + --hairline: #e0e7ec; + --hairline-strong: #c6d2da; + --accent: #0284c7; + --accent-ink: #075985; + --accent-soft: #e0f2fe; + --code-bg: #ebf1f5; + --term-bg: #0e1620; + --term-ink: #c9e4f5; + --brand-g1: #0891b2; + --brand-g2: #2563eb; + --brand-g3: #9333ea; +} + +.dark { + --ground: #0b1117; + --surface: #111925; + --ink: #e6edf3; + --ink-2: #93a6b4; + --ink-3: #62737f; + --hairline: #1f2c38; + --hairline-strong: #32434f; + --accent: #38bdf8; + --accent-ink: #7dd3fc; + --accent-soft: #0b3049; + --code-bg: #14202b; + --term-bg: #0a121b; + --term-ink: #bfe3f7; + --brand-g1: #22d3ee; + --brand-g2: #60a5fa; + --brand-g3: #c084fc; +} + +/* v3 对 var() 色不支持 /opacity 修饰,用 color-mix 还原 bg-ground/85(sticky 头透底 + 模糊)。 */ +.bg-ground-85 { + background-color: color-mix(in srgb, var(--ground) 85%, transparent); +} + +/* 品牌渐变文字(logo 同源:青→蓝→紫),落地页克制使用 */ +.text-brand-gradient { + background: linear-gradient(92deg, var(--brand-g1), var(--brand-g2) 55%, var(--brand-g3)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +/* 终端光标 */ +@keyframes caret-blink { + 50% { + opacity: 0; + } +} +.caret-blink { + animation: caret-blink 1.1s steps(1) infinite; +} + +/* 滚动进场(配合 Reveal 组件) */ +.reveal { + opacity: 0; + transform: translateY(16px); + transition: + opacity 0.6s ease, + transform 0.6s ease; +} +.reveal-in { + opacity: 1; + transform: none; +} + +/* 终端事件流:逐行浮现 */ +@keyframes term-line-in { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: none; + } +} +.term-line { + opacity: 0; + animation: term-line-in 0.45s ease forwards; +} + +@media (prefers-reduced-motion: reduce) { + .caret-blink, + .term-line { + animation: none; + } + .term-line { + opacity: 1; + } + .reveal { + opacity: 1; + transform: none; + transition: none; + } +} diff --git a/sundynix-admin/src/routes.tsx b/sundynix-admin/src/routes.tsx index 8d9028d..8337e7b 100644 --- a/sundynix-admin/src/routes.tsx +++ b/sundynix-admin/src/routes.tsx @@ -27,84 +27,84 @@ export interface RouteDef { export const routes: RouteDef[] = [ { - path: "/dashboard", + path: "dashboard", label: "概览", group: "分析", ready: true, element: , }, { - path: "/usage", + path: "usage", label: "计费 & 用量", group: "分析", ready: true, element: , }, { - path: "/models", + path: "models", label: "模型", group: "配置", ready: true, element: , }, { - path: "/datasources", + path: "datasources", label: "数据源 & RAG", group: "配置", ready: true, element: , }, { - path: "/prompts", + path: "prompts", label: "提示词", group: "配置", ready: true, element: , }, { - path: "/status", + path: "status", label: "服务状态", group: "运维", ready: true, element: , }, { - path: "/tasks", + path: "tasks", label: "任务观测", group: "运维", ready: true, element: , }, { - path: "/evals", + path: "evals", label: "自动评测", group: "运维", ready: true, element: , }, { - path: "/audit", + path: "audit", label: "审计 & 安全", group: "运维", ready: true, element: , }, { - path: "/tenants", + path: "tenants", label: "租户 & 用户", group: "平台", ready: true, element: , }, { - path: "/spaces", + path: "spaces", label: "空间", group: "平台", ready: true, element: , }, { - path: "/guardrails", + path: "guardrails", label: "安全护栏", group: "平台", ready: true, @@ -112,7 +112,7 @@ export const routes: RouteDef[] = [ }, ]; -export const defaultPath = "/dashboard"; +export const defaultPath = "dashboard"; // 派生分组导航(保持注册顺序)。 export function navGroups(): Array<{ group: string; items: RouteDef[] }> { diff --git a/sundynix-admin/src/shell/AppShell.tsx b/sundynix-admin/src/shell/AppShell.tsx index b612211..33d90cf 100644 --- a/sundynix-admin/src/shell/AppShell.tsx +++ b/sundynix-admin/src/shell/AppShell.tsx @@ -4,11 +4,15 @@ import { NavLink, Routes, Route, Navigate, useLocation } from "react-router-dom" import { routes, navGroups, defaultPath } from "../routes"; import { gatewayOnline, type AuthUser } from "../api"; +// 控制台挂在 /admin 前缀下(根 / 是官网)。路由注册表里 path 为相对(dashboard 等), +// 链接/匹配时统一加 /admin 前缀。 +const ADMIN_BASE = "/admin"; + // 控制台外壳:导航与内容均由路由注册表派生(动态路由)。 export function AppShell({ user, onLogout }: { user: AuthUser; onLogout: () => void }) { const [online, setOnline] = useState(false); const loc = useLocation(); - const current = routes.find((r) => r.path === loc.pathname); + const current = routes.find((r) => `${ADMIN_BASE}/${r.path}` === loc.pathname); useEffect(() => { const ping = () => gatewayOnline().then(setOnline); @@ -31,7 +35,7 @@ export function AppShell({ user, onLogout }: { user: AuthUser; onLogout: () => v {g.items.map((r) => ( `flex items-center justify-between rounded px-3 py-2 text-sm ${ isActive ? "bg-violet-50 font-medium text-violet-700" : "text-gray-600 hover:bg-gray-100" diff --git a/sundynix-admin/src/site/components/download-grid.tsx b/sundynix-admin/src/site/components/download-grid.tsx new file mode 100644 index 0000000..fe2a316 --- /dev/null +++ b/sundynix-admin/src/site/components/download-grid.tsx @@ -0,0 +1,31 @@ +import { DOWNLOADS } from '@/content/site' +import { cn } from '@/lib/utils' + +export function DownloadGrid() { + return ( +
+ {DOWNLOADS.map((d) => ( +
+
{d.os}
+
+ {d.meta} +
+ + {d.action} + +
+ ))} +
+ ) +} diff --git a/sundynix-admin/src/site/components/layout/site-footer.tsx b/sundynix-admin/src/site/components/layout/site-footer.tsx new file mode 100644 index 0000000..7aee537 --- /dev/null +++ b/sundynix-admin/src/site/components/layout/site-footer.tsx @@ -0,0 +1,85 @@ +import { Link } from 'react-router-dom' +import { SITE } from '@/content/site' +import { LogoMark } from '@/components/logo' + +const COLUMNS = [ + { + title: 'PRODUCT', + links: [ + { label: '功能', href: '/#features' }, + { label: '架构', href: '/#architecture' }, + { label: '下载', href: '/download', internal: true }, + ], + }, + { + title: 'RESOURCES', + links: [ + { label: '下载桌面版', href: '/download', internal: true }, + { label: '源码仓库', href: SITE.repo }, + ], + }, +] as const + +export function SiteFooter() { + return ( +
+
+
+

+ + sundynix agentix +

+

{SITE.tagline}

+

+ {SITE.version} · MACOS / WINDOWS / SELF-HOSTED +

+
+ + {COLUMNS.map((col) => ( + + ))} +
+ +
+
+ © 2026 SUNDYNIX AGENTIX + + {SITE.icp} + +
+
+
+ ) +} diff --git a/sundynix-admin/src/site/components/layout/site-header.tsx b/sundynix-admin/src/site/components/layout/site-header.tsx new file mode 100644 index 0000000..93aff17 --- /dev/null +++ b/sundynix-admin/src/site/components/layout/site-header.tsx @@ -0,0 +1,109 @@ +import { useState } from 'react' +import { Link, NavLink } from 'react-router-dom' +import { Menu, Moon, Sun, X } from 'lucide-react' +import { useTheme } from '@/components/theme-provider' +import { LogoMark } from '@/components/logo' +import { cn } from '@/lib/utils' + +const NAV_ITEMS = [ + { label: '产品', to: '/', end: true }, + { label: '功能', to: '/#features' }, + { label: '架构', to: '/#architecture' }, +] + +function ThemeToggle() { + const { resolved, setTheme } = useTheme() + return ( + + ) +} + +export function SiteHeader() { + const [open, setOpen] = useState(false) + + const links = NAV_ITEMS.map((item) => + item.to.includes('#') ? ( + setOpen(false)} + className="text-sm text-ink-2 transition-colors hover:text-ink" + > + {item.label} + + ) : ( + setOpen(false)} + className={({ isActive }) => + cn( + 'text-sm transition-colors hover:text-ink', + isActive ? 'text-ink' : 'text-ink-2', + ) + } + > + {item.label} + + ), + ) + + return ( +
+
+ + + sundynix agentix + + + + +
+ + +
+
+ + {open && ( + + )} +
+ ) +} diff --git a/sundynix-admin/src/site/components/layout/site-layout.tsx b/sundynix-admin/src/site/components/layout/site-layout.tsx new file mode 100644 index 0000000..c029b8b --- /dev/null +++ b/sundynix-admin/src/site/components/layout/site-layout.tsx @@ -0,0 +1,22 @@ +import { useEffect } from 'react' +import { Outlet, useLocation } from 'react-router-dom' +import { SiteHeader } from '@/components/layout/site-header' +import { SiteFooter } from '@/components/layout/site-footer' + +export function SiteLayout() { + const { pathname } = useLocation() + + useEffect(() => { + window.scrollTo(0, 0) + }, [pathname]) + + return ( +
+ +
+ +
+ +
+ ) +} diff --git a/sundynix-admin/src/site/components/logo.tsx b/sundynix-admin/src/site/components/logo.tsx new file mode 100644 index 0000000..f7d9e55 --- /dev/null +++ b/sundynix-admin/src/site/components/logo.tsx @@ -0,0 +1,19 @@ +interface LogoMarkProps { + size?: number + className?: string +} + +/** 品牌 Logo:霓虹数据流 S(用户原版位图裁切,源图在 design-assets/) */ +export function LogoMark({ size = 28, className }: LogoMarkProps) { + return ( + + ) +} diff --git a/sundynix-admin/src/site/components/reveal.tsx b/sundynix-admin/src/site/components/reveal.tsx new file mode 100644 index 0000000..53ebac5 --- /dev/null +++ b/sundynix-admin/src/site/components/reveal.tsx @@ -0,0 +1,45 @@ +import { useEffect, useRef, useState, type ReactNode } from 'react' +import { cn } from '@/lib/utils' + +interface RevealProps { + children: ReactNode + className?: string + /** 进场延迟(毫秒),用于同屏元素错峰 */ + delay?: number +} + +/** 滚动进场:进入视口后淡入上移一次;prefers-reduced-motion 时直接显示 */ +export function Reveal({ children, className, delay = 0 }: RevealProps) { + const ref = useRef(null) + const [visible, setVisible] = useState(false) + + useEffect(() => { + const el = ref.current + if (!el) return + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + setVisible(true) + return + } + const io = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setVisible(true) + io.disconnect() + } + }, + { threshold: 0.15, rootMargin: '0px 0px -40px 0px' }, + ) + io.observe(el) + return () => io.disconnect() + }, []) + + return ( +
+ {children} +
+ ) +} diff --git a/sundynix-admin/src/site/components/section-label.tsx b/sundynix-admin/src/site/components/section-label.tsx new file mode 100644 index 0000000..bcd4339 --- /dev/null +++ b/sundynix-admin/src/site/components/section-label.tsx @@ -0,0 +1,7 @@ +export function SectionLabel({ children }: { children: string }) { + return ( + + {children} + + ) +} diff --git a/sundynix-admin/src/site/components/terminal-demo.tsx b/sundynix-admin/src/site/components/terminal-demo.tsx new file mode 100644 index 0000000..bccfd2f --- /dev/null +++ b/sundynix-admin/src/site/components/terminal-demo.tsx @@ -0,0 +1,41 @@ +import { TERMINAL_DEMO } from '@/content/site' + +/** Hero 事件流终端窗 — 双主题共用深色 */ +export function TerminalDemo() { + return ( +
+
+ {[0, 1, 2].map((i) => ( + + ))} + + {TERMINAL_DEMO.title} + +
+
+
+ + {TERMINAL_DEMO.prompt} +
+
+ ────────────────────────────────── +
+ {TERMINAL_DEMO.events.map((ev, i) => ( +
+ + [{ev.tag}] + {' '} + {ev.text} + {i === TERMINAL_DEMO.events.length - 1 && ( + + )} +
+ ))} +
+
+ ) +} diff --git a/sundynix-admin/src/site/components/theme-provider.tsx b/sundynix-admin/src/site/components/theme-provider.tsx new file mode 100644 index 0000000..dc3be00 --- /dev/null +++ b/sundynix-admin/src/site/components/theme-provider.tsx @@ -0,0 +1,63 @@ +import { + createContext, + useContext, + useEffect, + useState, + type ReactNode, +} from 'react' + +type Theme = 'light' | 'dark' | 'system' + +interface ThemeContextValue { + theme: Theme + resolved: 'light' | 'dark' + setTheme: (t: Theme) => void +} + +const ThemeContext = createContext(null) + +const STORAGE_KEY = 'sundynix-theme' + +function systemTheme(): 'light' | 'dark' { + return window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light' +} + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [theme, setThemeState] = useState( + () => (localStorage.getItem(STORAGE_KEY) as Theme) ?? 'system', + ) + const [resolved, setResolved] = useState<'light' | 'dark'>(() => + theme === 'system' ? systemTheme() : theme, + ) + + useEffect(() => { + const apply = () => { + const r = theme === 'system' ? systemTheme() : theme + setResolved(r) + document.documentElement.classList.toggle('dark', r === 'dark') + } + apply() + const mq = window.matchMedia('(prefers-color-scheme: dark)') + mq.addEventListener('change', apply) + return () => mq.removeEventListener('change', apply) + }, [theme]) + + const setTheme = (t: Theme) => { + localStorage.setItem(STORAGE_KEY, t) + setThemeState(t) + } + + return ( + + {children} + + ) +} + +export function useTheme() { + const ctx = useContext(ThemeContext) + if (!ctx) throw new Error('useTheme must be used within ThemeProvider') + return ctx +} diff --git a/sundynix-admin/src/site/content/site.ts b/sundynix-admin/src/site/content/site.ts new file mode 100644 index 0000000..2adaf30 --- /dev/null +++ b/sundynix-admin/src/site/content/site.ts @@ -0,0 +1,139 @@ +/** 站点文案与数据,改这里即可,不用动组件 */ + +export const SITE = { + /** 静态回退版本号;线上以 /api/releases/latest 为准 */ + version: 'v0.1.2', + tagline: '事件驱动的 AI Agent 工作台', + repo: 'https://git.sundynix.cn/sundynix/sundynix-agentix', + icp: '滇ICP备2025056308号-1', +} + +export const TERMINAL_DEMO = { + title: 'SUNDYNIX.STREAMS.RT-7F2A', + prompt: '帮我调研「2026 国产大模型推理成本」,出一份带图表的报告', + events: [ + { + tag: 'coordinator', + text: '已拆解为 3 个子任务,派发 researcher × 2 · writer × 1', + }, + { + tag: 'knowledge', + text: '三路混合检索 · vector + fulltext + graph · RRF 融合', + }, + { tag: 'harness', text: '输出守卫通过 · 预算 ¥2.00 剩余 ¥1.37' }, + { + tag: 'report', + text: '大纲 5 章已确认,并行撰写中 → 推理成本分析.docx', + ok: true, + }, + ], +} + +export const FEATURES = [ + { + label: 'STUDIO', + title: '可视化编排画布', + desc: 'React Flow 画布拖出工作流,导出 JSON DSL 直接执行;分支路由、map 并行扇出、⌘K 命令面板。', + }, + { + label: 'MULTI-AGENT', + title: '多智能体协作', + desc: 'Orchestrator–Worker 模式:主脑撰写任务简报,并行派发专家 Agent,团队视图实时看它们「上班」。', + }, + { + label: 'KNOWLEDGE', + title: '三路混合检索知识库', + desc: '向量 + 全文 + 知识图谱三路召回,RRF 融合重排;双链笔记、反向链接与关系图谱,检索过程可调试。', + }, + { + label: 'REPORT', + title: '真 · Word 报告生成', + desc: '选题 → 大纲规划 → 分章并行研究撰写,产出原生 .docx(零依赖 OOXML 渲染),另有 PDF / Markdown。', + }, + { + label: 'MEMORY', + title: '长期记忆', + desc: '异步批量固化(ADD/UPDATE/DELETE),近因 × 重要性衰减打分,记忆面板随时可查可改。', + }, + { + label: 'HARNESS', + title: '可靠性套件', + desc: '提示注入拦截、流式密钥脱敏、熔断器、LLM 评审自动纠偏、成本预算上限——生产级守卫全内置。', + }, + { + label: 'SANDBOX', + title: '代码解释器沙箱', + desc: 'AST 静态审查 + Docker 隔离:无网络、非 root、只读根文件系统,让 Agent 放心跑代码。', + }, + { + label: 'STREAMING', + title: '一切皆流式', + desc: 'NATS 零拷贝骨干网直推 SSE/WS,节点级执行轨迹实时可见,Prometheus + OTel 全链路观测。', + }, +] + +export const ARCH_LAYERS = [ + { + id: 'L1 · CLIENT', + name: '桌面工作台', + detail: '· Web / 管理控制台', + tech: 'wails3 · react19', + }, + { + id: 'L2 · GATEWAY', + name: 'API 网关', + detail: '· 鉴权 / DSL 解析 / 计费 / 守卫', + tech: 'gin · postgres · redis', + }, + { + id: 'L4 · DISPATCHER', + name: '图执行引擎', + detail: '· Agent Loop / LLM Pool', + tech: 'eino · openai-compat', + }, + { + id: 'L5 · TOOLS', + name: 'MCP 工具层', + detail: '· 检索 / 解析 / 沙箱', + tech: 'milvus · bleve · neo4j · py3.11', + }, +] + +export const QUICKSTART_STEPS = [ + { + title: '下载或克隆。', + desc: '桌面版开箱即用;自部署走 docker compose。', + }, + { + title: '接入模型。', + desc: '任何 OpenAI 兼容端点:DeepSeek、百炼……填 Key 即可。', + }, + { + title: '喂知识,派任务。', + desc: '拖入文档建库,画布编排或直接对话,看团队开工。', + }, +] + +export const DOWNLOADS = [ + { + os: 'macOS', + meta: 'universal · .app · v0.1.2', + action: '下载 .dmg', + href: `${SITE.repo}/releases`, + highlight: true, + }, + { + os: 'Windows', + meta: 'x64 · .exe · v0.1.2', + action: '下载 .exe', + href: `${SITE.repo}/releases`, + highlight: false, + }, + { + os: '自托管', + meta: 'docker compose · 全平台', + action: '部署文档', + href: SITE.repo, + highlight: false, + }, +] diff --git a/sundynix-admin/src/site/hooks/use-page-title.ts b/sundynix-admin/src/site/hooks/use-page-title.ts new file mode 100644 index 0000000..f696ee8 --- /dev/null +++ b/sundynix-admin/src/site/hooks/use-page-title.ts @@ -0,0 +1,14 @@ +import { useEffect } from 'react' + +const BASE = 'sundynix agentix' +const DEFAULT = `${BASE} — 事件驱动的 AI Agent 工作台` + +/** 设置页面标题;不传参恢复默认 */ +export function usePageTitle(title?: string) { + useEffect(() => { + document.title = title ? `${title} — ${BASE}` : DEFAULT + return () => { + document.title = DEFAULT + } + }, [title]) +} diff --git a/sundynix-admin/src/site/hooks/use-release.ts b/sundynix-admin/src/site/hooks/use-release.ts new file mode 100644 index 0000000..0194a85 --- /dev/null +++ b/sundynix-admin/src/site/hooks/use-release.ts @@ -0,0 +1,22 @@ +import { SITE } from '@/content/site' + +export interface ReleaseInfo { + version: string + page_url: string + macos_url?: string + windows_url?: string + source: 'gitea' | 'fallback' +} + +// 官网融进 admin 后不接 posts/releases 后端(营销落地页 only):直接用静态版本号, +// 去掉原来那次必然失败的 fetch('/api/releases/latest')。 +const STATIC: ReleaseInfo = { + version: SITE.version, + page_url: `${SITE.repo}/releases`, + source: 'fallback', +} + +/** 最新版本信息(静态,来自 content/site.ts)。 */ +export function useRelease(): ReleaseInfo { + return STATIC +} diff --git a/sundynix-admin/src/site/lib/utils.ts b/sundynix-admin/src/site/lib/utils.ts new file mode 100644 index 0000000..fed2fe9 --- /dev/null +++ b/sundynix-admin/src/site/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/sundynix-admin/src/site/pages/download.tsx b/sundynix-admin/src/site/pages/download.tsx new file mode 100644 index 0000000..a1b3e78 --- /dev/null +++ b/sundynix-admin/src/site/pages/download.tsx @@ -0,0 +1,23 @@ +import { SectionLabel } from '@/components/section-label' +import { DownloadGrid } from '@/components/download-grid' +import { usePageTitle } from '@/hooks/use-page-title' + +export default function DownloadPage() { + usePageTitle('下载') + return ( +
+
+
+ DOWNLOAD +

+ 下载 sundynix agentix +

+

+ macOS 与 Windows 桌面版,或用 docker compose 私有化部署。 +

+
+ +
+
+ ) +} diff --git a/sundynix-admin/src/site/pages/home/architecture.tsx b/sundynix-admin/src/site/pages/home/architecture.tsx new file mode 100644 index 0000000..b5d126c --- /dev/null +++ b/sundynix-admin/src/site/pages/home/architecture.tsx @@ -0,0 +1,54 @@ +import { Fragment } from 'react' +import { ARCH_LAYERS } from '@/content/site' +import { SectionLabel } from '@/components/section-label' +import { Reveal } from '@/components/reveal' + +export function Architecture() { + return ( +
+
+ +
+ ARCHITECTURE +

+ 五层架构,一条零拷贝总线 +

+

+ Monolith First,为微服务演进预留缝隙(Morph B)。 +

+
+
+ + + {ARCH_LAYERS.map((layer, i) => ( + + {/* NATS 总线插在 L2 和 L4 之间 */} + {i === 2 && ( +
+ ⇅ NATS JETSTREAM — 零拷贝消息骨干网 · sundynix.* ⇅ +
+ )} +
+ + {layer.id} + + + {layer.name}{' '} + + {layer.detail} + + + + {layer.tech} + +
+
+ ))} +
+
+
+ ) +} diff --git a/sundynix-admin/src/site/pages/home/download-cta.tsx b/sundynix-admin/src/site/pages/home/download-cta.tsx new file mode 100644 index 0000000..b561238 --- /dev/null +++ b/sundynix-admin/src/site/pages/home/download-cta.tsx @@ -0,0 +1,26 @@ +import { SectionLabel } from '@/components/section-label' +import { DownloadGrid } from '@/components/download-grid' +import { Reveal } from '@/components/reveal' + +export function DownloadCta() { + return ( +
+
+ +
+ DOWNLOAD +

+ 把 AI 团队装进桌面 +

+

+ 内置应用内更新,发布流水线自动出包。 +

+
+
+ + + +
+
+ ) +} diff --git a/sundynix-admin/src/site/pages/home/features.tsx b/sundynix-admin/src/site/pages/home/features.tsx new file mode 100644 index 0000000..7bec4f2 --- /dev/null +++ b/sundynix-admin/src/site/pages/home/features.tsx @@ -0,0 +1,40 @@ +import { FEATURES } from '@/content/site' +import { SectionLabel } from '@/components/section-label' +import { Reveal } from '@/components/reveal' + +export function Features() { + return ( +
+
+ +
+ CAPABILITIES +

+ 一个工作台,一支 AI 团队 +

+

+ 从编排到交付,每个环节都是流式的、可观测的、有守卫的。 +

+
+
+ + +
+ {FEATURES.map((f) => ( +
+ + {f.label} + +

{f.title}

+

{f.desc}

+
+ ))} +
+
+
+
+ ) +} diff --git a/sundynix-admin/src/site/pages/home/hero.tsx b/sundynix-admin/src/site/pages/home/hero.tsx new file mode 100644 index 0000000..86ee401 --- /dev/null +++ b/sundynix-admin/src/site/pages/home/hero.tsx @@ -0,0 +1,58 @@ +import { Link } from 'react-router-dom' +import { Download } from 'lucide-react' +import { SITE } from '@/content/site' +import { TerminalDemo } from '@/components/terminal-demo' +import { Reveal } from '@/components/reveal' + +export function Hero() { + return ( +
+ + + + {SITE.version} 已发布 · macOS / Windows + + + + +

+ 事件驱动的 +
+ AI Agent 工作台 + +

+
+ + +

+ 在画布上编排智能体,让多 Agent + 团队替你研究、检索、写出真正的 Word + 报告。桌面开箱即用,也可一条命令私有化部署。 +

+
+ + +
+ + 下载桌面版 + + + $ make demo + +
+
+ + + + +
+ ) +} diff --git a/sundynix-admin/src/site/pages/home/index.tsx b/sundynix-admin/src/site/pages/home/index.tsx new file mode 100644 index 0000000..10e8ab0 --- /dev/null +++ b/sundynix-admin/src/site/pages/home/index.tsx @@ -0,0 +1,17 @@ +import { Hero } from '@/pages/home/hero' +import { Features } from '@/pages/home/features' +import { Architecture } from '@/pages/home/architecture' +import { Quickstart } from '@/pages/home/quickstart' +import { DownloadCta } from '@/pages/home/download-cta' + +export default function HomePage() { + return ( + <> + + + + + + + ) +} diff --git a/sundynix-admin/src/site/pages/home/quickstart.tsx b/sundynix-admin/src/site/pages/home/quickstart.tsx new file mode 100644 index 0000000..45b3f18 --- /dev/null +++ b/sundynix-admin/src/site/pages/home/quickstart.tsx @@ -0,0 +1,56 @@ +import { QUICKSTART_STEPS } from '@/content/site' +import { SectionLabel } from '@/components/section-label' +import { Reveal } from '@/components/reveal' + +export function Quickstart() { + return ( +
+
+ +
+ QUICKSTART +

+ 五分钟跑起来 +

+
+
+ + +
+            
+              # 内嵌 NATS,零配置体验
+              {'\n'}
+              $ git clone
+              https://git.sundynix.cn/sundynix/sundynix-agentix{'\n'}
+              $ make demo{'\n'}
+              {'\n'}
+              # 或一条命令私有化部署
+              {'\n'}
+              $ docker compose up -d
+            
+          
+ +
    + {QUICKSTART_STEPS.map((step, i) => ( +
  • + + 0{i + 1} + + + {step.title} + {step.desc} + +
  • + ))} +
+
+
+
+ ) +} diff --git a/sundynix-admin/src/site/pages/not-found.tsx b/sundynix-admin/src/site/pages/not-found.tsx new file mode 100644 index 0000000..41b64a8 --- /dev/null +++ b/sundynix-admin/src/site/pages/not-found.tsx @@ -0,0 +1,25 @@ +import { Link } from 'react-router-dom' +import { usePageTitle } from '@/hooks/use-page-title' + +export default function NotFoundPage() { + usePageTitle('页面不存在') + return ( +
+

+ 404 · NOT FOUND +

+

+ 这条事件流不存在 +

+

+ 地址可能已变更,回首页重新出发。 +

+ + 回首页 + +
+ ) +} diff --git a/sundynix-admin/tailwind.config.js b/sundynix-admin/tailwind.config.js index a440ac6..9da054c 100644 --- a/sundynix-admin/tailwind.config.js +++ b/sundynix-admin/tailwind.config.js @@ -1,6 +1,38 @@ /** @type {import('tailwindcss').Config} */ export default { content: ["./index.html", "./src/**/*.{ts,tsx}"], - theme: { extend: {} }, + darkMode: "class", + theme: { + extend: { + // 官网落地页设计 token(跟随 logo:青→蓝→紫)。值引用 CSS 变量(见 src/index.css 的 + // :root/.dark),admin 自身页面用默认 gray/violet 调色板、不碰这些 token,故不冲突。 + colors: { + ground: "var(--ground)", + surface: "var(--surface)", + ink: "var(--ink)", + "ink-2": "var(--ink-2)", + "ink-3": "var(--ink-3)", + hairline: "var(--hairline)", + "hairline-strong": "var(--hairline-strong)", + accent: "var(--accent)", + "accent-ink": "var(--accent-ink)", + "accent-soft": "var(--accent-soft)", + code: "var(--code-bg)", + term: "var(--term-bg)", + "term-ink": "var(--term-ink)", + }, + fontFamily: { + sans: [ + "-apple-system", + "PingFang SC", + "Hiragino Sans GB", + "Microsoft YaHei", + "Noto Sans SC", + "sans-serif", + ], + mono: ["ui-monospace", "SF Mono", "JetBrains Mono", "Menlo", "Consolas", "monospace"], + }, + }, + }, plugins: [], }; diff --git a/sundynix-admin/tsconfig.json b/sundynix-admin/tsconfig.json index 75411a3..a949670 100644 --- a/sundynix-admin/tsconfig.json +++ b/sundynix-admin/tsconfig.json @@ -10,7 +10,9 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "strict": true + "strict": true, + "baseUrl": ".", + "paths": { "@/*": ["./src/site/*"] } }, "include": ["src"] } diff --git a/sundynix-admin/vite.config.ts b/sundynix-admin/vite.config.ts index f67cd1d..d54dc93 100644 --- a/sundynix-admin/vite.config.ts +++ b/sundynix-admin/vite.config.ts @@ -1,9 +1,12 @@ /// import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import path from "node:path"; export default defineConfig({ plugins: [react()], + // 官网落地页融进 admin,代码在 src/site/ 下且沿用 @ 别名导入(@/content/site 等)。 + resolve: { alias: { "@": path.resolve(__dirname, "./src/site") } }, server: { port: 5174 }, // 单元/组件测试:纯逻辑 + 关键控制面(jsdom 环境)。运行:npm test test: {