1 Commits
15 changed files with 560 additions and 206 deletions
+3 -4
View File
@@ -33,13 +33,13 @@ export default function App() {
const content = useMemo(() => {
switch (page) {
case "overview":
return <OverviewPage />;
return <OverviewPage onNavigate={setPage} />;
case "catalog":
return <CatalogPage />;
case "my-cli":
return <MyCliPage />;
return <MyCliPage onNavigate={setPage} />;
case "config":
return <ConfigCenterPage />;
return <ConfigCenterPage onNavigate={setPage} />;
case "backup":
return <BackupPage />;
case "settings":
@@ -49,7 +49,6 @@ export default function App() {
return (
<div className="app-shell">
<div className="bg-grid grid-drift" aria-hidden="true" />
<Sidebar current={page} onNavigate={setPage} />
<div className="app-main">
<Header title={PAGE_META[page].title} warningCount={warningCount} />
+35 -1
View File
@@ -1,12 +1,46 @@
/**
* CLI 双字母头像缩写注册表(视觉规范 v1.3 §7:全系统唯一来源)
* 规则:① 首两个字母(大写);② 冲突时保留先收录者,后收录方取「首字母 + 辨识辅音」;
* ③ 仍冲突升级「首字母 + 末字母」。新收录 CLI 必须先在此注册,不得现场发明。
*/
const CLI_MONOGRAMS: Record<string, string> = {
codex: "CO",
"claude-code": "CL",
gemini: "GE",
copilot: "CP",
kimi: "KI",
qwen: "QW",
codebuddy: "CB",
opencode: "OC",
crush: "CR",
goose: "GO",
aider: "AI",
cursor: "CU",
cline: "CN",
warp: "WA",
/* 第二批 4 个预注册(暂缓接入,登记防碰撞) */
grok: "GR",
amp: "AM",
"deepseek-dsh": "DS",
plandex: "PL",
};
/** 未注册时的兜底:取主名单词首两个字母(大写),仅用于未来新增且尚未登记的过渡态 */
function fallbackAbbr(name: string): string {
return name.replace(/\s+/g, "").slice(0, 2).toUpperCase();
}
/** CLI 单色字母头像(视觉规范 §7:双字母单色,--ad-bg-2 底 + 1px --ad-border */
export function CliMonogram({
id,
name,
size = 32,
}: {
id: string;
name: string;
size?: number;
}) {
const chars = name.replace(/\s+/g, "").slice(0, 2).toUpperCase();
const chars = CLI_MONOGRAMS[id] ?? fallbackAbbr(name);
return (
<span
className="monogram"
+18 -17
View File
@@ -10,24 +10,25 @@ interface HeaderProps {
export function Header({ title, warningCount = 0 }: HeaderProps) {
return (
<header className="header">
<h1 className="header-title">{title}</h1>
<div className="header-actions">
<div className="search-box">
<Search size={16} strokeWidth={1.5} className="search-icon" aria-hidden="true" />
<input
type="search"
placeholder="搜索 CLI…"
aria-label="搜索 CLI"
// 搜索功能属于后续波次,Wave 0 仅为占位
/>
<div className="header-inner">
<h1 className="header-title">{title}</h1>
<div className="header-actions">
<div className="search-box">
<Search size={16} strokeWidth={1.5} className="search-icon" aria-hidden="true" />
<input
type="search"
placeholder="搜索 CLI"
aria-label="搜索 CLI"
/>
</div>
{warningCount > 0 && (
<button type="button" className="env-chip">
<span className="status-dot warn breathe" aria-hidden="true" />
<span>{warningCount} </span>
<ChevronDown size={12} strokeWidth={1.5} aria-hidden="true" />
</button>
)}
</div>
{warningCount > 0 && (
<button type="button" className="env-chip">
<span className="status-dot warn breathe" aria-hidden="true" />
<span>{warningCount} </span>
<ChevronDown size={12} strokeWidth={1.5} aria-hidden="true" />
</button>
)}
</div>
</header>
);
+16 -4
View File
@@ -11,17 +11,19 @@ interface KpiCardProps {
label: string;
value: number;
tone: KpiTone;
/** 副标题:对象点名句式(视觉规范 §3.1.1) */
/** 副标题:对象点名句式(视觉规范 §3.1.1)0 值用「确认句 + 行动」短句 */
subtitle: string;
/** 0 值行动后缀(如「去目录看看」),仅当存在对应页面动作时出现 */
action?: { label: string; onClick: () => void };
}
/**
* KPI 卡(视觉规范 §3.1.1):
* - 径向渐变玻璃板 + 上缘高光 + --ad-shadow-panel
* - 顶部色条贯通整宽;数值为 0 时色条降灰、数字回 --ad-text-1
* - 数字随状态色贯穿
* - 数字随状态色贯穿;0 值副文案 = 确认句 + 青字行动后缀
*/
export function KpiCard({ label, value, tone, subtitle }: KpiCardProps) {
export function KpiCard({ label, value, tone, subtitle, action }: KpiCardProps) {
const color = value > 0 ? TONE_COLOR[tone] : "var(--ad-border)";
const valueColor = value > 0 ? TONE_COLOR[tone] : "var(--ad-text-1)";
return (
@@ -32,7 +34,17 @@ export function KpiCard({ label, value, tone, subtitle }: KpiCardProps) {
{value}
</div>
<div className="kpi-label">{label}</div>
<div className="kpi-subtitle">{subtitle}</div>
<div className="kpi-subtitle">
{subtitle}
{action && value === 0 && (
<>
{" · "}
<button type="button" className="link-btn" onClick={action.onClick}>
{action.label}
</button>
</>
)}
</div>
</div>
</div>
);
+9 -4
View File
@@ -1,17 +1,22 @@
import { Archive } from "lucide-react";
/** 备份与迁移(PRD §7):创建备份、查看内容、恢复。Wave 0 占位。 */
/** 备份与迁移(PRD §7):创建备份、查看内容、恢复。空态按 v1.3 §3.7。 */
export function BackupPage() {
return (
<div className="placeholder-page">
<div className="placeholder-icon">
<Archive size={40} strokeWidth={1.5} aria-hidden="true" />
</div>
<h2 className="placeholder-title"></h2>
<h2 className="placeholder-title"></h2>
<p className="placeholder-desc">
Wave 4
CLI
</p>
<button type="button" className="btn btn-secondary" disabled>
<button
type="button"
className="btn btn-secondary"
disabled
title="安装 CLI 后才能创建备份"
>
</button>
</div>
+2 -4
View File
@@ -2,7 +2,6 @@ import { useMemo, useState } from "react";
import { Search } from "lucide-react";
import { useCatalog } from "../hooks/useCatalog";
import { StatusBadge } from "../components/StatusBadge";
import { MonoChip } from "../components/MonoChip";
import { CliMonogram } from "../components/CliMonogram";
type StatusFilter = "all" | "installed" | "uninstalled" | "update";
@@ -90,17 +89,16 @@ export function CatalogPage() {
{filtered.map((entry) => (
<article className="catalog-card" key={entry.id}>
<div className="catalog-card-top">
<CliMonogram name={entry.name} size={40} />
<CliMonogram id={entry.id} name={entry.name} size={40} />
<div className="catalog-card-head">
<div className="catalog-card-name">{entry.name_zh}</div>
<div className="catalog-card-vendor">{entry.vendor}</div>
</div>
</div>
<p className="catalog-card-desc"> · Wave 1 </p>
<p className="catalog-card-desc"> · </p>
<div className="catalog-card-bottom">
<StatusBadge kind="uninstalled" label="未安装" />
<span className="catalog-platforms">Windows · Linux</span>
<MonoChip>v1</MonoChip>
</div>
</article>
))}
+6 -5
View File
@@ -1,17 +1,18 @@
import { SlidersHorizontal } from "lucide-react";
import type { PageKey } from "../components/Sidebar";
/** 配置中心(PRD §7):按 CLI 展示中文配置表单。Wave 0 占位。 */
export function ConfigCenterPage() {
/** 配置中心(PRD §7):按 CLI 展示中文配置表单。空态按 v1.3 §3.7。 */
export function ConfigCenterPage({ onNavigate }: { onNavigate: (p: PageKey) => void }) {
return (
<div className="placeholder-page">
<div className="placeholder-icon">
<SlidersHorizontal size={40} strokeWidth={1.5} aria-hidden="true" />
</div>
<h2 className="placeholder-title"></h2>
<h2 className="placeholder-title"> CLI</h2>
<p className="placeholder-desc">
Wave 2 使
CLI
</p>
<button type="button" className="btn btn-secondary" disabled>
<button type="button" className="btn btn-secondary" onClick={() => onNavigate("catalog")}>
CLI
</button>
</div>
+6 -5
View File
@@ -1,17 +1,18 @@
import { SquareTerminal } from "lucide-react";
import type { PageKey } from "../components/Sidebar";
/** 我的 CLIPRD §7):版本 / 路径 / 授权状态。Wave 0 尚无已安装 CLI,展示空态。 */
export function MyCliPage() {
/** 我的 CLIPRD §7):版本 / 路径 / 授权状态。空态按 v1.3 §3.7。 */
export function MyCliPage({ onNavigate }: { onNavigate: (p: PageKey) => void }) {
return (
<div className="placeholder-page">
<div className="placeholder-icon">
<SquareTerminal size={40} strokeWidth={1.5} aria-hidden="true" />
</div>
<h2 className="placeholder-title"> CLI</h2>
<h2 className="placeholder-title"> CLI</h2>
<p className="placeholder-desc">
Wave 1 CLI
CLI
</p>
<button type="button" className="btn btn-primary" disabled>
<button type="button" className="btn btn-primary" onClick={() => onNavigate("catalog")}>
CLI
</button>
</div>
+236 -94
View File
@@ -1,12 +1,13 @@
import { useState } from "react";
import { Activity, Archive, ChevronRight, Lock, Plus } from "lucide-react";
import { Activity, Archive, Lock, Plus, ScanSearch } from "lucide-react";
import { useEnv } from "../hooks/useEnv";
import { useCatalog } from "../hooks/useCatalog";
import { KpiCard } from "../components/KpiCard";
import { MonoChip } from "../components/MonoChip";
import { StatusBadge } from "../components/StatusBadge";
import { CliMonogram } from "../components/CliMonogram";
import type { PlatformEnv, RuntimeInfo } from "../ipc/types";
import type { PlatformEnv, RuntimeInfo, RuntimeStatus, CatalogEntry } from "../ipc/types";
import type { PageKey } from "../components/Sidebar";
/** 平台相关运行时集合(Windows 不看 aptLinux 不看 winget */
const WINDOWS_RUNTIMES = ["node", "npm", "python", "uv", "git", "winget"] as const;
@@ -24,42 +25,61 @@ function runtimeProblems(env: PlatformEnv): { name: string; info: RuntimeInfo }[
return problems;
}
function runtimeStatusText(status: string): string {
/** 本机环境运行时项四元组(v1.3 §3.1.6):状态点 + 名称 + 版本位 + 行动位 */
function runtimeMeta(status: RuntimeStatus): {
dot: string;
missingText: string | null;
needsGuide: boolean;
} {
switch (status) {
case "not_in_path":
return "不在 PATH";
case "installed":
return { dot: "status-dot ok", missingText: null, needsGuide: false };
case "not_installed":
return "未安装";
return { dot: "status-dot unknown", missingText: "未检测到", needsGuide: true };
case "not_in_path":
return { dot: "status-dot warn breathe", missingText: "不在 PATH", needsGuide: false };
case "permission_denied":
return { dot: "status-dot warn breathe", missingText: "无访问权限", needsGuide: false };
case "exec_failed":
return "执行失败";
return { dot: "status-dot warn breathe", missingText: "执行失败", needsGuide: false };
case "version_unparseable":
return "版本未知";
return { dot: "status-dot warn breathe", missingText: "版本未知", needsGuide: false };
default:
return "未知";
return { dot: "status-dot unknown", missingText: "未知", needsGuide: false };
}
}
function RuntimeItem({ name, info }: { name: string; info: RuntimeInfo }) {
const dotClass =
info.status === "installed"
? "status-dot ok"
: info.status === "exec_failed"
? "status-dot error"
: "status-dot warn";
function RuntimeItem({
name,
info,
onNavigate,
}: {
name: string;
info: RuntimeInfo;
onNavigate: (p: PageKey) => void;
}) {
const meta = runtimeMeta(info.status);
return (
<div className="runtime-item">
<span className={dotClass} aria-hidden="true" />
<span className={meta.dot} aria-hidden="true" />
<span className="runtime-name">{name}</span>
{info.version ? (
<MonoChip>{info.version}</MonoChip>
<span className="runtime-version">
<MonoChip>{info.version}</MonoChip>
</span>
) : (
<span className="runtime-missing">{runtimeStatusText(info.status)}</span>
<span className="runtime-missing">{meta.missingText}</span>
)}
{meta.needsGuide && (
<button type="button" className="link-btn runtime-action" onClick={() => onNavigate("catalog")}>
</button>
)}
</div>
);
}
function EnvPanel() {
function EnvPanel({ onNavigate }: { onNavigate: (p: PageKey) => void }) {
const { env, loading, error } = useEnv();
const [showAllPath, setShowAllPath] = useState(false);
@@ -122,7 +142,7 @@ function EnvPanel() {
(key) => {
const info = env.runtimes[key];
if (!info) return null;
return <RuntimeItem key={key} name={key} info={info} />;
return <RuntimeItem key={key} name={key} info={info} onNavigate={onNavigate} />;
},
)}
</div>
@@ -155,100 +175,222 @@ function EnvPanel() {
);
}
/** 总览页(视觉规范 §3.1 + PRD §7):KPI + 最近诊断/快速操作 + CLI 状态网格 + 本机环境 */
export function OverviewPage() {
/** 首跑空态引导插画(v1.3 §7:等距线框终端 + 集装箱 Dock,1px 线,霓虹点缀) */
function OnboardingArt() {
return (
<svg width="200" height="150" viewBox="0 0 200 150" fill="none" aria-hidden="true">
<rect x="24" y="18" width="120" height="86" rx="8" stroke="currentColor" strokeWidth="1" />
<line x1="24" y1="40" x2="144" y2="40" stroke="currentColor" strokeWidth="1" />
<circle cx="36" cy="29" r="2" style={{ fill: "var(--ad-primary)" }} />
<circle cx="46" cy="29" r="2" fill="currentColor" opacity="0.55" />
<circle cx="56" cy="29" r="2" fill="currentColor" opacity="0.55" />
<rect x="36" y="52" width="58" height="6" rx="3" fill="currentColor" opacity="0.5" />
<rect x="36" y="66" width="84" height="6" rx="3" fill="currentColor" opacity="0.32" />
<rect x="36" y="80" width="46" height="6" rx="3" style={{ fill: "var(--ad-accent)" }} opacity="0.8" />
<rect x="24" y="116" width="36" height="26" rx="4" stroke="currentColor" strokeWidth="1" />
<rect x="66" y="116" width="36" height="26" rx="4" stroke="currentColor" strokeWidth="1" />
<rect x="108" y="116" width="36" height="26" rx="4" stroke="currentColor" strokeWidth="1" />
<line x1="24" y1="142" x2="144" y2="142" stroke="currentColor" strokeWidth="1" opacity="0.4" />
</svg>
);
}
/** 首跑引导区(v1.3 §3.1.5:插画 + 三句式文案 + 主行动按钮) */
function Onboarding({ onNavigate }: { onNavigate: (p: PageKey) => void }) {
return (
<div className="panel onboarding">
<div className="onboarding-art">
<OnboardingArt />
</div>
<div className="onboarding-copy">
<h2 className="onboarding-title"></h2>
<p className="onboarding-desc">
Agent CLI
</p>
<div className="onboarding-actions">
<button type="button" className="btn btn-primary" onClick={() => onNavigate("catalog")}>
CLI
</button>
<button type="button" className="btn btn-secondary">
</button>
</div>
</div>
</div>
);
}
/** 最近诊断卡(v1.3 §3.1.2:无记录时展示空态,不渲染占位行与「查看全部」) */
function DiagPanel() {
// Wave 0.5 尚无诊断引擎数据,恒为空态(语义按 §3.7,不虚构记录)
const records: { cli: string; text: string; time: string }[] = [];
return (
<div className="panel diag-panel">
<div className="panel-head">
<h2 className="panel-title"></h2>
</div>
{records.length === 0 ? (
<div className="diag-empty">
<span className="diag-empty-text"></span>
<button type="button" className="btn btn-secondary">
<ScanSearch size={16} strokeWidth={1.5} aria-hidden="true" />
</button>
</div>
) : (
<ul className="diag-list">
{records.map((r) => (
<li className="diag-row" key={r.cli}>
<span className="status-dot ok" aria-hidden="true" />
<span className="diag-cli">{r.cli}</span>
<span className="diag-text">{r.text}</span>
<span className="diag-time">{r.time}</span>
</li>
))}
</ul>
)}
</div>
);
}
/** 快速操作卡(v1.3 §3.1.3:三按钮竖向操作组,创建备份依赖数据置灰) */
function QuickActions({
firstRun,
onNavigate,
}: {
firstRun: boolean;
onNavigate: (p: PageKey) => void;
}) {
return (
<div className="panel quick-card">
<h2 className="panel-title"></h2>
<button type="button" className="btn btn-primary" onClick={() => onNavigate("catalog")}>
<Plus size={16} strokeWidth={1.5} aria-hidden="true" /> CLI
</button>
<button type="button" className="btn btn-secondary">
<Activity size={16} strokeWidth={1.5} aria-hidden="true" />
</button>
<button
type="button"
className="btn btn-secondary"
disabled
title={firstRun ? "安装 CLI 后才能创建备份" : "暂无内容可备份"}
>
<Archive size={16} strokeWidth={1.5} aria-hidden="true" />
</button>
<div className="recent-installs">
<div className="group-label"></div>
<p className="group-empty"></p>
</div>
<p className="safety-note">
<Lock size={12} strokeWidth={1.5} aria-hidden="true" />
</p>
</div>
);
}
/** CLI 状态网格块(v1.3 §3.1.4;首跑精简为两行 + 安装入口,§3.1.5) */
function CliBlock({
entry,
compact,
onNavigate,
}: {
entry: CatalogEntry;
compact: boolean;
onNavigate: (p: PageKey) => void;
}) {
const installed = entry.status === "installed";
return (
<div className="cli-block">
<div className="cli-block-top">
<CliMonogram id={entry.id} name={entry.name} size={32} />
<span className="cli-block-name">{entry.name_zh}</span>
<StatusBadge kind={installed ? "installed" : "uninstalled"} label={installed ? "已安装" : "未安装"} />
</div>
<div className="cli-block-version">
{installed ? null : <span className="version-missing"></span>}
</div>
{!compact && (
<div className="cli-block-auth">
{installed ? (
<>
<span className="status-dot ok" aria-hidden="true" />
<span></span>
</>
) : (
<>
<span className="status-dot unknown" aria-hidden="true" />
<span> · </span>
</>
)}
</div>
)}
{compact && (
<div className="cli-block-install">
<button type="button" className="link-btn" onClick={() => onNavigate("catalog")}>
</button>
</div>
)}
</div>
);
}
interface OverviewProps {
onNavigate: (p: PageKey) => void;
}
/** 总览页(视觉规范 §3.1 + PRD §7):首跑空态 / KPI + 诊断/快速操作 + CLI 状态网格 + 本机环境 */
export function OverviewPage({ onNavigate }: OverviewProps) {
const { env } = useEnv();
const { entries } = useCatalog();
const installedCount = entries.filter((e) => e.status === "installed").length;
const firstRun = installedCount === 0; // 无安装且无诊断记录(当前无诊断引擎)
const warningCount = env ? runtimeProblems(env).length : 0;
const warningNames = env ? runtimeProblems(env).map((p) => p.name) : [];
return (
<div className="overview">
{/* ① KPI 一排 4 张 */}
<section className="kpi-grid" aria-label="概览指标">
<KpiCard label="已安装" value={0} tone="primary" subtitle="暂无已安装 CLI" />
<KpiCard label="待授权" value={0} tone="attention" subtitle="暂无待授权项" />
<KpiCard label="可更新" value={0} tone="accent" subtitle="暂无可用更新" />
<KpiCard
label="环境异常"
value={warningCount}
tone="warning"
subtitle={warningCount > 0 ? warningNames.join(" · ") : "环境正常"}
/>
</section>
{/* ① KPI 一排 4 张(首跑空态隐藏,v1.3 §3.1.5) */}
{!firstRun && (
<section className="kpi-grid" aria-label="概览指标">
<KpiCard label="已安装" value={installedCount} tone="primary" subtitle="暂无安装" action={{ label: "去目录看看", onClick: () => onNavigate("catalog") }} />
<KpiCard label="待授权" value={0} tone="attention" subtitle="暂无待授权" />
<KpiCard label="可更新" value={0} tone="accent" subtitle="全部已是最新" />
<KpiCard
label="环境异常"
value={warningCount}
tone="warning"
subtitle={warningCount > 0 ? warningNames.join(" · ") : "环境正常"}
/>
</section>
)}
{/* ② 中段:左 2/3 最近诊断 + 右 1/3 快速操作 */}
{/* ② 中段:诊断/引导 + 快速操作(超宽三栏见 global.css) */}
<section className="overview-mid">
<div className="panel diag-panel">
<div className="panel-head">
<h2 className="panel-title"></h2>
<button type="button" className="link-btn">
<ChevronRight size={14} strokeWidth={1.5} aria-hidden="true" />
</button>
</div>
<ul className="diag-list">
<li className="diag-row">
<span className="status-dot ok" aria-hidden="true" />
<span className="diag-cli"></span>
<span className="diag-text"> Wave 1 </span>
<span className="diag-time"></span>
</li>
</ul>
</div>
<div className="panel quick-card">
<h2 className="panel-title"></h2>
<button type="button" className="btn btn-primary" disabled>
<Plus size={16} strokeWidth={1.5} aria-hidden="true" /> CLI
</button>
<button type="button" className="btn btn-secondary" disabled>
<Activity size={16} strokeWidth={1.5} aria-hidden="true" />
</button>
<button type="button" className="btn btn-secondary" disabled>
<Archive size={16} strokeWidth={1.5} aria-hidden="true" />
</button>
<div className="recent-installs">
<div className="group-label"></div>
<p className="group-empty"></p>
</div>
<p className="safety-note">
<Lock size={12} strokeWidth={1.5} aria-hidden="true" />
</p>
</div>
{firstRun ? <Onboarding onNavigate={onNavigate} /> : <DiagPanel />}
<QuickActions firstRun={firstRun} onNavigate={onNavigate} />
</section>
{/* ③ 下段:CLI 状态网格通栏 */}
{/* ③ 下段:CLI 状态网格通栏(首跑精简:可安装列表 + 安装入口) */}
<section className="panel cli-status-panel">
<div className="panel-head">
<h2 className="panel-title">CLI </h2>
<button type="button" className="link-btn">
{entries.length}
<ChevronRight size={14} strokeWidth={1.5} aria-hidden="true" />
</button>
<h2 className="panel-title">
{firstRun ? `可安装的 CLI${entries.length}` : "CLI 状态"}
</h2>
{/* 「管理全部 N 个」入口:仅当未全量展示时才渲染(v1.3 §3.1.4),本页恒全量展示,故不渲染 */}
</div>
<div className="cli-status-grid">
{entries.map((entry) => (
<div className="cli-block" key={entry.id}>
<div className="cli-block-top">
<CliMonogram name={entry.name} size={32} />
<span className="cli-block-name">{entry.name_zh}</span>
<StatusBadge kind="uninstalled" label="未安装" />
</div>
<div className="cli-block-version">
<MonoChip></MonoChip>
</div>
<div className="cli-block-auth">
<span className="status-dot unknown" aria-hidden="true" />
<span> · </span>
</div>
</div>
<CliBlock key={entry.id} entry={entry} compact={firstRun} onNavigate={onNavigate} />
))}
</div>
</section>
{/* ④ 本机环境(真实检测结果,架构 §5) */}
<EnvPanel />
{/* ④ 本机环境(真实检测结果,架构 §5;超宽断点上提为第三栏 */}
<EnvPanel onNavigate={onNavigate} />
</div>
);
}
+3 -3
View File
@@ -9,14 +9,14 @@ export function SettingsPage() {
<div className="setting-row">
<div className="setting-info">
<div className="setting-label"></div>
<div className="setting-hint"> CLI Wave 1 </div>
<div className="setting-hint"> CLI </div>
</div>
<span className="setting-value"></span>
</div>
<div className="setting-row">
<div className="setting-info">
<div className="setting-label"></div>
<div className="setting-hint"> AgentDock CLI Wave 4 </div>
<div className="setting-hint"> AgentDock CLI </div>
</div>
<span className="setting-value"></span>
</div>
@@ -30,7 +30,7 @@ export function SettingsPage() {
<div className="setting-row">
<div className="setting-info">
<div className="setting-label"></div>
<div className="setting-hint">Wave 1 </div>
<div className="setting-hint"></div>
</div>
<span className="setting-value"></span>
</div>
+215 -49
View File
@@ -1,6 +1,6 @@
/* ============================================================
* AgentDock 全局样式(Wave 0
* 全部颜色/辉光/动效时长来自 tokens/(视觉规范 v1.2),
* AgentDock 全局样式(Wave 0.5
* 全部颜色/辉光/动效时长来自 tokens/(视觉规范 v1.3),
* 本文件不出现任何魔法色值;布局尺寸来自规范 §3.0/§6。
* ============================================================ */
@@ -12,18 +12,22 @@
}
html,
body,
#root {
body {
height: 100%;
}
#root {
min-height: 100%;
}
body {
font-family: var(--ad-font-ui);
font-size: var(--ad-text-m-size);
line-height: var(--ad-text-m-height);
color: var(--ad-text-1);
background: var(--ad-bg-0);
overflow: hidden;
background: var(--ad-bg-gradient); /* v1.3 §3.0:静态深空渐变,网格已移除 */
overflow-y: auto; /* v1.3 §3.0:主滚动容器 = 窗口,滚动条贴右缘 */
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
@@ -31,25 +35,40 @@ button {
font-family: inherit;
}
/* ---------- 背景网格(§3.0:5% + 边缘羽化,低档静态 ---------- */
.bg-grid {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
background-image:
linear-gradient(var(--ad-grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--ad-grid-color) 1px, transparent 1px);
background-size: var(--ad-grid-size) var(--ad-grid-size);
-webkit-mask-image: var(--ad-grid-mask);
mask-image: var(--ad-grid-mask);
/* ---------- 自定义滚动条(v1.3 §2.7:10px、灰蓝滑块、透明轨道、不发光 ---------- */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--ad-scrollbar-thumb);
border-radius: 5px;
border: 2px solid transparent;
background-clip: padding-box;
min-height: 32px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--ad-scrollbar-thumb-hover);
background-clip: padding-box;
}
/* 标准属性兜底(Firefox 等) */
* {
scrollbar-width: thin;
scrollbar-color: var(--ad-scrollbar-thumb) transparent;
}
/* ---------- 应用外壳 ---------- */
.app-shell {
position: relative;
display: flex;
height: 100%;
min-height: 100vh;
z-index: 1;
}
@@ -60,17 +79,29 @@ button {
flex-direction: column;
}
.app-content {
flex: 1;
overflow-y: auto;
/* 内容区与顶栏内区共用同一宽度体系(v1.3 §3.0:顶栏与内容区右缘对齐) */
.app-content,
.header-inner {
width: 100%;
max-width: 1440px; /* §6 宽阔断点锁宽居中 */
margin: 0 auto;
padding: var(--ad-space-6) var(--ad-space-8);
max-width: var(--ad-frame-max);
margin-left: auto;
margin-right: auto;
padding-left: var(--ad-frame-pad-x);
padding-right: var(--ad-frame-pad-x);
}
/* ---------- 侧边栏(§3.0 ---------- */
.app-content {
flex: 1;
padding-top: var(--ad-space-6);
padding-bottom: var(--ad-space-6);
}
/* ---------- 侧边栏(§3.0:232px 贴左,窗口滚动时保持固定) ---------- */
.sidebar {
position: sticky;
top: 0;
align-self: flex-start;
height: 100vh;
width: 232px;
flex-shrink: 0;
display: flex;
@@ -141,17 +172,23 @@ button {
margin-left: auto;
}
/* ---------- 顶栏(§3.0 ---------- */
/* ---------- 顶栏(§3.0:通栏 56px,内区与内容区同一宽度体系,滚动时吸顶 ---------- */
.header {
position: sticky;
top: 0;
z-index: 10;
height: 56px;
flex-shrink: 0;
background: var(--ad-bg-0);
border-bottom: 1px solid var(--ad-border);
}
.header-inner {
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--ad-space-4);
padding: 0 var(--ad-space-8);
background: var(--ad-bg-0);
border-bottom: 1px solid var(--ad-border);
}
.header-title {
@@ -233,6 +270,8 @@ button {
justify-content: center;
gap: var(--ad-space-2);
height: 36px;
min-width: 96px; /* v1.3 §2.6.1 */
max-width: 360px; /* v1.3 §2.6.1:禁止通栏 */
padding: 0 var(--ad-space-4);
border: 1px solid transparent;
border-radius: var(--ad-radius-s);
@@ -245,7 +284,7 @@ button {
}
.btn:disabled {
opacity: 0.55;
opacity: 0.4; /* v1.3 §3.7:无意义操作置灰 */
cursor: not-allowed;
}
@@ -524,8 +563,12 @@ button {
gap: var(--ad-space-3);
}
/* v1.3 §2.6.1:快速操作组定宽(组宽 = min(内容宽, 360px)),左对齐,不通栏 */
.quick-card .btn {
width: 100%;
width: min(100%, 360px);
justify-content: flex-start;
padding-left: var(--ad-space-4);
padding-right: var(--ad-space-4);
}
.recent-installs {
@@ -592,10 +635,6 @@ button {
color: var(--ad-text-1);
}
.cli-block-version .mono-chip {
color: var(--ad-text-3);
}
.cli-block-auth {
display: flex;
align-items: center;
@@ -604,6 +643,80 @@ button {
font-size: var(--ad-text-xs-size);
}
/* 未安装卡版本位(v1.3 §3.1.4:废止「—」chip,改灰字「未安装」) */
.cli-block-version .version-missing {
color: var(--ad-text-3);
font-size: var(--ad-text-s-size);
}
/* 首跑精简块:安装入口(v1.3 §3.1.5) */
.cli-block-install {
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: var(--ad-space-1);
}
/* ---------- 总览:首跑空态引导区(v1.3 §3.1.5 ---------- */
.onboarding {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--ad-space-6);
align-items: center;
padding: var(--ad-space-6);
background: var(--ad-bg-1);
border: 1px solid var(--ad-border);
border-radius: var(--ad-radius-l);
}
.onboarding-art {
display: flex;
align-items: center;
justify-content: center;
color: var(--ad-border-bright);
}
.onboarding-copy {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--ad-space-3);
}
.onboarding-title {
font-size: var(--ad-text-xl-size);
font-weight: var(--ad-text-xl-weight);
color: var(--ad-text-1);
}
.onboarding-desc {
max-width: 480px;
color: var(--ad-text-2);
font-size: var(--ad-text-m-size);
line-height: 1.6;
}
.onboarding-actions {
display: flex;
align-items: center;
gap: var(--ad-space-3);
margin-top: var(--ad-space-2);
}
/* 诊断空态(v1.3 §3.1.2 / §3.7:无记录不渲染占位行) */
.diag-empty {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--ad-space-3);
padding: var(--ad-space-5) var(--ad-space-2);
}
.diag-empty-text {
color: var(--ad-text-2);
font-size: var(--ad-text-s-size);
}
/* ---------- 总览:本机环境 ---------- */
.env-panel {
margin-top: var(--ad-space-6);
@@ -640,10 +753,11 @@ button {
word-break: break-all;
}
/* 本机环境运行时项(v1.3 §3.1.6:四元组同构,点—名称—版本—行动,行高 28px) */
.runtime-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: var(--ad-space-3);
grid-template-columns: repeat(4, 1fr);
gap: var(--ad-space-4);
align-content: start;
}
@@ -651,17 +765,32 @@ button {
display: flex;
align-items: center;
gap: var(--ad-space-2);
min-height: 28px;
min-width: 0;
}
.runtime-name {
flex: 1;
color: var(--ad-text-2);
color: var(--ad-text-1);
font-size: var(--ad-text-s-size);
white-space: nowrap;
}
.runtime-version {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.runtime-missing {
color: var(--ad-warning);
color: var(--ad-text-3);
font-size: var(--ad-text-xs-size);
white-space: nowrap;
}
.runtime-action {
margin-left: auto;
white-space: nowrap;
}
.path-block {
@@ -903,8 +1032,8 @@ button {
}
}
/* ---------- 响应式(§6 断点) ---------- */
/* 紧凑 9601199px:导航收起 64pxKPI 2×2,中段上下堆叠,状态网格 2 列 */
/* ---------- 响应式(v1.3 §6 断点) ---------- */
/* 紧凑 9601199px:导航收起 64pxKPI 2×2,中段上下堆叠 */
@media (max-width: 1199px) {
.sidebar {
width: 64px;
@@ -942,7 +1071,7 @@ button {
grid-template-columns: 1fr;
}
.cli-status-grid {
.runtime-grid {
grid-template-columns: repeat(2, 1fr);
}
@@ -983,11 +1112,48 @@ button {
}
}
/* 标准 12001599px:默认样式(完整导航、KPI 一排 4、诊断+快速 2:1、状态网格 3 列) */
/* 宽阔 ≥1600px:内容锁 1440 居中,状态网格 4 列 */
@media (min-width: 1600px) {
.cli-status-grid {
grid-template-columns: repeat(4, 1fr);
/* 标准 12001919px:默认(完整导航、KPI 一排 4、诊断+快速 2:1、内容区 max 1440 居中)
* 宽阔 19202559px:内容区 max 1760 居中(v1.3 §6
* 超宽 ≥2560px:取消居中锁宽,内容区全宽、边距 48px(v1.3 §6 */
@media (min-width: 1920px) and (max-width: 2559px) {
:root {
--ad-frame-max: 1760px;
}
}
@media (min-width: 2560px) {
:root {
--ad-frame-max: none;
--ad-frame-pad-x: var(--ad-space-12);
}
}
/* 超宽 ≥2560px:中段三栏(诊断 1/2 + 快速操作 1/4 + 本机环境 1/4),本机环境上提 */
@media (min-width: 2560px) {
.overview {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
column-gap: var(--ad-space-4);
row-gap: var(--ad-space-6);
}
.overview .kpi-grid {
grid-column: 1 / -1;
margin-bottom: 0;
}
.overview .overview-mid {
grid-column: 1 / 3;
margin-bottom: 0;
}
.overview .cli-status-panel {
grid-column: 1 / -1;
}
.overview .env-panel {
grid-column: 3;
margin-top: 0;
}
}
+4
View File
@@ -12,6 +12,10 @@
--ad-border: #22304A; /* 常规 1px 分隔线 */
--ad-border-bright: #35507A; /* 悬浮/选中的边框 */
/* ---- 自定义滚动条(v1.3 §2.7:灰蓝滑块、透明轨道、不发光)---- */
--ad-scrollbar-thumb: #1A2438; /* = --ad-bg-3 */
--ad-scrollbar-thumb-hover: #35507A; /* = --ad-border-bright */
/* ---- 霓虹强调色(全系统只有这两个霓虹色)---- */
--ad-primary: #00E5FF; /* 主操作、链接、聚焦、选中态(青) */
--ad-primary-hover: #4DEFFF; /* 主操作悬浮 */
+2 -4
View File
@@ -30,10 +30,8 @@
--ad-btn-accent-glow-hover: 0 0 14px rgba(255, 77, 219, 0.30),
0 0 36px rgba(255, 77, 219, 0.12);
/* ---- 背景网格(§3.0:要么可见要么不做---- */
--ad-grid-color: rgba(0, 229, 255, 0.05); /* 透明度固定 5%,施工员不许再调 */
--ad-grid-size: 40px;
--ad-grid-mask: radial-gradient(120% 90% at 50% 40%, #000 30%, transparent 75%);
/* ---- 背景氛围(v1.3 §3.0:网格移除,降级为静态深空渐变,三档通用---- */
--ad-bg-gradient: radial-gradient(120% 90% at 50% 0%, #0D1420 0%, #070B14 70%);
/* ---- 弹窗遮罩(§3.6---- */
--ad-modal-mask: rgba(4, 7, 13, 0.7);
+5
View File
@@ -13,6 +13,11 @@
--ad-space-6: 24px;
--ad-space-8: 32px;
--ad-space-10: 40px;
--ad-space-12: 48px; /* v1.3 §3.0 超宽断点内容区边距 */
/* ---- 内容区宽度体系(v1.3 §6:标准 1440 / 宽阔 1760 / 超宽全宽)---- */
--ad-frame-max: 1440px;
--ad-frame-pad-x: var(--ad-space-8);
/* ---- 圆角 ---- */
--ad-radius-s: 4px; /* 标签、小按钮、chip */
-12
View File
@@ -34,18 +34,6 @@
box-shadow: var(--ad-glow-balanced);
}
/* 背景网格漂移(§4.8,60s 一周,仅高档/中档) */
[data-fx-tier="low"] .grid-drift {
animation: none;
}
.grid-drift {
animation: grid-drift 60s linear infinite;
}
@keyframes grid-drift {
from { background-position: 0 0; }
to { background-position: 40px 40px; }
}
/* 状态点呼吸(§4.5:仅警告/错误/授权可能过期呼吸;低档停用) */
[data-fx-tier="low"] .status-dot-breathe {
animation: none;