Pārlūkot izejas kodu

Refine shell layout and default entry flow

zhaozhi 2 nedēļas atpakaļ
vecāks
revīzija
afd91e38d8

+ 0 - 4
app/agents/page.tsx

@@ -19,10 +19,6 @@ export default async function AgentsPage({ searchParams }: AgentsPageProps) {
 
   return (
     <main className="page-shell">
-      <section className="page-title">
-        <h1>Agent 观察室</h1>
-        <p>这里直接看 OpenClaw 各个 agent 的任务、心跳、队列、主机和最近输出,方便做真实运维观察。</p>
-      </section>
       <AgentsDashboard initialFeed={feed} initialStatus={activeStatus} />
     </main>
   );

+ 0 - 5
app/chat/page.tsx

@@ -403,11 +403,6 @@ export default function ChatPage() {
 
   return (
     <main className="page-shell chat-wechat-page">
-      <section className="page-title chat-wechat-page-title">
-        <h1>局域网聊天室</h1>
-        <p>像群聊一样在同一个界面里收发文字、图片和文件,PC 端同时显示在线成员。</p>
-      </section>
-
       <section className="chat-wechat-shell">
         <aside className="chat-wechat-sidebar" aria-label="在线用户">
           <div className="chat-wechat-sidebar__title">局域网在线设备</div>

+ 92 - 24
app/globals.css

@@ -275,7 +275,7 @@ textarea {
   display: grid;
   grid-template-columns: 1.15fr 0.85fr;
   gap: 24px;
-  padding: 44px 0 26px;
+  padding: 28px 0 18px;
 }
 
 .card {
@@ -286,7 +286,7 @@ textarea {
 }
 
 .hero__main {
-  padding: 36px;
+  padding: 28px;
 }
 
 .eyebrow {
@@ -301,7 +301,7 @@ textarea {
 }
 
 .hero h1 {
-  margin: 18px 0 14px;
+  margin: 14px 0 12px;
   font-size: clamp(2.2rem, 5vw, 4.3rem);
   line-height: 1.05;
 }
@@ -309,15 +309,15 @@ textarea {
 .hero p {
   margin: 0;
   color: var(--muted);
-  font-size: 1.03rem;
-  line-height: 1.75;
+  font-size: 0.98rem;
+  line-height: 1.68;
 }
 
 .hero__actions {
   display: flex;
   flex-wrap: wrap;
   gap: 12px;
-  margin-top: 28px;
+  margin-top: 20px;
 }
 
 .button {
@@ -610,6 +610,7 @@ textarea {
 }
 
 .library-shell {
+  margin-top: 44px;
   padding: 22px;
   border-radius: 32px;
   margin-bottom: 40px;
@@ -1079,17 +1080,24 @@ textarea {
 }
 
 .agents-shell--compact {
+  margin-top: 44px;
   padding: 20px 22px 24px;
 }
 
 .agents-monitor-top {
   display: flex;
-  align-items: center;
+  align-items: flex-start;
   justify-content: space-between;
   gap: 16px;
   margin-bottom: 14px;
 }
 
+.agents-monitor-controls {
+  display: grid;
+  justify-items: end;
+  gap: 12px;
+}
+
 .agents-monitor-source {
   display: grid;
   gap: 6px;
@@ -1104,24 +1112,50 @@ textarea {
   font-size: 0.92rem;
 }
 
-.agents-monitor-strip {
+.agents-monitor-summary {
   display: grid;
-  grid-template-columns: repeat(5, minmax(0, 1fr));
+  grid-template-columns: repeat(5, minmax(72px, auto));
   gap: 10px;
-  margin-bottom: 16px;
 }
 
-.agents-monitor-strip__item {
-  min-height: 52px;
-  padding: 0 16px;
+.agents-monitor-summary__item {
+  min-height: 58px;
+  min-width: 84px;
+  padding: 8px 14px;
   border: 1px solid var(--line);
   border-radius: 16px;
   background: rgba(255, 250, 243, 0.82);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  color: #5f544b;
-  font-weight: 600;
+  display: grid;
+  align-content: center;
+  justify-items: center;
+  gap: 4px;
+  color: #6d6056;
+}
+
+.agents-monitor-summary__item strong {
+  font-size: 1.08rem;
+  line-height: 1;
+  color: #2d241f;
+}
+
+.agents-monitor-summary__item span {
+  font-size: 0.82rem;
+}
+
+.agents-monitor-summary__item--working strong {
+  color: #4f8552;
+}
+
+.agents-monitor-summary__item--idle strong {
+  color: #8a6f39;
+}
+
+.agents-monitor-summary__item--warning strong {
+  color: #b26f20;
+}
+
+.agents-monitor-summary__item--offline strong {
+  color: #b45a49;
 }
 
 .agents-worklist {
@@ -1694,8 +1728,10 @@ textarea {
 
 .chat-wechat-shell {
   width: 100%;
+  margin-top: 44px;
   min-height: 0;
-  height: 100%;
+  min-height: calc(100vh - 210px);
+  height: calc(100vh - 210px);
   border-radius: 30px;
   overflow: hidden;
   border: 1px solid rgba(255, 255, 255, 0.72);
@@ -2282,8 +2318,12 @@ textarea {
     align-items: stretch;
   }
 
-  .agents-monitor-strip {
-    grid-template-columns: repeat(2, minmax(0, 1fr));
+  .agents-monitor-controls {
+    justify-items: stretch;
+  }
+
+  .agents-monitor-summary {
+    grid-template-columns: repeat(5, minmax(0, 1fr));
   }
 
   .agents-worklist {
@@ -2532,6 +2572,7 @@ textarea {
 
   .chat-wechat-shell {
     width: 100%;
+    margin-top: 0;
     min-height: 0;
     height: 100%;
     border-radius: 0;
@@ -2740,7 +2781,7 @@ textarea {
   }
 
   .site-header__inner {
-    padding: 12px 0;
+    padding: 10px 0;
   }
 
   .site-drawer,
@@ -2767,9 +2808,15 @@ textarea {
     color: #241c16;
   }
 
+  .site-brand__tag {
+    display: none;
+  }
+
   .site-menu-button {
     border-color: rgba(60, 42, 27, 0.14);
     background: #fff8f0;
+    width: 58px;
+    height: 58px;
   }
 
   .hero {
@@ -2777,6 +2824,12 @@ textarea {
     gap: 16px;
   }
 
+  .library-shell,
+  .agents-shell--compact,
+  .chat-wechat-shell {
+    margin-top: 18px;
+  }
+
   .hero__main,
   .section-card,
   .library-shell,
@@ -2820,8 +2873,12 @@ textarea {
     grid-template-columns: 1fr;
   }
 
-  .agents-monitor-strip {
-    grid-template-columns: 1fr;
+  .agents-monitor-summary {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+
+  .agents-monitor-summary__item:first-child {
+    grid-column: span 2;
   }
 
   .agents-worklist {
@@ -3029,3 +3086,14 @@ textarea {
     padding: 16px 16px 24px;
   }
 }
+
+@media (max-width: 640px) {
+  .chat-wechat-page {
+    width: 100vw;
+    max-width: 100vw;
+  }
+
+  .chat-wechat-shell {
+    margin-top: 0 !important;
+  }
+}

+ 0 - 6
app/library/page.tsx

@@ -4,11 +4,6 @@ import { demoBooks } from "@/data/demo-books";
 export default function LibraryPage() {
   return (
     <main className="page-shell">
-      <section className="page-title">
-        <h1>书架</h1>
-        <p>现在书架里放了 4 本演示书,方便你直接测试书架布局、阅读页排版,以及从书架进入阅读器的整体体验。</p>
-      </section>
-
       <section className="library-shell">
         <div className="library-grid">
           {demoBooks.map((book) => (
@@ -36,4 +31,3 @@ export default function LibraryPage() {
     </main>
   );
 }
-

+ 2 - 101
app/page.tsx

@@ -1,104 +1,5 @@
-import Link from "next/link";
-
-const chatHighlights = [
-  "打开网站即自动生成临时设备身份",
-  "公共聊天室支持文字、图片、文件、粘贴和拖拽",
-  "手机和电脑共用同一个入口,便于局域网内快速传资料"
-];
-
-const libraryHighlights = [
-  "书架内置几本演示书,方便先验证阅读体验",
-  "阅读页采用干净居中的版心,弱干扰、强正文",
-  "后续可继续接入章节目录、阅读设置和进度同步"
-];
-
-const agentHighlights = [
-  "按成员卡片方式查看每个 agent 的角色、状态和当前任务",
-  "适合后续接入真实心跳、队列、产出摘要和运行节点信息",
-  "同样兼容手机查看,适合做局域网里的总览大盘"
-];
+import { redirect } from "next/navigation";
 
 export default function HomePage() {
-  return (
-    <main className="page-shell">
-      <section className="hero">
-        <div className="card hero__main">
-          <div className="eyebrow">一期骨架已就位</div>
-          <h1>一个入口,承接局域网聊天和小说阅读。</h1>
-          <p>
-            这个版本先把信息架构、页面风格和响应式骨架搭起来。你可以先从聊天室进入,也可以直接去书架页查看阅读器的版式方向。
-          </p>
-          <div className="hero__actions">
-            <Link className="button button--primary" href="/chat">
-              打开聊天室
-            </Link>
-            <Link className="button button--secondary" href="/library">
-              进入书架
-            </Link>
-          </div>
-        </div>
-
-        <div className="hero__aside">
-          <div className="stat-card">
-            <h3>局域网公共聊天室</h3>
-            <p>为几台电脑和手机之间传送文本、图片、文件而设计,页面结构已经按后续实时通信预留好了区域。</p>
-          </div>
-          <div className="stat-card">
-            <h3>简洁阅读器</h3>
-            <p>书架、章节、阅读页都已经连通,后续只需要继续接入真实数据和阅读设置即可。</p>
-          </div>
-        </div>
-      </section>
-
-      <section className="home-sections">
-        <div className="section-card">
-          <h2>模块一:聊天室</h2>
-          <p>先做一个你自己在局域网里使用的公共聊天空间,操作简单,文件流转顺手。</p>
-          <div className="section-card__list">
-            {chatHighlights.map((item, index) => (
-              <div className="section-card__item" key={item}>
-                <div className="section-card__index">{index + 1}</div>
-                <div>{item}</div>
-              </div>
-            ))}
-          </div>
-          <Link className="button button--primary" href="/chat">
-            去看聊天页骨架
-          </Link>
-        </div>
-
-        <div className="section-card">
-          <h2>模块二:书架与阅读</h2>
-          <p>阅读页先走你要的简洁路线,把版心、段落、操作区和手机排版先定下来。</p>
-          <div className="section-card__list">
-            {libraryHighlights.map((item, index) => (
-              <div className="section-card__item" key={item}>
-                <div className="section-card__index">{index + 1}</div>
-                <div>{item}</div>
-              </div>
-            ))}
-          </div>
-          <Link className="button button--secondary" href="/library">
-            去看书架骨架
-          </Link>
-        </div>
-
-        <div className="section-card">
-          <h2>模块三:Agent 观察页</h2>
-          <p>新增一个偏大盘视角的成员总览,主要用于观察各个 agent 当前的状态和最近输出。</p>
-          <div className="section-card__list">
-            {agentHighlights.map((item, index) => (
-              <div className="section-card__item" key={item}>
-                <div className="section-card__index">{index + 1}</div>
-                <div>{item}</div>
-              </div>
-            ))}
-          </div>
-          <Link className="button button--primary" href="/agents">
-            去看 Agent 页面
-          </Link>
-        </div>
-      </section>
-    </main>
-  );
+  redirect("/chat");
 }

+ 26 - 27
components/agents/agents-dashboard.tsx

@@ -3,7 +3,6 @@
 import Link from "next/link";
 import { startTransition, useEffect, useState } from "react";
 import { AgentAvatar } from "@/components/agents/agent-avatar";
-import { agentStatusFilters } from "@/data/demo-agents";
 import { AgentFeed, AgentStatus } from "@/types/agent";
 
 const statusClassMap = {
@@ -32,13 +31,12 @@ function formatTimeLabel(value: string) {
 
 export function AgentsDashboard({ initialFeed, initialStatus }: AgentsDashboardProps) {
   const [feed, setFeed] = useState(initialFeed);
-  const [activeStatus, setActiveStatus] = useState<"all" | AgentStatus>(initialStatus);
 
   useEffect(() => {
     let mounted = true;
 
     async function loadAgents() {
-      const query = activeStatus === "all" ? "" : `?status=${activeStatus}`;
+      const query = initialStatus === "all" ? "" : `?status=${initialStatus}`;
       const response = await fetch(`/api/agents${query}`, { cache: "no-store" });
 
       if (!response.ok) {
@@ -65,7 +63,7 @@ export function AgentsDashboard({ initialFeed, initialStatus }: AgentsDashboardP
       mounted = false;
       window.clearInterval(interval);
     };
-  }, [activeStatus]);
+  }, [initialStatus]);
 
   const agents = feed.agents;
   const counts = {
@@ -85,30 +83,31 @@ export function AgentsDashboard({ initialFeed, initialStatus }: AgentsDashboardP
           </span>
         </div>
 
-        <div className="chip-row">
-          {agentStatusFilters.map((filter) => (
-            <button
-              key={filter.key}
-              type="button"
-              className={`filter-chip${activeStatus === filter.key ? " filter-chip--active" : ""}`}
-              onClick={() => {
-                if (filter.key === "all" || isAgentStatus(filter.key)) {
-                  setActiveStatus(filter.key);
-                }
-              }}
-            >
-              {filter.label}
-            </button>
-          ))}
-        </div>
-      </div>
+        <div className="agents-monitor-controls">
+          <div className="agents-monitor-summary" aria-label="Agent 状态统计">
+            <div className="agents-monitor-summary__item">
+              <strong>{agents.length}</strong>
+              <span>全部</span>
+            </div>
+            <div className="agents-monitor-summary__item agents-monitor-summary__item--working">
+              <strong>{counts.working}</strong>
+              <span>工作中</span>
+            </div>
+            <div className="agents-monitor-summary__item agents-monitor-summary__item--idle">
+              <strong>{counts.idle}</strong>
+              <span>待命</span>
+            </div>
+            <div className="agents-monitor-summary__item agents-monitor-summary__item--warning">
+              <strong>{counts.warning}</strong>
+              <span>待确认</span>
+            </div>
+            <div className="agents-monitor-summary__item agents-monitor-summary__item--offline">
+              <strong>{counts.offline}</strong>
+              <span>离线</span>
+            </div>
+          </div>
 
-      <div className="agents-monitor-strip">
-        <div className="agents-monitor-strip__item">工作中 {counts.working}</div>
-        <div className="agents-monitor-strip__item">待命 {counts.idle}</div>
-        <div className="agents-monitor-strip__item">待确认 {counts.warning}</div>
-        <div className="agents-monitor-strip__item">离线 {counts.offline}</div>
-        <div className="agents-monitor-strip__item">总计 {agents.length}</div>
+        </div>
       </div>
 
       <div className="agents-worklist">

+ 2 - 7
components/site-header.tsx

@@ -5,7 +5,6 @@ import { usePathname } from "next/navigation";
 import { useEffect, useState } from "react";
 
 const navItems = [
-  { href: "/", label: "首页" },
   { href: "/chat", label: "聊天室" },
   { href: "/library", label: "书架" },
   { href: "/agents", label: "Agent 观察" }
@@ -37,7 +36,7 @@ export function SiteHeader() {
   return (
     <header className={`site-header${menuOpen ? " site-header--menu-open" : ""}`}>
       <div className="page-shell site-header__inner">
-        <Link href="/" className="site-brand">
+        <Link href="/chat" className="site-brand">
           <div className="site-brand__mark">LAN</div>
           <div>
             <div className="site-brand__name">局域网书房</div>
@@ -92,11 +91,7 @@ export function SiteHeader() {
 
           <nav className="site-drawer__nav" aria-label="移动端主导航">
             {navItems.map((item) => (
-              <Link
-                key={item.href}
-                href={item.href}
-                className={pathname === item.href ? "is-active" : undefined}
-              >
+              <Link key={item.href} href={item.href} className={pathname === item.href ? "is-active" : undefined}>
                 {item.label}
               </Link>
             ))}