初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,30 @@
|
||||
# 魂镜前端本地环境变量示例
|
||||
# 复制为 .env.local 后按需修改;.env.local 不提交到 Git。
|
||||
# 仅 NEXT_PUBLIC_ 开头的变量会暴露给浏览器(environment-config-design §2)。
|
||||
|
||||
# BFF 连接后端的服务端地址(仅 Next 服务端读取,不暴露给浏览器)。
|
||||
# 浏览器所有请求同源走 BFF(/api/proxy、/api/sse、/api/auth),客户端不需要后端地址;
|
||||
# BFF route 用此变量 fetch 后端。前后端分机部署时改为后端服务器地址(跨公网须 https://)。
|
||||
BACKEND_URL=http://localhost:8000
|
||||
|
||||
# 是否显示前端调试面板(本地开发默认开启)。
|
||||
NEXT_PUBLIC_ENABLE_DEBUG_PANEL=true
|
||||
|
||||
# 是否启用 Avatar 表现层原型(当前阶段默认关闭)。
|
||||
NEXT_PUBLIC_ENABLE_AVATAR=false
|
||||
|
||||
# 是否显示内驱力候选入口(当前开发周期为预留入口)。
|
||||
NEXT_PUBLIC_ENABLE_DRIVE_PANEL=true
|
||||
|
||||
# 是否显示世界模型入口(当前开发周期为预留入口)。
|
||||
NEXT_PUBLIC_ENABLE_WORLD_PANEL=true
|
||||
|
||||
# 是否启用前端 Mock 数据层(lib/mock)。
|
||||
# 为 true 时,apiRequest 命中内置路由表即返回 Mock 数据、SSE 用定时器模拟流式
|
||||
# 回复,可在无后端时验证全部页面与交互;后端就绪后改为 false 即恢复真实请求。
|
||||
# 复制 .env.local.example 为 .env.local 后按需开启(.env.local 不提交 Git)。
|
||||
NEXT_PUBLIC_ENABLE_MOCK=false
|
||||
|
||||
# 是否显示司命台管理控制台入口。
|
||||
# 开启后侧栏出现「治理 → 司命台」入口(仅管理员可见)。
|
||||
NEXT_PUBLIC_ENABLE_ADMIN_PANEL=false
|
||||
@@ -0,0 +1,5 @@
|
||||
# 前端提交前检查(frontend/CLAUDE.md:lint 与 type-check 必须同时通过方可提交)。
|
||||
# git 在仓库根(父目录)执行 hook,故先进入前端目录:
|
||||
# - lint-staged 仅对暂存文件跑 eslint/prettier(非前端文件自动跳过)
|
||||
# - type-check 整体校验类型
|
||||
cd frontend && pnpm lint-staged && pnpm type-check
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
|
||||
"*.{js,cjs,mjs}": ["eslint --fix", "prettier --write"],
|
||||
"*.{json,css}": ["prettier --write"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# 依赖与构建产物
|
||||
node_modules/
|
||||
.next/
|
||||
out/
|
||||
build/
|
||||
coverage/
|
||||
|
||||
# 锁文件与类型缓存(不应格式化)
|
||||
pnpm-lock.yaml
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
# Next.js 生成的类型声明
|
||||
.next/types/
|
||||
.next/dev/types/
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
# 前端开发规范
|
||||
|
||||
## 技术栈
|
||||
|
||||
Next.js + TypeScript + React + Tailwind CSS + Zustand/Jotai + pnpm
|
||||
|
||||
### 核心依赖
|
||||
|
||||
- **实时通信**: SSE / WebSocket
|
||||
- **可视化**: Mermaid + 图谱可视化库
|
||||
- **测试**: Vitest + Testing Library
|
||||
- **代码质量**: ESLint (flat config) + Prettier + Husky + lint-staged
|
||||
|
||||
## 开发命令
|
||||
|
||||
```bash
|
||||
pnpm install # 安装依赖
|
||||
pnpm add <pkg> # 添加依赖
|
||||
pnpm remove <pkg> # 移除依赖
|
||||
|
||||
pnpm dev # 启动开发服务器 (默认端口 3000)
|
||||
pnpm build # 构建生产版本
|
||||
pnpm start # 启动生产服务器
|
||||
|
||||
pnpm lint # 运行 ESLint 检查
|
||||
pnpm type-check # TypeScript 类型检查
|
||||
|
||||
# 代码格式化(Prettier)
|
||||
pnpm format # 格式化全部文件
|
||||
pnpm format:check # 检查格式是否合规(CI / 提交钩子使用)
|
||||
|
||||
# 测试(Vitest)
|
||||
pnpm test # 运行全部测试(单次)
|
||||
pnpm test:watch # 监听模式
|
||||
pnpm test:coverage # 运行测试并生成覆盖率报告
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── app/ # Next.js App Router
|
||||
│ │ ├── admin/ # 司命台管理控制台(增强 UI,开关驱动)
|
||||
│ │ ├── archive/ # 归档(记忆时间河)
|
||||
│ │ ├── characters/ # 人物
|
||||
│ │ ├── chat/ # 对话
|
||||
│ │ ├── memory/ # 记忆管理
|
||||
│ │ ├── personality/ # 人格
|
||||
│ │ ├── runs/[runId]/ # 运行轨迹
|
||||
│ │ ├── tools/ # 工具调用
|
||||
│ │ ├── error.tsx # 全局错误边界(Client,reset)
|
||||
│ │ ├── global-error.tsx # 根级错误兜底(自带 html/body)
|
||||
│ │ ├── layout.tsx # 根布局
|
||||
│ │ ├── loading.tsx # 路由加载态
|
||||
│ │ ├── not-found.tsx # 404
|
||||
│ │ ├── page.tsx # 首页(重定向 /chat)
|
||||
│ │ └── template.tsx # 页面过渡动画
|
||||
│ ├── components/ # React 组件(按功能域组织)
|
||||
│ │ ├── admin/ # 司命台签名组件(StarMap/VermilionAction…)
|
||||
│ │ ├── archive/ characters/ chat/ memory/ personality/ runs/ tools/
|
||||
│ │ ├── layout/ # 应用壳层 / 侧栏 / 主题初始化
|
||||
│ │ ├── shared/ # 共享状态视图(Empty/Error/Loading)
|
||||
│ │ └── ui/ # 通用 UI(Button/Card/BronzeMirror/Badge…)
|
||||
│ ├── lib/
|
||||
│ │ ├── api/ # API 客户端(client.ts 统一出口 + 按域分模块)
|
||||
│ │ ├── constants/ # 枚举标签常量
|
||||
│ │ ├── hooks/ # 自定义 Hooks
|
||||
│ │ ├── mock/ # Mock 数据层(无后端时驱动全页面)
|
||||
│ │ ├── stores/ # Zustand/Jotai stores
|
||||
│ │ └── utils/ # 工具函数(cn/format/error)
|
||||
│ ├── test/ # Vitest 全局 setup
|
||||
│ ├── types/ # TypeScript 类型定义
|
||||
│ └── styles/ # 全局样式(琉璃镜 token)
|
||||
├── .husky/ # Git hooks(pre-commit)
|
||||
├── public/ # 静态资源
|
||||
├── .env.local # 环境变量 (本地)
|
||||
├── .lintstagedrc.json # lint-staged 配置
|
||||
├── eslint.config.mjs # ESLint flat config
|
||||
├── vitest.config.ts # Vitest 配置
|
||||
└── next.config.ts # Next.js 配置(rewrites 代理 /api/v1 → 后端)
|
||||
```
|
||||
|
||||
> 组织约定:采用 `src/` 根 + `components` 按功能域分子目录 + `stores` 置于 `lib` 下的方式,不设独立 `features/` 顶层目录。浏览器请求经 `next.config.ts` 的 rewrites 同源代理到后端(规避跨域,含 SSE);认证与鉴权统一走 BFF:token 仅存于 httpOnly cookie,由 App Router 内的 `/api/auth/*`(认证)、`/api/proxy/*`(业务 REST 代理)、`/api/sse/*`(SSE 流式代理)在服务端读取并注入 Authorization,客户端 JS 不接触 token。
|
||||
|
||||
## 代码规范
|
||||
|
||||
### 页面设计
|
||||
|
||||
图标:统一使用 Lucide 图标,界面全程禁止使用表情符号。
|
||||
设计标准:对标 Awwwards 顶级网站水准,达到 Awwwards、FWA、CSS Design Awards 每日最佳网站同等设计品质。
|
||||
创意自由度:将浏览器视作交互式艺术画布,跳出传统布局框架,追求先锋视觉风格、实验性排版、流畅物理动效、极具冲击力的文字版式。
|
||||
沉浸式体验:融合代码、高级渲染逻辑、图片素材,打造统一完整的精品页面,做出突破常规 UI 认知、令人惊艳的数字交互体验。
|
||||
|
||||
### 通用规范
|
||||
|
||||
- 遵循 ESLint + Prettier 配置,提交前必须通过 lint 与格式检查
|
||||
- 使用 TypeScript 严格模式 (`strict: true`),禁止使用 `any` 类型
|
||||
- 组件使用函数式组件 + Hooks,优先使用 Server Components,必要时使用 `'use client'`
|
||||
- 文件命名: 组件使用 PascalCase (`UserProfile.tsx`),工具函数使用 camelCase (`formatDate.ts`)
|
||||
- 使用 `path` 别名 (`@/*`) 导入模块,避免相对路径层级过深
|
||||
- 状态管理: 全局状态使用 Zustand/Jotai,局部状态使用 React useState/useReducer
|
||||
|
||||
### CSS 变量引用
|
||||
|
||||
CSS 变量引用遵循 **三层递进规则**,优先级由上至下。
|
||||
|
||||
#### 规则
|
||||
|
||||
```text
|
||||
Tailwind 映射类 → 优先使用
|
||||
Tailwind 任意值 [var(--)] → 仅当变量不在 @theme 映射表中时使用
|
||||
内联 style → 仅限复合值 / 动态运行时值
|
||||
```
|
||||
|
||||
#### 具体对照
|
||||
|
||||
`globals.css` 的 `@theme inline {}` 块已映射的部分,直接用 Tailwind 类:
|
||||
|
||||
```tsx
|
||||
// ✅ 正确:变量在 @theme inline {} 中有 --color-X 映射 → 用 Tailwind 类
|
||||
<div className="bg-patina text-fg-dim border-rule" />
|
||||
<span className="text-cinnabar" />
|
||||
<div className="bg-night-bronze text-fg" />
|
||||
<div className="bg-surface" />
|
||||
<button className="border-gilt text-gilt" />
|
||||
<div className="bg-btn-primary-bg" /> {/* Layer 2 组件 token 同理 */}
|
||||
|
||||
// ✅ 正确:变量未映射到 @theme inline {} → 用 Tailwind 任意值
|
||||
<div className="bg-[var(--patina-wash)]" />
|
||||
<span className="text-[var(--fg-faint)]" />
|
||||
<div className="border-[var(--gilt-dim)]" />
|
||||
<div className="bg-[var(--cinnabar-wash)]" />
|
||||
```
|
||||
|
||||
> `bg-[var(--x)]` 是 Tailwind v4 的合法任意值语法(非 hack),属于 Tailwind 体系内,**不是** style 违规。`--patina-wash`、`--patina-dim`、`--gilt-faint`、`--gilt-dim`、`--cinnabar-wash`、`--cinnabar-dim` 当前均未直接映射,使用任意值是正确做法。
|
||||
|
||||
```tsx
|
||||
// ❌ 禁止:变量已在 @theme inline {} 中有映射,却用任意值
|
||||
// 不要用 bg-[var(--patina)] —— 应写 bg-patina(已映射)
|
||||
// 不要用 text-[var(--cinnabar)] —— 应写 text-cinnabar(已映射)
|
||||
// 不要用 border-[var(--rule)] —— 应写 border-rule(已映射)
|
||||
// 不要用 text-[var(--fg-dim)] —— 应写 text-fg-dim(已映射)
|
||||
|
||||
// ❌ 禁止:用内联 style 表达单值 CSS 变量
|
||||
// 不要写 style={{ color: "var(--patina)" }}
|
||||
// 不要写 style={{ background: "var(--surface)" }}
|
||||
|
||||
// ✅ 正确:渐变色/复合 CSS 用内联 style(Tailwind 无法表达)
|
||||
<div style={{
|
||||
background: "linear-gradient(180deg, var(--patina-wash), var(--surface))",
|
||||
boxShadow: "inset 0 1px 0 var(--face-in-top), var(--shadow)",
|
||||
}} />
|
||||
|
||||
// ✅ 正确:动态运行时值用内联 style
|
||||
<div style={{ color: health === "ok" ? "var(--patina)" : "var(--cinnabar)" }} />
|
||||
|
||||
// ✅ 正确:SVG 属性用内联 style 或 className
|
||||
<circle style={{ fill: "var(--patina)", stroke: "var(--gilt)" }} />
|
||||
```
|
||||
|
||||
#### 映射速查
|
||||
|
||||
当前 `@theme inline {}` 中已映射的 铜镜鉴心 变量(查阅 `globals.css` 确认完整列表):
|
||||
|
||||
| CSS 变量 | Tailwind 类前缀 | 示例 |
|
||||
| ---------- | ---------------- | ------ |
|
||||
| `--patina` | `patina` | `bg-patina` `text-patina` `border-patina` |
|
||||
| `--patina-bright` | `patina-bright` | `bg-patina-bright` |
|
||||
| `--gilt` | `gilt` | `bg-gilt` `text-gilt` |
|
||||
| `--cinnabar` | `cinnabar` | `bg-cinnabar` `text-cinnabar` |
|
||||
| `--ink` | `ink` | `bg-ink` |
|
||||
| `--night-bronze` | `night-bronze` | `bg-night-bronze` |
|
||||
| `--fg` | `fg` | `text-fg` |
|
||||
| `--fg-dim` | `fg-dim` | `text-fg-dim` |
|
||||
| `--fg-faint` | `fg-faint` | `text-fg-faint` |
|
||||
| `--rule` | `rule` | `border-rule` `bg-rule` |
|
||||
| `--surface` | `surface` | `bg-surface` |
|
||||
| `--surface-hi` | `surface-hi` | `bg-surface-hi` |
|
||||
| `--patina-wash` | `patina-wash` | `bg-patina-wash` |
|
||||
| `--patina-dim` | `patina-dim` | `border-patina-dim` `bg-patina-dim` |
|
||||
| `--gilt-dim` | `gilt-dim` | `border-gilt-dim` `text-gilt-dim` `bg-gilt-dim` |
|
||||
| `--gilt-faint` | `gilt-faint` | `bg-gilt-faint` |
|
||||
| `--cinnabar-wash` | `cinnabar-wash` | `bg-cinnabar-wash` |
|
||||
| `--cinnabar-dim` | `cinnabar-dim` | `border-cinnabar-dim` |
|
||||
| `--glass` | `glass` | `bg-glass` |
|
||||
| `--glass-border` | `glass-border` | `border-glass-border` |
|
||||
| `--overlay-hover` | `overlay-hover` | `bg-overlay-hover` |
|
||||
|
||||
**未映射**(用 `bg-[var(--x)]`):`--face-sheen` `--sweep-gradient` `--face-in-top` `--face-in-bot` `--shadow` `--aurora-*` `--code-*` 以及 SVG 特有的 `--raw-*`
|
||||
|
||||
#### 例外清单
|
||||
|
||||
以下变量是复合 CSS 值或动画属性,**必须**用内联 style,不能抽象为 Tailwind 类:
|
||||
|
||||
- 渐变/光照效果:`--face-sheen` `--sweep-gradient` `--face-in-top` `--face-in-bot`
|
||||
- 阴影:`--shadow`
|
||||
- 背景光晕:`--aurora-*`
|
||||
- 动态 `color-mix()` 表达式
|
||||
|
||||
> 规则生效日:此约定写入后,新代码必须遵守。存量代码暂不回溯,待 P0/P1 功能补齐后再做 codemod 迁移。
|
||||
|
||||
- 统一通过 `lib/api`(`client.ts` 出口)调用后端,禁止在组件内散落 fetch
|
||||
- SSE 连接封装在 `lib/sse-client.ts`,不在组件内直接实例化 EventSource
|
||||
- 所有 API 调用必须有 TypeScript 类型定义和错误处理(`ApiError` 统一承载 code/trace_id)
|
||||
|
||||
## 可视化规范
|
||||
|
||||
- 图谱可视化使用专用图谱库,Mermaid 用于流程图/时序图等
|
||||
- 可视化组件必须支持响应式和主题切换 (暗色/亮色)
|
||||
|
||||
## 代码审查
|
||||
|
||||
```bash
|
||||
pnpm lint # ESLint 检查
|
||||
pnpm type-check # TypeScript 类型检查
|
||||
pnpm format:check # Prettier 格式检查
|
||||
pnpm test # Vitest 测试
|
||||
```
|
||||
|
||||
- 必须同时通过 lint、type-check 与格式检查才能提交
|
||||
- Husky + lint-staged 已配置 `pre-commit` 钩子:对暂存文件自动跑 ESLint --fix + Prettier,再整体 type-check;不通过则阻止提交(临时跳过用 `git commit --no-verify`)
|
||||
- 注:本仓库 git 根在父目录(monorepo),`prepare` 脚本以 `cd .. && husky frontend/.husky` 安装钩子,`core.hooksPath` 指向 `frontend/.husky/_`
|
||||
|
||||
## 绝对禁止事项
|
||||
|
||||
- 直接修改 `package.json` 或 `pnpm-lock.yaml`(依赖必须用 `pnpm add/remove`;scripts 字段可用 `pnpm pkg set`,注意带冒号的键需用方括号语法 `scripts["name"]`)
|
||||
- 在客户端代码中硬编码敏感信息 (密钥、Token等)
|
||||
- 使用 `any` 类型 (除非经过团队评审)
|
||||
- 忽略 TypeScript 或 ESLint 错误强制提交
|
||||
- 在 Server Component 中直接使用 `useState`、`useEffect` 等客户端 Hooks
|
||||
- 使用 CSS-in-JS 方案 (统一使用 Tailwind CSS)
|
||||
- 在 Client Component 中直接调用数据库
|
||||
- 在 useEffect 中做页面初始数据请求
|
||||
- 创建 Class 组件
|
||||
@@ -0,0 +1,184 @@
|
||||
# design — 视觉原型(North Star)
|
||||
|
||||
本目录是魂镜前端的设计原型集合,**非生产代码**。入口:[`index.html`](./index.html)(可点击目录,每张卡带 **live iframe 缩略图**,`pnpm design` 后访问)。每个 `*-preview.html` 是视觉参考稿,共享三层样式(`tokens` / `_base` / `_chrome`)与统一侧栏(`_nav.html` 经 `_theme.js` 注入),页面 `<style>` 仅保留页面特异样式。实现时按其重写为 `src/` 下的 React 组件。
|
||||
|
||||
## 共享层
|
||||
|
||||
原型引用**三层共享文件** + **统一侧栏片段** + **共享脚本**,消除此前每个原型各存一份 token / reset / sidebar 副本(且已漂移、写法分叉)的问题。改一处,全站 25 页同步。
|
||||
|
||||
| 文件 | 职责 |
|
||||
| --- | --- |
|
||||
| [`tokens.css`](./tokens.css) | 铜镜鉴心 暗+亮 全套 CSS 变量(`--ink` / `--patina` / `--gilt` / `--cinnabar` …) |
|
||||
| [`_base.css`](./_base.css) | reset / `body` / 极光铜晕 `body::before` / `.serif` / `.mono` / `prefers-reduced-motion` |
|
||||
| [`_chrome.css`](./_chrome.css) | AppShell 组件层:`.sidebar` / `.nav-item` / `.card` / `.btn` / `.tag` / `.page-header` / `.back-link` |
|
||||
| [`_nav.html`](./_nav.html) | 统一 7 组侧栏片段(20 项),由 `_theme.js` fetch 注入各页 `.sidebar-mount` |
|
||||
| [`_theme.js`](./_theme.js) | nav 注入 + 当前页高亮 + 主题切换 + `localStorage` 持久化 + logo 回首页 |
|
||||
|
||||
加载顺序:`tokens.css` → `_base.css` → `_chrome.css` → 页面 `<style>`。**页面 `<style>` 只写页面特异样式**,不得重复定义 `_chrome.css` 已覆盖的 `.sidebar` / `.nav-item` / `body` / `:root`——否则后加载覆盖共享层,改一处不再全站生效。
|
||||
|
||||
**新建 AppShell 原型时**:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>/* 仅页面特异样式 */</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<div class="sidebar-mount" data-active="当前页名"></div>
|
||||
<div class="main-area"><!-- page-header + page-body --></div>
|
||||
</div>
|
||||
<script src="_theme.js" defer></script>
|
||||
</body>
|
||||
```
|
||||
|
||||
`data-active` 对齐 `_nav.html` 的 `data-nav`(如 `对话` / `灵枢` / `魂册降生`)。**加导航项 / 改链接**:只改 `_nav.html` 一处。**改 sidebar 视觉**:只改 `_chrome.css` 一处。
|
||||
|
||||
> **与生产侧的差异(有意)**:`tokens.css` 的 `--surface` 是半透明玻璃(`rgba(255,255,255,0.022)`,配极光底做镜面通透感);`src/styles/globals.css` 演进为实色面板(`#121817`,渲染可预测)。两层各持一套,勿强行统一。
|
||||
|
||||
## 布局体系
|
||||
|
||||
原型按页面层级用 4 种 shell:
|
||||
|
||||
| Shell | 用途 | 结构 | 代表页 |
|
||||
| --- | --- | --- | --- |
|
||||
| **AppShell** | 功能页(主导航可达) | `.sidebar-mount`(注入)+ `main-area`(page-header + page-body) | cosmos / wuxing / characters / chat / personality / drive / evolution / world / time / memory / archive / tools / model-gateway / settings / admin / card-import / lorebook / rune-engine / margin-notes … |
|
||||
| **NarrowShell** | 二级详情页(从人物列表进入) | `app-main` max-width 居中 + `back-link` 面包屑;**仍注入侧栏**(高亮上级项 `人物`) | character-detail / character-materials |
|
||||
| **StageShell** | 登录 / 落地 / 目录 | `stage` max-width 居中,无 sidebar(或独立 theme 按钮) | copper-mirror-v2 / login / **index** |
|
||||
| **AdminShell** | 司命台控制台 | dashboard 网格(也注入 `.sidebar-mount`) | admin / admin-characters |
|
||||
|
||||
> **NarrowShell 与侧栏**:二级详情页(人物详情 / 资料)保留侧栏注入并高亮 `人物`,便于在详情态随时切走;`back-link` 面包屑负责"回到列表"的层级回退。两者职责不同,并不冲突。
|
||||
>
|
||||
> **归类修正(2026-07-25)**:`card-import` / `lorebook` 原标 NarrowShell,实际带 sidebar(属一级功能页),归 **AppShell**——主区宽度与 shell 类型解耦(窄流程页用 `.app-main` 限宽,但仍有全局导航)。NarrowShell 仅用于「从人物列表进入的二级详情」。
|
||||
>
|
||||
> **分组重组(2026-07-27)**:原「载入」组重命名为「**编排**」——该组横跨数据载入(魂册降生 / 世谱)与运行时编排(符文引擎 / 眉批),「编排」涵盖两者。心智组内分层:灵枢(总览)↔ 维度(五行 / 人格 / 内驱力 / 演化),以 `.nav-divider` 发丝线分隔。每项 nav 加 `data-route` 数据属性(供调试 / a11y 辅助,侧栏不渲染——路由映射见目录页 `.proto-route` 与原型索引表)。
|
||||
|
||||
**chat** 同样接入 `.sidebar-mount`(与其他 AppShell 页一致)。其此前的 60px 可折叠 sidebar 是未完成态(折叠隐藏文字但 HTML 无图标替代 → 空白窄条,且无触发器无法展开),已统一为标准 220px 展开侧栏;对话区宽度由 `.main-area` flex 自适应。
|
||||
|
||||
**统一 sidebar 导航(7 组 20 项)**:见 [`_nav.html`](./_nav.html)。灵台 · 魂册 · 心智 · **编排·酒馆融合** · 内景 · 藏识 · 百工。所有 AppShell 页(除 chat)由 `_theme.js` 自动注入同一份;底部另有「设计系统」入口直达 North Star 基准页。**logo 单击即时回目录**,350ms 内连点 5 次触发「镜启」彩蛋。
|
||||
|
||||
## 本地预览
|
||||
|
||||
侧栏经 `_theme.js` 用 `fetch` 注入 `_nav.html`,目录页缩略图用 live `<iframe>`;`file://` 双击打开时两者均受 CORS 限制(侧栏显示 fallback 提示、缩略图空白)。须启动本地服务器:
|
||||
|
||||
```bash
|
||||
pnpm design # → http://localhost:3001(零依赖,scripts/serve-design.mjs)
|
||||
```
|
||||
|
||||
## 原型索引
|
||||
|
||||
**入口**:[`index.html`](./index.html)(可点击的目录页,每张卡带 live iframe 缩略图)。
|
||||
|
||||
> 状态:✅ 已落地(有对应路由) · 🟡 部分/待确认 · ⬜ 待实现 · 🆕 新增
|
||||
|
||||
### 设计系统 / 北极星
|
||||
|
||||
| 文件 | 功能 | 路由 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `copper-mirror-redesign-v2` | 铜镜鉴心 North Star(色谱 / 字阶 / 组件 / 九枢星盘) | — | 🎯 设计基准 |
|
||||
| `copper-mirror-login` | 登入灵台 | `/login` | ✅ |
|
||||
|
||||
### 灵台 · 对话
|
||||
|
||||
| 文件 | 功能 | 路由 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `chat-redesign-preview` | 对话页 v6 | `/chat` | ✅ |
|
||||
| `run-trace-preview` | 运行轨迹 | `/runs/[id]` | ✅ |
|
||||
|
||||
### 魂册
|
||||
|
||||
| 文件 | 功能 | 路由 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `characters-preview` | 人物列表 | `/characters` | ✅ |
|
||||
| `character-detail-preview` | 人物详情 | `/characters/[id]` | 🟡 |
|
||||
| `character-materials-preview` | 人物资料与沉淀 | `/characters/[id]/*` | 🟡 |
|
||||
|
||||
### 心智
|
||||
|
||||
| 文件 | 功能 | 路由 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `cosmos-preview` | 灵枢(九枢系统视图) | `/cosmos` | ✅ |
|
||||
| `wuxing-preview` | 五行 | `/wuxing` | ✅ |
|
||||
| `personality-preview` | 人格 | `/personality` | ✅ |
|
||||
| `drive-preview` | 内驱力 | `/drive` | ✅ |
|
||||
| `evolution-preview` | 演化 | `/evolution` | ✅ |
|
||||
|
||||
### 编排 · 酒馆融合(P0/P1,路由已定)
|
||||
|
||||
| 文件 | 功能 | 路由 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `card-import-preview` | 魂册降生 · 角色卡导入导出 | `/characters/import` | ⬜ P0 |
|
||||
| `lorebook-preview` | 世谱 · World Info | `/lorebooks` | ⬜ P0 |
|
||||
| `rune-engine-preview` | 符文引擎 · Macros | `/macros` | 🆕 P0 |
|
||||
| `margin-notes-preview` | 眉批 · Author's Note | `/author-notes` | 🆕 P1 |
|
||||
|
||||
### 内景
|
||||
|
||||
| 文件 | 功能 | 路由 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `world-preview` | 世界(河图绘形) | `/world` | ✅ |
|
||||
| `time-preview` | 时态 | `/time` | ✅ |
|
||||
|
||||
### 藏识
|
||||
|
||||
| 文件 | 功能 | 路由 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `memory-preview` | 记忆 | `/memory` | ✅ |
|
||||
| `archive-preview` | 归档(记忆时间河) | `/archive` | ✅ |
|
||||
|
||||
### 百工 · 系统
|
||||
|
||||
| 文件 | 功能 | 路由 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `tools-preview` | 工具 | `/tools` | ✅ |
|
||||
| `model-gateway-preview` | 模型网关 | `/models` | ✅ |
|
||||
| `settings-preview` | 设置 | `/settings` | ✅ |
|
||||
|
||||
### 司命台 · admin
|
||||
|
||||
| 文件 | 功能 | 路由 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| `admin-preview` | 司命台融合稿 | `/admin` | ✅ |
|
||||
| `admin-characters-preview` | 人物模板管理 | `/admin/*` | 🟡 |
|
||||
|
||||
## 酒馆(SillyTavern)融合映射
|
||||
|
||||
酒馆功能**已融入魂镜中式命名**,不单设 `tavern/` 子目录——相关原型就是魂镜的功能页。下表把酒馆概念串到魂镜名、对应原型、后端表与设计文档章节。权威设计见 [`docs/contracts/contract-glossary.md`](../../docs/contracts/contract-glossary.md)(命名已于 2026-07-24 拍板,登记契约字典 §2.1)。
|
||||
|
||||
| 酒馆概念 | 魂镜名 | design 原型 | 后端表 | 文档 § | 阶段 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Character Card | **魂册** | `card-import` · `character-materials` · `personality` | `character` | §8 | **P0** |
|
||||
| World Info / Lorebook | **世谱** | `lorebook` · `world` | `lorebooks` | §7 | **P0** |
|
||||
| Macros `{{char}}` | **符文引擎** | `rune-engine` | `macros` | §8.4 | **P0** |
|
||||
| Author's Note | **眉批** | `margin-notes` | `author_notes` | §9.1 | P1 |
|
||||
| Prompt Manager | 言灵编排 | ⛔ 无 | — | §9.2 | — |
|
||||
| User Personas | 本相 | ⛔ 无 | — | §9.3 | — |
|
||||
| Presets | 火候 | ⛔ 无 | — | §9.4 | — |
|
||||
| Instruct Mode | 科仪 | ⛔ 无 | — | §9.5 | — |
|
||||
| Regex Scripts | 刻符 | ⛔ 无 | — | §9.6 | — |
|
||||
| Quick Reply | 锦囊 | ⛔ 无 | — | §9.7 | — |
|
||||
| Slash Commands | 咒令 | ⛔ 无 | — | §9.8 | — |
|
||||
| Group Chat | 雅集 | ⛔ 无 | — | §9.9 | — |
|
||||
|
||||
**已建原型**:`符文引擎`(rune-engine,P0)、`眉批`(margin-notes,P1)。其余酒馆载体(言灵编排 / 本相 / 火候 / …)尚无原型——若需要,可建 `design/tavern/` 子目录放置并在上表登记。
|
||||
|
||||
## 设计决策摘要
|
||||
|
||||
- **铜镜鉴心语言**:铜绿锈(主色·在场/健康/记忆)、鎏金(结构/发丝线)、朱漆(**仅**司命/朱批/告警)、墨/夜铜(暗底)。暗色优先,亮色一等公民。
|
||||
- **字阶**:宋体(Songti SC)显示字 + 系统体正文 + JetBrains Mono 数据/标签。
|
||||
- **签名元素**:九枢星盘是"活的仪器"(数据可视化),非装饰。
|
||||
- **a11y**:亮色 AA 对比度修正、`prefers-reduced-motion`、`focus-visible`、SVG `aria-label`。
|
||||
- **转 React**:原型共享层(`tokens` / `_base` / `_chrome` / `nav` / `_theme`)仅服务于设计稿;实现时按 section 重写为 React 组件,样式走 `globals.css` 的 `@theme` 映射类(见 `frontend/CLAUDE.md` 的 CSS 变量三层规则)。页面特异样式随组件迁移。
|
||||
|
||||
## 已废弃
|
||||
|
||||
- `copper-mirror-redesign.html`(v1,双面镜探索方向)——已被 v2 取代并删除,`git show HEAD:frontend/design/copper-mirror-redesign.html` 可取回。
|
||||
- `admin-preview.html.bak`——已删除。
|
||||
|
||||
## 维护脚本(`frontend/scripts/`)
|
||||
|
||||
- [`migrate-design.mjs`](../scripts/migrate-design.mjs) — 把原型接入共享层(加 `_chrome.css` link、替换 sidebar 为 mount、加 `_theme.js`)。一次性迁移已完成;新原型按上文模板手写即可。
|
||||
- [`clean-design-css.mjs`](../scripts/clean-design-css.mjs) — 清理各页与 `_base.css` / `_chrome.css` 重复的 body / reset / sidebar CSS(幂等,可重复跑)。
|
||||
- [`serve-design.mjs`](../scripts/serve-design.mjs) — 零依赖预览服务器,`pnpm design` 调用。
|
||||
@@ -0,0 +1,95 @@
|
||||
/* ============================================================
|
||||
* design/_base.css — 原型共享基底
|
||||
* ============================================================
|
||||
*
|
||||
* 用途:供 design/*.html 原型 <link> 引用。集中此前在每个原型
|
||||
* <style> 里字面重复的 reset / body / 极光铜晕 / 字体类 /
|
||||
* reduced-motion,消除 24 份副本。
|
||||
*
|
||||
* 依赖:必须在本文件之前 <link href="tokens.css"> —— 下方的
|
||||
* body 背景与极光底色均引用 token 变量。
|
||||
*
|
||||
* 字体策略:与生产 src/styles/globals.css 的 --font-sans 对齐,
|
||||
* 优先系统字体,避免 CJK 网页字体(15MB+)。
|
||||
* .serif 用宋族做显示字,.mono 用 JetBrains Mono 做数据/标签。
|
||||
* ============================================================ */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--ink);
|
||||
color: var(--fg);
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei",
|
||||
"Helvetica Neue", system-ui, sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 极光铜晕:铜绿主导,把暗底拉向 verdigris-on-bronze。
|
||||
* 各原型此前字面一致,固定为设计语言的氛围底色。 */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: -10vmax;
|
||||
z-index: -1;
|
||||
background:
|
||||
radial-gradient(
|
||||
54vmax 54vmax at 12% 4%,
|
||||
rgba(111, 163, 137, 0.2),
|
||||
transparent 60%
|
||||
),
|
||||
radial-gradient(
|
||||
40vmax 40vmax at 92% 8%,
|
||||
rgba(201, 162, 74, 0.1),
|
||||
transparent 60%
|
||||
),
|
||||
radial-gradient(
|
||||
60vmax 60vmax at 50% 112%,
|
||||
rgba(184, 57, 46, 0.06),
|
||||
transparent 60%
|
||||
);
|
||||
filter: blur(28px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.serif {
|
||||
font-family:
|
||||
"Songti SC", "STSong", "Source Han Serif SC", "Noto Serif SC", serif;
|
||||
}
|
||||
.mono {
|
||||
font-family: "JetBrains Mono", "SF Mono", ui-monospace, monospace;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* iframe 缩略图态:被 index.html 的 live <iframe> 嵌入时(_theme.js 加 .in-frame),
|
||||
动画瞬间完成到末态、过渡归零——避免 25 个缩略图同时持续重绘耗 CPU。
|
||||
用 duration 归零而非 play-state:paused:确保入场动画(如 opacity 0→1)跑到
|
||||
末帧可见态,不会停在首帧导致元素消失。JS(rAF/setTimeout)驱动的动画
|
||||
(仅 chat)开销可控,不在此处理。 */
|
||||
.in-frame *,
|
||||
.in-frame *::before,
|
||||
.in-frame *::after {
|
||||
animation-duration: 0.001s !important;
|
||||
animation-delay: 0s !important;
|
||||
transition-duration: 0.001s !important;
|
||||
}
|
||||
@@ -0,0 +1,800 @@
|
||||
/* ============================================================
|
||||
* design/_chrome.css — 原型共享组件层(AppShell chrome)
|
||||
* ============================================================
|
||||
*
|
||||
* 用途:供所有 AppShell / NarrowShell 原型 <link> 引用。集中此前
|
||||
* 每个原型 <style> 里字面重复的 sidebar / 导航 / 卡片 / 按钮 /
|
||||
* 标签 / 页头 等组件样式,消除 25 份副本与已出现的写法分叉
|
||||
* (如角标写法分叉:部分页用 class,部分页内联)。
|
||||
*
|
||||
* 依赖:<link href="tokens.css"> 与 <link href="_base.css"> 必须在本
|
||||
* 文件之前——组件样式引用 token 变量,_base.css 提供 reset/body。
|
||||
*
|
||||
* 层次:tokens.css(变量)→ _base.css(reset/body/极光)→ _chrome.css
|
||||
* (共享组件)→ 页面 <style>(仅页面特异样式)。
|
||||
*
|
||||
* sidebar 注入:侧栏 DOM 由 _theme.js 从 _nav.html fetch 注入
|
||||
* 到 .sidebar-mount;本文件提供 .sidebar-* 样式。
|
||||
* ============================================================ */
|
||||
|
||||
/* ── AppShell 布局骨架 ── */
|
||||
.app-shell {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.sidebar-mount {
|
||||
display: flex;
|
||||
} /* 让注入的 .sidebar-wrap 拉伸填满本列全高 */
|
||||
.main-area {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ── 侧栏(可折叠:默认 220 展开 / .collapsed 60 图标态)──
|
||||
DOM 由 _theme.js 从 _nav.html 注入;align-self + sticky 让长页面下贴顶滚动。
|
||||
折叠由 #sidebar-fold 触发,_theme.js 持久化到 localStorage。
|
||||
背景在 .sidebar-wrap 上(填满 flex 列高)而非 .sidebar(仅 100vh sticky)。
|
||||
border-right 同层,确保全高发丝线不断。 */
|
||||
.sidebar-wrap {
|
||||
display: flex;
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
z-index: 30;
|
||||
transition:
|
||||
width 0.28s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.28s;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--night-bronze) 92%, var(--patina) 4%),
|
||||
var(--night-bronze)
|
||||
);
|
||||
border-right: 1px solid var(--rule);
|
||||
}
|
||||
.sidebar-wrap.collapsed {
|
||||
width: 60px;
|
||||
box-shadow: 8px 0 36px -10px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px 0 8px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* 顶栏:logo + 折叠按钮 */
|
||||
.sidebar-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
.sidebar-wrap.collapsed .sidebar-topbar {
|
||||
justify-content: center;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
.sidebar .logo {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--fg);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 4px;
|
||||
}
|
||||
.sidebar .logo:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
.sidebar .logo .logo-mark {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar .logo .logo-mark .knob {
|
||||
fill: var(--gilt);
|
||||
}
|
||||
|
||||
.sidebar-wrap.collapsed .sidebar .logo {
|
||||
font-size: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-wrap.collapsed .sidebar .logo .logo-mark {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
/* logo 彩蛋:tick(单击敲门)+ egg-pulse(1.2s 连点 5 次,金光 + 谶语) */
|
||||
@keyframes logoTick {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.07);
|
||||
}
|
||||
}
|
||||
.logo.tick .logo-mark {
|
||||
animation: logoTick 0.3s ease;
|
||||
}
|
||||
@keyframes logoEggPulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
filter: drop-shadow(0 0 0 transparent);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1.2);
|
||||
filter: drop-shadow(0 0 7px var(--gilt));
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
filter: drop-shadow(0 0 0 transparent);
|
||||
}
|
||||
}
|
||||
.logo.egg-pulse .logo-mark {
|
||||
animation: logoEggPulse 1.4s ease;
|
||||
color: var(--gilt);
|
||||
}
|
||||
|
||||
/* 折叠按钮(折叠态旋转 180,箭头指向展开) */
|
||||
.sidebar-foldbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
color: var(--muted);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition:
|
||||
background 0.12s,
|
||||
color 0.12s,
|
||||
transform 0.28s;
|
||||
}
|
||||
.sidebar-foldbtn:hover {
|
||||
background: var(--overlay-hover);
|
||||
color: var(--fg);
|
||||
}
|
||||
.sidebar-wrap.collapsed .sidebar-foldbtn {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* 图标默认(Lucide 风:stroke currentColor、fill none) */
|
||||
.sidebar .ico {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── 导航组与条目 ── */
|
||||
.nav-group {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.nav-group:first-of-type {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.nav-group-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
padding: 0 18px;
|
||||
margin-bottom: 4px;
|
||||
opacity: 0.7;
|
||||
max-height: 22px;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
opacity 0.18s,
|
||||
max-height 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
/* 载入组:新功能视觉区分(顶边鎏金淡线) */
|
||||
.nav-group.tavern {
|
||||
border-top: 1px solid var(--gilt-faint);
|
||||
padding-top: 14px;
|
||||
}
|
||||
.sidebar-wrap.collapsed .nav-group-title {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 7px 18px;
|
||||
border-radius: 10px;
|
||||
font-size: 13.5px;
|
||||
font-weight: 500;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background var(--duration-fast),
|
||||
color var(--duration-fast),
|
||||
padding var(--duration-fast),
|
||||
box-shadow var(--duration-fast);
|
||||
cursor: pointer;
|
||||
gap: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.nav-item:hover {
|
||||
background: var(--overlay-hover);
|
||||
color: var(--fg);
|
||||
}
|
||||
.nav-item.active {
|
||||
background: var(--patina-wash);
|
||||
color: var(--patina);
|
||||
box-shadow: inset 2px 0 0 var(--patina);
|
||||
}
|
||||
.nav-item .ico {
|
||||
opacity: 0.65;
|
||||
}
|
||||
.nav-item.active .ico {
|
||||
opacity: 1;
|
||||
}
|
||||
/* 司命台 · 朱漆语义(设计语言:朱漆仅司命/朱批/告警) */
|
||||
.nav-item.siming {
|
||||
color: var(--cinnabar);
|
||||
}
|
||||
.nav-item.siming:hover {
|
||||
background: var(--cinnabar-wash);
|
||||
color: var(--cinnabar);
|
||||
}
|
||||
.nav-item.siming.active {
|
||||
background: var(--cinnabar-wash);
|
||||
color: var(--cinnabar);
|
||||
}
|
||||
|
||||
/* ── 心智分层:灵枢(总览)↔ 维度(五行/人格/内驱力/演化)之间的分隔线
|
||||
.nav-item--overview 暂为语义标记(无额外样式),视觉分层仅靠 .nav-divider
|
||||
一条发丝线,不靠底色/字重——克制,避免总览项与 active 态混淆。 ── */
|
||||
.nav-divider {
|
||||
height: 1px;
|
||||
margin: 6px 18px;
|
||||
background: var(--rule);
|
||||
opacity: 0.8;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-wrap.collapsed .nav-item {
|
||||
padding: 9px 0;
|
||||
font-size: 0;
|
||||
gap: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-wrap.collapsed .nav-item .ico {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sidebar .spacer {
|
||||
flex: 1;
|
||||
min-height: 8px;
|
||||
}
|
||||
|
||||
/* ── 底栏:用户卡 + foot-btn + 主题 ── */
|
||||
.sidebar .user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
margin-top: 8px;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
.sidebar .user-card .avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--patina);
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar .user-card .info {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.sidebar .user-card .name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--fg);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sidebar .user-card .role {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.sidebar-wrap.collapsed .user-card {
|
||||
padding: 10px 0;
|
||||
gap: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-wrap.collapsed .user-card .info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.foot-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 7px 18px;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-family: inherit;
|
||||
transition:
|
||||
background 0.12s,
|
||||
color 0.12s,
|
||||
padding 0.2s;
|
||||
}
|
||||
.foot-btn:hover {
|
||||
background: var(--overlay-hover);
|
||||
color: var(--fg);
|
||||
}
|
||||
.sidebar-wrap.collapsed .foot-btn {
|
||||
padding: 7px 0;
|
||||
font-size: 0;
|
||||
gap: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-item.nav-theme {
|
||||
margin: 0 8px;
|
||||
}
|
||||
.sidebar-wrap.collapsed .nav-item.nav-theme {
|
||||
margin: 0;
|
||||
padding: 9px 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ── 页头与标题 ──
|
||||
通用 .page-title/.page-sub 是默认(NarrowShell 22px);.page-header .page-title
|
||||
特异性更高、覆盖为 AppShell 19px。无 .page-head 容器的页直接用通用默认。 */
|
||||
.page-title {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--fg);
|
||||
margin: 0;
|
||||
}
|
||||
.page-sub {
|
||||
font-size: 13px;
|
||||
color: var(--fg-dim);
|
||||
margin-top: 3px;
|
||||
}
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 28px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
background: var(--surface);
|
||||
backdrop-filter: blur(8px);
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.page-header .page-title {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--fg);
|
||||
}
|
||||
.page-header .page-sub {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 12px;
|
||||
color: var(--fg-faint);
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.page-header .spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ── 页体 ── */
|
||||
.page-body {
|
||||
flex: 1;
|
||||
padding: 24px 28px 56px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 22px;
|
||||
max-width: 1180px;
|
||||
width: 100%;
|
||||
}
|
||||
.section-label {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--fg-faint);
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.section-label .glyph {
|
||||
color: var(--gilt);
|
||||
opacity: 0.85;
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
.section-label .sub {
|
||||
color: var(--fg-dim);
|
||||
opacity: 0.7;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.section-label .count {
|
||||
font-size: 11px;
|
||||
color: var(--fg-dim);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ── NarrowShell 主区(窄流程页:card-import / lorebook / character-*)
|
||||
AppShell 壳 + 窄主区;.app-main 居中限宽,配 .back-link 面包屑。 ── */
|
||||
.app-main {
|
||||
flex: 1;
|
||||
padding: 24px 28px 56px;
|
||||
max-width: 920px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.app-main.narrow {
|
||||
max-width: 760px;
|
||||
} /* 更窄:导入流程等 */
|
||||
.page-head {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.page-head .page-title {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--fg);
|
||||
margin: 0;
|
||||
}
|
||||
.page-head .page-sub {
|
||||
font-size: 13px;
|
||||
color: var(--fg-dim);
|
||||
margin-top: 3px;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-bottom: 14px;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.back-link:hover {
|
||||
color: var(--patina);
|
||||
}
|
||||
|
||||
/* ── 卡片 ── */
|
||||
.card {
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 14px;
|
||||
background: var(--surface);
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 20px 0;
|
||||
}
|
||||
.card-title {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--fg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
.card-title .glyph {
|
||||
color: var(--patina);
|
||||
opacity: 0.8;
|
||||
}
|
||||
.card-body {
|
||||
padding: 16px 20px 20px;
|
||||
}
|
||||
|
||||
/* ── 按钮 ── */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 7px 16px;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 0.12s,
|
||||
border-color 0.12s,
|
||||
color 0.12s;
|
||||
border: 1px solid transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--patina);
|
||||
color: var(--ink);
|
||||
border-color: var(--patina-dim);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: var(--patina-bright);
|
||||
}
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: var(--fg-dim);
|
||||
border-color: var(--rule);
|
||||
}
|
||||
.btn-outline:hover {
|
||||
background: var(--surface-hi);
|
||||
color: var(--fg);
|
||||
}
|
||||
.btn-sm {
|
||||
padding: 4px 11px;
|
||||
font-size: 11px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.btn-ghost:hover {
|
||||
color: var(--fg);
|
||||
background: var(--overlay-hover);
|
||||
}
|
||||
.btn-danger {
|
||||
background: transparent;
|
||||
color: var(--cinnabar);
|
||||
border-color: var(--cinnabar-dim);
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background: var(--cinnabar-wash);
|
||||
color: var(--cinnabar);
|
||||
}
|
||||
.btn-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--duration-fast),
|
||||
color var(--duration-fast);
|
||||
}
|
||||
.btn-icon:hover {
|
||||
background: var(--overlay-hover);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
/* ── 表单元素(统一 input / textarea / select / switch)── */
|
||||
.input,
|
||||
.textarea,
|
||||
.select {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 10px;
|
||||
background: var(--glass);
|
||||
color: var(--fg);
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
transition: border-color var(--duration-fast);
|
||||
}
|
||||
.input:focus,
|
||||
.textarea:focus,
|
||||
.select:focus {
|
||||
outline: none;
|
||||
border-color: var(--patina-dim);
|
||||
}
|
||||
.textarea {
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
width: 32px;
|
||||
height: 18px;
|
||||
border-radius: 999px;
|
||||
background: var(--rule);
|
||||
cursor: pointer;
|
||||
transition: background var(--duration-fast);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.switch::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: var(--fg-dim);
|
||||
transition:
|
||||
transform var(--duration-fast),
|
||||
background var(--duration-fast);
|
||||
}
|
||||
.switch.on {
|
||||
background: var(--patina-dim);
|
||||
}
|
||||
.switch.on::after {
|
||||
transform: translateX(14px);
|
||||
background: var(--patina);
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: block;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* ── 标签(通用胶囊)── */
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 9px;
|
||||
border-radius: 999px;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 10.5px;
|
||||
border: 1px solid;
|
||||
}
|
||||
.tag-gilt {
|
||||
border-color: var(--gilt-dim);
|
||||
color: var(--gilt);
|
||||
background: var(--gilt-faint);
|
||||
}
|
||||
.tag-patina {
|
||||
border-color: var(--patina-dim);
|
||||
color: var(--patina);
|
||||
background: var(--patina-wash);
|
||||
}
|
||||
|
||||
/* ── 状态药丸 ── */
|
||||
.stat-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 11.5px;
|
||||
color: var(--patina);
|
||||
padding: 4px 12px;
|
||||
border: 1px solid var(--patina-dim);
|
||||
border-radius: 999px;
|
||||
background: var(--patina-wash);
|
||||
}
|
||||
|
||||
/* ── 滚动条 ── */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--rule);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* ── 响应式:中屏折叠侧栏为图标态,窄屏隐藏 ── */
|
||||
@media (max-width: 1200px) and (min-width: 901px) {
|
||||
.sidebar-wrap {
|
||||
width: 60px;
|
||||
}
|
||||
.sidebar-wrap .nav-group-title {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.sidebar-wrap .nav-item {
|
||||
padding: 9px 0;
|
||||
font-size: 0;
|
||||
gap: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-wrap .nav-item .ico {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
opacity: 1;
|
||||
}
|
||||
.sidebar-wrap .sidebar-topbar {
|
||||
justify-content: center;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
.sidebar-wrap .sidebar .logo {
|
||||
font-size: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-wrap .sidebar .logo .logo-mark {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.sidebar-wrap .user-card {
|
||||
padding: 10px 0;
|
||||
gap: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-wrap .user-card .info {
|
||||
display: none;
|
||||
}
|
||||
.sidebar-wrap .foot-btn {
|
||||
padding: 7px 0;
|
||||
font-size: 0;
|
||||
gap: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-wrap .nav-item.nav-theme {
|
||||
margin: 0;
|
||||
padding: 9px 0;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.sidebar-wrap {
|
||||
display: none;
|
||||
}
|
||||
.page-body {
|
||||
padding: 18px 16px 40px;
|
||||
}
|
||||
.app-main {
|
||||
padding: 18px 16px 40px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<!-- ============================================================
|
||||
design/_nav.html — 统一侧栏片段(全功能版)
|
||||
============================================================
|
||||
由 _theme.js fetch 注入各页 .sidebar-mount。改这里全站同步。
|
||||
|
||||
能力:
|
||||
· 7 组 20 项导航,每项带 Lucide 图标(折叠态只显图标)
|
||||
· 每项带 data-route(hover 显示路由角标,_chrome.css 渲染)
|
||||
· logo 鎏金 mark + 折叠按钮(#sidebar-fold 切换 .collapsed)
|
||||
· 心智组分层:灵枢(总览)↔ 维度(五行 / 人格 / 内驱力 / 演化)
|
||||
· user-card 底部用户信息 + foot-btn 设计系统入口
|
||||
· theme-toggle 按钮(#theme-toggle)
|
||||
|
||||
分组语义(2026-07-27 重组):
|
||||
· 「编排」原「载入」——该组横跨数据载入(魂册降生 / 世谱)
|
||||
与运行时编排(符文引擎 / 眉批),「编排」涵盖两者。
|
||||
· tavern class 名保留(内部标识,_chrome.css 据此加视觉区分)。
|
||||
|
||||
折叠:.sidebar-wrap.collapsed → 60px 图标态(_theme.js 控制 + 持久化)
|
||||
高亮:data-active 命中 data-nav → .active
|
||||
图标 stroke/fill 由 _chrome.css 的 .ico 统一
|
||||
============================================================ -->
|
||||
<div class="sidebar-wrap">
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-topbar">
|
||||
<button class="logo" type="button" data-nav="home" title="返回目录 · 五连点见镜启">
|
||||
<svg class="logo-mark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="8.5" /><circle cx="12" cy="12" r="4.5" opacity="0.45" /><circle class="knob" cx="12" cy="12" r="1.6" /></svg>
|
||||
魂鏡
|
||||
</button>
|
||||
<button class="sidebar-foldbtn" id="sidebar-fold" type="button" aria-label="折叠侧栏">
|
||||
<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">灵台</div>
|
||||
<a class="nav-item" data-nav="对话" data-route="/chat" href="chat-redesign-preview.html" title="对话"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg>对话</a>
|
||||
<a class="nav-item" data-nav="轨迹" data-route="/runs/[id]" href="run-trace-preview.html" title="运行轨迹"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12" /></svg>轨迹</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">魂册</div>
|
||||
<a class="nav-item" data-nav="人物" data-route="/characters" href="characters-preview.html" title="人物列表"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /></svg>人物</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">心智</div>
|
||||
<a class="nav-item nav-item--overview" data-nav="灵枢" data-route="/cosmos" href="cosmos-preview.html" title="灵枢 · 心智总览"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" /></svg>灵枢</a>
|
||||
<div class="nav-divider" role="separator" aria-label="心智维度"></div>
|
||||
<a class="nav-item" data-nav="五行" data-route="/wuxing" href="wuxing-preview.html" title="五行生克"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.07-2.14-.22-4.05 2-6 .5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.15.43-2.29 1-3a2.5 2.5 0 0 0 2.5 2.5z" /></svg>五行</a>
|
||||
<a class="nav-item" data-nav="人格" data-route="/personality" href="personality-preview.html" title="人格定义"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" y1="9" x2="9.01" y2="9" /><line x1="15" y1="9" x2="15.01" y2="9" /></svg>人格</a>
|
||||
<a class="nav-item" data-nav="内驱力" data-route="/drive" href="drive-preview.html" title="内驱力"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" /><circle cx="12" cy="12" r="6" /><circle cx="12" cy="12" r="2" /></svg>内驱力</a>
|
||||
<a class="nav-item" data-nav="演化" data-route="/evolution" href="evolution-preview.html" title="大周天演化"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><polyline points="22 7 13.5 15.5 8.5 10.5 2 17" /><polyline points="16 7 22 7 22 13" /></svg>演化</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group tavern">
|
||||
<div class="nav-group-title">编排</div>
|
||||
<a class="nav-item" data-nav="魂册降生" data-route="/characters/import" href="card-import-preview.html" title="魂册降生 · 角色卡导入"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><line x1="19" y1="8" x2="19" y2="14" /><line x1="22" y1="11" x2="16" y2="11" /></svg>魂册降生</a>
|
||||
<a class="nav-item" data-nav="世谱" data-route="/lorebooks" href="lorebook-preview.html" title="世谱 · 世界设定"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" /></svg>世谱</a>
|
||||
<a class="nav-item" data-nav="符文引擎" data-route="/macros" href="rune-engine-preview.html" title="符文引擎 · 占位符显化"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3l1.9 5.8L20 11l-6.1 2.2L12 19l-1.9-5.8L4 11l6.1-2.2z" /></svg>符文引擎</a>
|
||||
<a class="nav-item" data-nav="眉批" data-route="/author-notes" href="margin-notes-preview.html" title="眉批 · 深度注入"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20h9" /><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4z" /></svg>眉批</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">内景</div>
|
||||
<a class="nav-item" data-nav="世界" data-route="/world" href="world-preview.html" title="世界 · 河图绘形"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" /><line x1="2" y1="12" x2="22" y2="12" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg>世界</a>
|
||||
<a class="nav-item" data-nav="时态" data-route="/time" href="time-preview.html" title="时态 · 时间线"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>时态</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">藏识</div>
|
||||
<a class="nav-item" data-nav="记忆" data-route="/memory" href="memory-preview.html" title="记忆 · 息壤"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M3 5v14a9 3 0 0 0 18 0V5" /><path d="M3 12a9 3 0 0 0 18 0" /></svg>记忆</a>
|
||||
<a class="nav-item" data-nav="归档" data-route="/archive" href="archive-preview.html" title="归档 · 记忆时间河"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><rect x="2" y="4" width="20" height="5" rx="1" /><path d="M4 9v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9" /><line x1="10" y1="13" x2="14" y2="13" /></svg>归档</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">百工</div>
|
||||
<a class="nav-item" data-nav="工具" data-route="/tools" href="tools-preview.html" title="工具调用"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" /></svg>工具</a>
|
||||
<a class="nav-item" data-nav="模型网关" data-route="/models" href="model-gateway-preview.html" title="模型网关"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="2" /><rect x="9" y="9" width="6" height="6" /><path d="M9 2v2M15 2v2M9 20v2M15 20v2M2 9h2M2 15h2M20 9h2M20 15h2" /></svg>模型网关</a>
|
||||
<a class="nav-item" data-nav="设置" data-route="/settings" href="settings-preview.html" title="设置"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><line x1="4" y1="21" x2="4" y2="14" /><line x1="4" y1="10" x2="4" y2="3" /><line x1="12" y1="21" x2="12" y2="12" /><line x1="12" y1="8" x2="12" y2="3" /><line x1="20" y1="21" x2="20" y2="16" /><line x1="20" y1="12" x2="20" y2="3" /><line x1="1" y1="14" x2="7" y2="14" /><line x1="9" y1="8" x2="15" y2="8" /><line x1="17" y1="16" x2="23" y2="16" /></svg>设置</a>
|
||||
<a class="nav-item siming" data-nav="司命台" data-route="/admin" href="admin-preview.html" title="司命台 · 管理控制台"><svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /></svg>司命台</a>
|
||||
</div>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<!-- 底部:用户卡 + 设计系统入口 + 主题(「设置」已在百工组,不在此重复) -->
|
||||
<div class="user-card">
|
||||
<div class="avatar">辰</div>
|
||||
<div class="info">
|
||||
<div class="name">辰</div>
|
||||
<div class="role">司命</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="foot-btn" href="copper-mirror-redesign-v2.html" title="设计系统 · 北极星基准">
|
||||
<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" /><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76" /></svg>
|
||||
<span class="foot-label">设计系统</span>
|
||||
</a>
|
||||
<button class="nav-item nav-theme" id="theme-toggle" type="button">
|
||||
<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /></svg>
|
||||
<span id="theme-label">夜铜</span>
|
||||
</button>
|
||||
</aside>
|
||||
</div>
|
||||
@@ -0,0 +1,208 @@
|
||||
/* ============================================================
|
||||
* design/_theme.js — 原型共享脚本层
|
||||
* ============================================================
|
||||
* 职责:
|
||||
* 1. fetch _nav.html → 注入各页 .sidebar-mount
|
||||
* 2. 按 mount 的 data-active 高亮当前页 nav-item
|
||||
* 3. logo 点击 → index.html(目录入口)
|
||||
* 4. #theme-toggle 切换暗/亮,localStorage 持久化
|
||||
*
|
||||
* file:// 限制:fetch 在 file:// 协议下被 CORS 拦截,会显示 fallback
|
||||
* 提示。请用 `pnpm design` 启动本地预览服务器获得完整导航。
|
||||
*
|
||||
* 用法:页面末尾 <script src="_theme.js" defer></script>
|
||||
* 页面 body 内放 <div class="sidebar-mount" data-active="页面名"></div>
|
||||
* ============================================================ */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var ROOT = document.documentElement;
|
||||
|
||||
/* iframe 缩略图态:index.html 以 live <iframe> 嵌入各原型页做缩略图;
|
||||
检测嵌入态加 .in-frame,由 _base.css 把动画瞬间完成,避免 25 个
|
||||
缩略图同时持续重绘而耗 CPU。 */
|
||||
if (window.self !== window.top) ROOT.classList.add("in-frame");
|
||||
|
||||
function currentTheme() {
|
||||
return ROOT.dataset.theme === "light" ? "light" : "dark";
|
||||
}
|
||||
function applyTheme(t) {
|
||||
ROOT.dataset.theme = t;
|
||||
try {
|
||||
localStorage.setItem("design-theme", t);
|
||||
} catch (e) {}
|
||||
document.querySelectorAll("#theme-label").forEach(function (l) {
|
||||
l.textContent = t === "dark" ? "夜铜" : "素绢";
|
||||
});
|
||||
}
|
||||
function toggleTheme() {
|
||||
applyTheme(currentTheme() === "dark" ? "light" : "dark");
|
||||
}
|
||||
|
||||
/* 恢复上次主题(减轻刷新闪屏;data-theme 默认 dark 在 <html> 上已设) */
|
||||
try {
|
||||
var saved = localStorage.getItem("design-theme");
|
||||
if (saved === "light" || saved === "dark") ROOT.dataset.theme = saved;
|
||||
} catch (e) {}
|
||||
|
||||
/* 事件委托:logo / 主题按钮(nav 注入前后都生效,index 页独立按钮也覆盖) */
|
||||
document.addEventListener("click", function (e) {
|
||||
var t = e.target;
|
||||
if (!t || !t.closest) return;
|
||||
if (t.closest("#theme-toggle")) {
|
||||
e.preventDefault();
|
||||
toggleTheme();
|
||||
return;
|
||||
}
|
||||
if (t.closest("#sidebar-fold")) {
|
||||
e.preventDefault();
|
||||
toggleFold();
|
||||
return;
|
||||
}
|
||||
if (t.closest(".sidebar .logo")) {
|
||||
e.preventDefault();
|
||||
onLogoClick();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
/* 折叠侧栏(chat 默认折叠让对话区扩展,其他页默认展开;localStorage 持久化) */
|
||||
function applyFold(state) {
|
||||
document.querySelectorAll(".sidebar-wrap").forEach(function (w) {
|
||||
w.classList.toggle("collapsed", state === "collapsed");
|
||||
});
|
||||
}
|
||||
function toggleFold() {
|
||||
var wrap = document.querySelector(".sidebar-wrap");
|
||||
if (!wrap) return;
|
||||
var collapsed = wrap.classList.toggle("collapsed");
|
||||
try {
|
||||
localStorage.setItem(
|
||||
"design-sidebar",
|
||||
collapsed ? "collapsed" : "expanded",
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
function initFold() {
|
||||
var mount = document.querySelector(".sidebar-mount");
|
||||
var isChat = mount && mount.dataset.active === "对话";
|
||||
var saved = null;
|
||||
try {
|
||||
saved = localStorage.getItem("design-sidebar");
|
||||
} catch (e) {}
|
||||
applyFold(saved || (isChat ? "collapsed" : "expanded"));
|
||||
}
|
||||
|
||||
/* ── logo:单击即时回目录;350ms 内连点 5 次触发「镜启」彩蛋(不跳转)── */
|
||||
var EGG_VERSES = [
|
||||
"镜中有镜,心外无心。",
|
||||
"汝来看此镜时,此镜亦照汝。",
|
||||
"一念起,万镜生。",
|
||||
"镜非鉴物,乃鉴心也。",
|
||||
"幻由心生,镜随心转。",
|
||||
"九枢归一,一归于何处?",
|
||||
"铜镜本无字,照尽往来人。",
|
||||
"且向镜中看,那个是谁?",
|
||||
];
|
||||
var logoClickSeq = 0;
|
||||
var lastLogoTime = 0;
|
||||
|
||||
function showEggToast(msg) {
|
||||
var toast = document.createElement("div");
|
||||
toast.textContent = msg;
|
||||
toast.style.cssText =
|
||||
"position:fixed;bottom:48px;left:50%;transform:translateX(-50%);padding:12px 28px;border-radius:12px;background:var(--patina-wash);color:var(--patina);font-family:'Songti SC',serif;font-size:14px;border:1px solid var(--patina-dim);z-index:9999;pointer-events:none;opacity:0;transition:opacity 0.4s;";
|
||||
document.body.appendChild(toast);
|
||||
requestAnimationFrame(function () {
|
||||
toast.style.opacity = "1";
|
||||
});
|
||||
setTimeout(function () {
|
||||
toast.style.opacity = "0";
|
||||
setTimeout(function () {
|
||||
toast.remove();
|
||||
}, 400);
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
function onLogoClick() {
|
||||
var logoEl = document.querySelector(".sidebar .logo");
|
||||
var now = Date.now();
|
||||
var fast = now - lastLogoTime < 350; /* 350ms 内视为连点 */
|
||||
lastLogoTime = now;
|
||||
if (fast) {
|
||||
logoClickSeq += 1;
|
||||
if (logoEl) {
|
||||
logoEl.classList.add("tick");
|
||||
setTimeout(function () {
|
||||
logoEl.classList.remove("tick");
|
||||
}, 300);
|
||||
}
|
||||
if (logoClickSeq >= 5) {
|
||||
/* 5 连点:镜启彩蛋,不跳转 */
|
||||
logoClickSeq = 0;
|
||||
var v = EGG_VERSES[Math.floor(Math.random() * EGG_VERSES.length)];
|
||||
if (logoEl) {
|
||||
logoEl.classList.add("egg-pulse");
|
||||
setTimeout(function () {
|
||||
logoEl.classList.remove("egg-pulse");
|
||||
}, 1400);
|
||||
}
|
||||
showEggToast("镜启 · " + v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* 单击(间隔 > 350ms):tick 反馈后立即回目录 */
|
||||
logoClickSeq = 0;
|
||||
if (logoEl) {
|
||||
logoEl.classList.add("tick");
|
||||
setTimeout(function () {
|
||||
logoEl.classList.remove("tick");
|
||||
}, 300);
|
||||
}
|
||||
location.href = "index.html";
|
||||
}
|
||||
|
||||
function highlightActive(mount) {
|
||||
var key = mount.dataset.active;
|
||||
if (!key) return;
|
||||
var item = mount.querySelector('[data-nav="' + key + '"]');
|
||||
if (item) item.classList.add("active");
|
||||
}
|
||||
|
||||
/* file:// fallback:提示需启动本地服务器 */
|
||||
function showFallback(mount) {
|
||||
mount.innerHTML =
|
||||
'<div class="sidebar-wrap"><aside class="sidebar" style="padding:48px 20px;text-align:center;display:flex;flex-direction:column;gap:18px;align-items:center">' +
|
||||
"<div style=\"font-family:'Songti SC',serif;font-size:18px;font-weight:700;letter-spacing:0.1em;color:var(--fg)\">魂鏡</div>" +
|
||||
'<div style="font-size:12px;color:var(--fg-dim);line-height:1.7">导航需本地预览服务器<br/><code style="font-family:\'JetBrains Mono\',monospace;font-size:11px;color:var(--patina)">pnpm design</code></div>' +
|
||||
'<a class="nav-item" href="index.html" style="justify-content:center;border:1px solid var(--rule);padding:7px 16px">← 目录</a>' +
|
||||
"</aside></div>";
|
||||
}
|
||||
|
||||
function injectNav() {
|
||||
var mounts = document.querySelectorAll(".sidebar-mount");
|
||||
if (!mounts.length) return;
|
||||
fetch("_nav.html", { cache: "no-cache" })
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error(String(res.status));
|
||||
return res.text();
|
||||
})
|
||||
.then(function (html) {
|
||||
mounts.forEach(function (m) {
|
||||
m.innerHTML = html;
|
||||
highlightActive(m);
|
||||
});
|
||||
applyTheme(currentTheme()); /* 注入后同步主题标签文案 */
|
||||
initFold(); /* 恢复折叠态 */
|
||||
})
|
||||
.catch(function () {
|
||||
mounts.forEach(showFallback);
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", injectNav);
|
||||
} else {
|
||||
injectNav();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,752 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 司命台 · 人物模板管理</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.3 — 司命台 · 人物模板管理(§19.8)
|
||||
*
|
||||
* 文档基准:docs/product/master-prd.md §2-§5
|
||||
* · 铜镜四态 = MirrorState 健康度(void/breathing/steady/cracked),§2.3
|
||||
* · 三签分层:星盘(空间·主角)/ 朱批(动作)/ 起居注(时间),§3
|
||||
* · 司命台独立壳层(AdminShell 横向导航),§4
|
||||
*
|
||||
* data-view:char-templates
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 司命台壳层 ── */
|
||||
.admin-shell { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
.topbar { position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: 10px; padding: 10px 28px; border-bottom: 1px solid var(--rule); background: var(--surface); backdrop-filter: blur(16px) saturate(140%); flex-wrap: wrap; }
|
||||
.topbar-brand { font-family: "Songti SC", serif; font-size: 17px; font-weight: 700; letter-spacing: 0.06em; color: var(--fg); display: flex; align-items: center; gap: 7px; flex-shrink: 0; cursor: pointer; }
|
||||
.topbar-brand .knob { width: 16px; height: 16px; }
|
||||
.topbar-brand .knob circle:last-child { fill: var(--cinnabar); }
|
||||
.topbar-sep { color: var(--fg-faint); opacity: 0.5; flex-shrink: 0; }
|
||||
.topbar-nav { display: flex; align-items: center; gap: 1px; flex-wrap: wrap; }
|
||||
.topbar-navitem { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: 8px; font-size: 13px; font-weight: 500; color: var(--muted); text-decoration: none; transition: background 0.12s, color 0.12s; cursor: pointer; background: none; border: none; font-family: inherit; }
|
||||
.topbar-navitem:hover { background: var(--overlay-hover); color: var(--fg); }
|
||||
.topbar-navitem.active { background: var(--patina-wash); color: var(--patina); }
|
||||
.topbar-navitem.pivot { font-size: 12px; padding: 4px 9px; }
|
||||
.topbar-spacer { flex: 1; }
|
||||
|
||||
/* ── 视图与页头 ── */
|
||||
main { flex: 1; display: flex; flex-direction: column; min-height: 0; }
|
||||
|
||||
.page-header .spacer { flex: 1; }
|
||||
.header-badge { display: inline-flex; align-items: center; gap: 6px; font-family: "JetBrains Mono", monospace; font-size: 11.5px; padding: 4px 12px; border-radius: 999px; border: 1px solid var(--patina-dim); color: var(--patina); background: var(--patina-wash); }
|
||||
.view-body { flex: 1; padding: 16px 28px 48px; display: flex; flex-direction: column; gap: 22px; max-width: 1180px; width: 100%; overflow-y: auto; }
|
||||
|
||||
.section-label .glyph { color: var(--cinnabar); opacity: 0.85; }
|
||||
.section-heading { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin-bottom: 12px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||||
.section-heading .sub { font-size: 12px; font-weight: 400; color: var(--fg-dim); margin-left: 6px; }
|
||||
.section-heading .glyph { color: var(--cinnabar); opacity: 0.85; }
|
||||
|
||||
/* ── 操作栏 ── */
|
||||
.toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 4px; }
|
||||
.toolbar .spacer { flex: 1; }
|
||||
|
||||
|
||||
.btn-danger { border-color: var(--cinnabar-dim); background: var(--cinnabar-wash); color: var(--cinnabar); }
|
||||
.btn-danger:hover { background: var(--cinnabar-dim); color: var(--fg); }
|
||||
|
||||
.btn-icon { padding: 6px; min-width: 0; }
|
||||
.search-input { display: inline-flex; align-items: center; gap: 8px; padding: 6px 14px; border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); color: var(--fg); font-family: inherit; font-size: 13px; min-width: 200px; transition: border-color 0.15s; }
|
||||
.search-input:focus { outline: none; border-color: var(--patina-dim); }
|
||||
.search-input::placeholder { color: var(--fg-faint); }
|
||||
|
||||
/* ── 模板列表 ── */
|
||||
.template-table { width: 100%; border-collapse: separate; border-spacing: 0; }
|
||||
.template-table th { font-family: "JetBrains Mono", monospace; font-size: 11.5px; letter-spacing: 0.06em; color: var(--fg-faint); padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--rule); font-weight: 500; }
|
||||
.template-table td { padding: 12px 14px; border-bottom: 1px solid var(--rule); font-size: 14px; vertical-align: top; }
|
||||
.template-table tr:hover td { background: var(--surface-hi); }
|
||||
.template-table .key-cell { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--gilt); white-space: nowrap; }
|
||||
.template-table .name-cell { font-weight: 600; }
|
||||
.template-table .display-name-cell { color: var(--fg-dim); }
|
||||
.template-table .desc-cell { color: var(--fg-dim); max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.template-table .date-cell { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg-faint); white-space: nowrap; }
|
||||
.template-table .actions-cell { white-space: nowrap; text-align: right; }
|
||||
.template-table .actions-cell .btn { margin-left: 4px; }
|
||||
|
||||
/* ── 标签 ── */
|
||||
|
||||
.tag-default { background: var(--patina-wash); color: var(--patina); border: 1px solid var(--patina-dim); }
|
||||
.tag-api { background: var(--blue-wash); color: var(--blue); border: 1px solid var(--blue-dim); }
|
||||
.tag-yaml { background: var(--gilt-faint); color: var(--gilt); border: 1px solid var(--gilt-dim); }
|
||||
|
||||
/* ── 空状态 ── */
|
||||
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; text-align: center; }
|
||||
.empty-state .glyph { font-size: 36px; margin-bottom: 12px; opacity: 0.3; }
|
||||
.empty-state .title { font-family: "Songti SC", serif; font-size: 16px; color: var(--fg-dim); }
|
||||
.empty-state .desc { font-size: 13px; color: var(--fg-faint); margin-top: 6px; }
|
||||
|
||||
/* ── Modal ── */
|
||||
.modal-overlay { position: fixed; inset: 0; z-index: 100; background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(6px); display: flex; align-items: center; justify-content: center; padding: 20px; }
|
||||
.modal { background: var(--ink); border: 1px solid var(--rule); border-radius: 18px; width: 100%; max-width: 620px; max-height: 88vh; display: flex; flex-direction: column; box-shadow: 0 12px 60px rgba(0, 0, 0, 0.6); }
|
||||
.modal-header { display: flex; align-items: center; gap: 12px; padding: 18px 22px 10px; border-bottom: 1px solid var(--rule); }
|
||||
.modal-title { font-family: "Songti SC", serif; font-size: 17px; font-weight: 600; color: var(--fg); flex: 1; }
|
||||
.modal-close { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 8px; cursor: pointer; border: none; background: none; color: var(--fg-faint); font-size: 18px; transition: background 0.12s, color 0.12s; }
|
||||
.modal-close:hover { background: var(--surface-hi); color: var(--fg); }
|
||||
.modal-body { flex: 1; overflow-y: auto; padding: 18px 22px 22px; display: flex; flex-direction: column; gap: 16px; }
|
||||
.modal-footer { display: flex; align-items: center; gap: 10px; padding: 12px 22px 18px; border-top: 1px solid var(--rule); justify-content: flex-end; }
|
||||
|
||||
.field { display: flex; flex-direction: column; gap: 5px; }
|
||||
.field-label { font-family: "JetBrains Mono", monospace; font-size: 11.5px; letter-spacing: 0.06em; color: var(--fg-faint); }
|
||||
.field-hint { font-size: 12px; color: var(--fg-faint); margin-top: 2px; }
|
||||
.field input, .field textarea, .field select { padding: 8px 12px; border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); color: var(--fg); font-family: inherit; font-size: 13px; transition: border-color 0.15s; width: 100%; }
|
||||
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--patina-dim); }
|
||||
.field textarea { min-height: 72px; resize: vertical; }
|
||||
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
||||
|
||||
/* ── Separator ── */
|
||||
.form-section { margin-top: 4px; padding-top: 14px; border-top: 1px solid var(--rule); }
|
||||
.form-section-title { font-family: "Songti SC", serif; font-size: 13px; font-weight: 600; color: var(--gilt); margin-bottom: 12px; display: flex; align-items: center; gap: 6px; }
|
||||
.form-section-title .glyph { opacity: 0.6; }
|
||||
|
||||
/* ── Tag input (hobbies/values) ── */
|
||||
.tag-input-wrap { display: flex; flex-wrap: wrap; gap: 5px; padding: 6px 8px; border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); min-height: 36px; align-items: center; cursor: text; transition: border-color 0.15s; }
|
||||
.tag-input-wrap:focus-within { border-color: var(--patina-dim); }
|
||||
.tag-input-tag { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 999px; font-size: 12px; background: var(--patina-wash); color: var(--patina); border: 1px solid var(--patina-dim); }
|
||||
.tag-input-tag .remove { cursor: pointer; opacity: 0.5; font-size: 14px; line-height: 1; margin-left: 2px; }
|
||||
.tag-input-tag .remove:hover { opacity: 1; color: var(--cinnabar); }
|
||||
.tag-input-native { border: none !important; background: transparent !important; outline: none !important; flex: 1; min-width: 80px; padding: 2px 4px; font-size: 13px; color: var(--fg); font-family: inherit; }
|
||||
|
||||
/* ── Detail modal extra ── */
|
||||
.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.detail-item { border: 1px solid var(--rule); border-radius: 10px; padding: 12px; background: var(--surface); }
|
||||
.detail-item.full { grid-column: 1 / -1; }
|
||||
.detail-item .label { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.06em; color: var(--fg-faint); margin-bottom: 4px; }
|
||||
.detail-item .value { font-size: 13px; color: var(--fg); white-space: pre-wrap; }
|
||||
.detail-item .value.json { font-family: "JetBrains Mono", monospace; font-size: 11.5px; }
|
||||
|
||||
/* ── Delete confirmation ── */
|
||||
.confirm-box { border: 1px solid var(--cinnabar-dim); border-radius: 12px; padding: 16px; background: var(--cinnabar-wash); display: flex; flex-direction: column; gap: 10px; }
|
||||
.confirm-box .title { font-size: 14px; font-weight: 600; color: var(--cinnabar); }
|
||||
.confirm-box .desc { font-size: 13px; color: var(--fg-dim); }
|
||||
|
||||
/* ── Toast ── */
|
||||
.toast { position: fixed; bottom: 24px; right: 24px; z-index: 200; display: flex; align-items: center; gap: 10px; padding: 12px 18px; border-radius: 12px; border: 1px solid var(--patina-dim); background: var(--surface); backdrop-filter: blur(12px); box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5); font-size: 13px; color: var(--patina); }
|
||||
.toast.error { border-color: var(--cinnabar-dim); color: var(--cinnabar); }
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.view-body { padding: 12px 16px 36px; }
|
||||
.field-row { grid-template-columns: 1fr; }
|
||||
.detail-grid { grid-template-columns: 1fr; }
|
||||
.template-table th, .template-table td { padding: 8px 10px; }
|
||||
.template-table .desc-cell { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<div class="sidebar-mount" data-active="司命台"></div>
|
||||
<div class="main-area">
|
||||
<div class="admin-shell">
|
||||
|
||||
<!-- ═══ 司命台顶栏 ═══ -->
|
||||
<header class="topbar">
|
||||
<div class="topbar-brand">
|
||||
<svg class="knob" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" fill="none" stroke="var(--gilt)" stroke-width="1.3"/><circle cx="8" cy="8" r="2"/></svg>
|
||||
司命台
|
||||
</div>
|
||||
<span class="topbar-sep">|</span>
|
||||
<nav class="topbar-nav">
|
||||
<span class="topbar-navitem">星盘</span>
|
||||
<span class="topbar-navitem">朱批</span>
|
||||
<span class="topbar-navitem">起居注</span>
|
||||
<span class="topbar-navitem">太一</span>
|
||||
<span class="topbar-navitem">烛照</span>
|
||||
<span class="topbar-navitem">河图</span>
|
||||
<span class="topbar-navitem">洛书</span>
|
||||
<span class="topbar-navitem">天工阁</span>
|
||||
<span class="topbar-navitem">用户</span>
|
||||
<span class="topbar-navitem active">人物模板</span>
|
||||
<span class="topbar-navitem">链路</span>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- ═══ 页头 ═══ -->
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">人物模板</h1>
|
||||
<span class="page-sub">Character Templates · §19.8</span>
|
||||
<span class="header-badge">2 个模板</span>
|
||||
<div class="spacer"></div>
|
||||
<button class="btn btn-primary" onclick="openCreate()">✦ 新建模板</button>
|
||||
</div>
|
||||
|
||||
<div class="view-body">
|
||||
|
||||
<!-- ── 操作栏 ── -->
|
||||
<div class="toolbar">
|
||||
<input class="search-input" type="text" placeholder="搜索模板 key 或名称…" />
|
||||
<div class="spacer"></div>
|
||||
<button class="btn btn-sm" onclick="syncFromYaml()">↻ 从 YAML 同步</button>
|
||||
</div>
|
||||
|
||||
<!-- ── 模板列表 ── -->
|
||||
<table class="template-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>名称</th>
|
||||
<th>显示名</th>
|
||||
<th>来源</th>
|
||||
<th>简介</th>
|
||||
<th>创建时间</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 魂镜(YAML 管理) -->
|
||||
<tr>
|
||||
<td class="key-cell">default</td>
|
||||
<td class="name-cell">魂镜</td>
|
||||
<td class="display-name-cell">魂镜</td>
|
||||
<td><span class="tag tag-yaml">YAML</span></td>
|
||||
<td class="desc-cell">一面照见人心的镜子</td>
|
||||
<td class="date-cell">2026-06-19</td>
|
||||
<td class="actions-cell">
|
||||
<button class="btn btn-sm" onclick="openDetail('default')">查看</button>
|
||||
<button class="btn btn-sm" onclick="openEdit('default')">编辑</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="confirmDelete('default')">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 时崎狂三(YAML 管理) -->
|
||||
<tr>
|
||||
<td class="key-cell">tokisaki_kurumi</td>
|
||||
<td class="name-cell">时崎狂三</td>
|
||||
<td class="display-name-cell">时崎狂三</td>
|
||||
<td><span class="tag tag-yaml">YAML</span></td>
|
||||
<td class="desc-cell">被称为「梦魇」的时间精灵</td>
|
||||
<td class="date-cell">2026-07-09</td>
|
||||
<td class="actions-cell">
|
||||
<button class="btn btn-sm" onclick="openDetail('tokisaki_kurumi')">查看</button>
|
||||
<button class="btn btn-sm" onclick="openEdit('tokisaki_kurumi')">编辑</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="confirmDelete('tokisaki_kurumi')">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- API 创建的模板 -->
|
||||
<tr>
|
||||
<td class="key-cell">api_gufeng</td>
|
||||
<td class="name-cell">古风人物</td>
|
||||
<td class="display-name-cell">墨语</td>
|
||||
<td><span class="tag tag-api">API</span></td>
|
||||
<td class="desc-cell">穿越千年而来的书卷气</td>
|
||||
<td class="date-cell">2026-07-13</td>
|
||||
<td class="actions-cell">
|
||||
<button class="btn btn-sm" onclick="openDetail('api_gufeng')">查看</button>
|
||||
<button class="btn btn-sm" onclick="openEdit('api_gufeng')">编辑</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="confirmDelete('api_gufeng')">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ── 来自 YAML 的提示 ── -->
|
||||
<div class="detail-item" style="border-style:dashed;">
|
||||
<div class="label">⚠ YAML 管理提示</div>
|
||||
<div style="font-size:13px; color:var(--fg-dim);">
|
||||
key 与 <code style="font-size:12px; color:var(--gilt);">config/character_templates.yaml</code> 中定义一致的模板,在服务重启时会从 YAML 文件覆盖同步。
|
||||
API 创建的模板(来源标记为 API)不受影响。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /view-body -->
|
||||
</main>
|
||||
</div><!-- /admin-shell -->
|
||||
|
||||
<!-- ═══ 查看详情 Modal ═══ -->
|
||||
<div class="modal-overlay" id="modal-detail" style="display:none;">
|
||||
<div class="modal" style="max-width: 680px;">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">模板详情 · <span id="detail-key">default</span></h2>
|
||||
<button class="modal-close" onclick="closeModal('modal-detail')">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<div class="label">Key</div>
|
||||
<div class="value" id="d-key" style="font-family:monospace;">default</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">来源</div>
|
||||
<div><span class="tag tag-yaml">YAML</span></div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">名称</div>
|
||||
<div class="value" id="d-name">魂镜</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">显示名</div>
|
||||
<div class="value" id="d-display">魂镜</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">语言</div>
|
||||
<div class="value" id="d-lang">zh-CN</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">私隐等级</div>
|
||||
<div class="value" id="d-privacy">normal</div>
|
||||
</div>
|
||||
<div class="detail-item full">
|
||||
<div class="label">人物档案(profile)</div>
|
||||
<div class="value json" id="d-profile">{
|
||||
"description": "一面照见人心的镜子",
|
||||
"background": "默认人物",
|
||||
"speaking_style": "温和、清晰、简洁",
|
||||
"temperament": "沉静而温暖",
|
||||
"hobbies": ["哲学", "文学", "围棋"],
|
||||
"values": ["真诚", "同理心", "独立思考"],
|
||||
"birthday": "2026-06-19",
|
||||
}</div>
|
||||
</div>
|
||||
<div class="detail-item full">
|
||||
<div class="label">模型策略(model_policy)</div>
|
||||
<div class="value json" id="d-policy">{
|
||||
"memory_extraction_model_hint": "memory-extraction",
|
||||
"personality_analysis_model_hint": "personality-analysis",
|
||||
"embedding_model_hint": "embedding",
|
||||
"default_chat_model_hint": "deepseek-v4-flash"
|
||||
}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" onclick="closeModal('modal-detail')">关闭</button>
|
||||
<button class="btn btn-primary" onclick="closeModal('modal-detail'); openEdit('default')">编辑</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 创建/编辑 Modal ═══ -->
|
||||
<div class="modal-overlay" id="modal-edit" style="display:none;">
|
||||
<div class="modal" style="max-width: 720px;">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="edit-title">新建模板</h2>
|
||||
<button class="modal-close" onclick="closeModal('modal-edit')">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- ── 基础信息 ── -->
|
||||
<div class="field-row">
|
||||
<div class="field">
|
||||
<label class="field-label">Key *</label>
|
||||
<input id="f-key" type="text" placeholder="唯一标识,如 my_template" />
|
||||
<span class="field-hint">仅小写字母、数字、下划线、连字符。创建后不可修改。</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">是否默认</label>
|
||||
<select id="f-is-default">
|
||||
<option value="false">否</option>
|
||||
<option value="true">是(新用户以此创建初始角色)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<div class="field">
|
||||
<label class="field-label">名称 *</label>
|
||||
<input id="f-name" type="text" placeholder="内部名称" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">显示名</label>
|
||||
<input id="f-display" type="text" placeholder="显示昵称" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<div class="field">
|
||||
<label class="field-label">语言</label>
|
||||
<select id="f-lang">
|
||||
<option value="zh-CN">zh-CN</option>
|
||||
<option value="en">en</option>
|
||||
<option value="ja">ja</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">私隐等级</label>
|
||||
<select id="f-privacy">
|
||||
<option value="normal">normal</option>
|
||||
<option value="sensitive">sensitive</option>
|
||||
<option value="local_only">local_only</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 人物档案 ── -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-title"><span class="glyph">📋</span> 人物档案(Profile)</div>
|
||||
|
||||
<div class="field" style="margin-bottom:12px;">
|
||||
<label class="field-label">一句话简介</label>
|
||||
<input id="f-description" type="text" placeholder="例如:一面照见人心的镜子" />
|
||||
</div>
|
||||
|
||||
<div class="field" style="margin-bottom:12px;">
|
||||
<label class="field-label">背景故事</label>
|
||||
<textarea id="f-background" rows="4" placeholder="人物背景故事设定…"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<div class="field">
|
||||
<label class="field-label">说话风格</label>
|
||||
<input id="f-speaking-style" type="text" placeholder="例如:温和、清晰、简洁" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">性情气质</label>
|
||||
<input id="f-temperament" type="text" placeholder="例如:沉静而温暖" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row" style="margin-top:12px;">
|
||||
<div class="field">
|
||||
<label class="field-label">生日</label>
|
||||
<input id="f-birthday" type="date" />
|
||||
<span class="field-hint">留空则系统用创建时间自动兜底</span>
|
||||
</div>
|
||||
<div class="field-row" style="grid-template-columns:1fr 1fr; gap:10px; margin:0;">
|
||||
<div class="field">
|
||||
<label class="field-label">身高</label>
|
||||
<input id="f-height" type="number" placeholder="cm" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">体重</label>
|
||||
<input id="f-weight" type="number" placeholder="kg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row" style="margin-top:12px;">
|
||||
<div class="field">
|
||||
<label class="field-label">兴趣爱好</label>
|
||||
<div class="tag-input-wrap" id="hobbies-wrap">
|
||||
<span class="tag-input-tag">抚琴<span class="remove" onclick="removeTag(this)">×</span></span>
|
||||
<span class="tag-input-tag">书法<span class="remove" onclick="removeTag(this)">×</span></span>
|
||||
<span class="tag-input-tag">品茶<span class="remove" onclick="removeTag(this)">×</span></span>
|
||||
<span class="tag-input-tag">对弈<span class="remove" onclick="removeTag(this)">×</span></span>
|
||||
<input class="tag-input-native" id="f-hobbies-input" type="text" placeholder="输入后按回车添加…" onkeydown="if(event.key==='Enter'){event.preventDefault();addTag(this,document.getElementById('hobbies-hidden'));}" />
|
||||
</div>
|
||||
<input id="hobbies-hidden" type="hidden" value='["抚琴","书法","品茶","对弈"]' />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">价值观</label>
|
||||
<div class="tag-input-wrap" id="values-wrap">
|
||||
<span class="tag-input-tag">中庸<span class="remove" onclick="removeTag(this)">×</span></span>
|
||||
<span class="tag-input-tag">礼义<span class="remove" onclick="removeTag(this)">×</span></span>
|
||||
<span class="tag-input-tag">自然<span class="remove" onclick="removeTag(this)">×</span></span>
|
||||
<input class="tag-input-native" id="f-values-input" type="text" placeholder="输入后按回车添加…" onkeydown="if(event.key==='Enter'){event.preventDefault();addTag(this,document.getElementById('values-hidden'));}" />
|
||||
</div>
|
||||
<input id="values-hidden" type="hidden" value='["中庸","礼义","自然"]' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 额外内容(JSON,补充人物档案) ── -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-title"><span class="glyph">⋯</span> 额外内容(Extra JSON)<span style="font-weight:400;font-size:12px;color:var(--fg-faint);margin-left:6px;">以上字段未覆盖的自定义 profile 键值</span></div>
|
||||
<div class="field">
|
||||
<textarea id="f-extra-json" rows="3" style="font-family:monospace;font-size:12px;">{}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 模型策略 ── -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-title"><span class="glyph">⚙</span> 模型策略(Model Policy)<span style="font-weight:400;font-size:12px;color:var(--fg-faint);margin-left:6px;">不选则留空</span></div>
|
||||
<div class="field-row" style="grid-template-columns:1fr 1fr;">
|
||||
<div class="field">
|
||||
<label class="field-label">对话模型</label>
|
||||
<select id="f-chat-hint">
|
||||
<option value="">— 不设置(使用系统默认)</option>
|
||||
<option value="deepseek-v4-flash">deepseek-v4-flash</option>
|
||||
<option value="deepseek-v3">deepseek-v3</option>
|
||||
<option value="gpt-4o">gpt-4o</option>
|
||||
<option value="claude-sonnet-5">claude-sonnet-5</option>
|
||||
<option value="claude-opus-4-8">claude-opus-4-8</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">记忆提取模型</label>
|
||||
<select id="f-memory-hint">
|
||||
<option value="">— 不设置(空)</option>
|
||||
<option value="memory-extraction">memory-extraction</option>
|
||||
<option value="deepseek-v4-flash">deepseek-v4-flash</option>
|
||||
<option value="deepseek-v3">deepseek-v3</option>
|
||||
<option value="gpt-4o-mini">gpt-4o-mini</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">人格分析模型</label>
|
||||
<select id="f-personality-hint">
|
||||
<option value="">— 不设置(空)</option>
|
||||
<option value="personality-analysis">personality-analysis</option>
|
||||
<option value="deepseek-v4-flash">deepseek-v4-flash</option>
|
||||
<option value="deepseek-v3">deepseek-v3</option>
|
||||
<option value="gpt-4o">gpt-4o</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">向量嵌入模型</label>
|
||||
<select id="f-embedding-hint">
|
||||
<option value="">— 不设置(空)</option>
|
||||
<option value="embedding">embedding</option>
|
||||
<option value="text-embedding-3-small">text-embedding-3-small</option>
|
||||
<option value="text-embedding-3-large">text-embedding-3-large</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" onclick="closeModal('modal-edit')">取消</button>
|
||||
<button class="btn btn-primary" id="edit-save-btn">✦ 保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 删除确认 Modal ═══ -->
|
||||
<div class="modal-overlay" id="modal-delete" style="display:none;">
|
||||
<div class="modal" style="max-width: 460px;">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">确认删除</h2>
|
||||
<button class="modal-close" onclick="closeModal('modal-delete')">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="confirm-box">
|
||||
<div class="title">⚠ 确定要删除此模板?</div>
|
||||
<div class="desc">
|
||||
将删除系统模板 <strong id="del-key-name">default · 魂镜</strong>。如果有用户人物引用了此模板将无法删除。
|
||||
此操作不可撤销。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" onclick="closeModal('modal-delete')">取消</button>
|
||||
<button class="btn btn-danger" onclick="performDelete()">✕ 确认删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ Toast ═══ -->
|
||||
<div class="toast" id="toast" style="display:none;">✔ 操作成功</div>
|
||||
|
||||
<script>
|
||||
// ── Tag input helpers ──
|
||||
function addTag(inputEl, hiddenEl) {
|
||||
const val = inputEl.value.trim();
|
||||
if (!val) return;
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'tag-input-tag';
|
||||
tag.innerHTML = val + '<span class="remove" onclick="removeTag(this)">×</span>';
|
||||
inputEl.parentNode.insertBefore(tag, inputEl);
|
||||
inputEl.value = '';
|
||||
syncTags(hiddenEl);
|
||||
}
|
||||
function removeTag(removeEl) {
|
||||
const tag = removeEl.parentNode;
|
||||
const wrap = tag.parentNode;
|
||||
tag.remove();
|
||||
const hidden = wrap.parentNode.querySelector('input[type="hidden"]') || wrap.nextElementSibling;
|
||||
if (hidden) syncTags(hidden);
|
||||
}
|
||||
function syncTags(hiddenEl) {
|
||||
const wrap = hiddenEl.parentNode.querySelector('.tag-input-wrap') || hiddenEl.previousElementSibling;
|
||||
const items = [];
|
||||
wrap.querySelectorAll('.tag-input-tag').forEach(t => items.push(t.textContent.replace('×','').trim()));
|
||||
hiddenEl.value = JSON.stringify(items);
|
||||
}
|
||||
function populateTags(wrapId, hiddenId, values) {
|
||||
const wrap = document.getElementById(wrapId);
|
||||
const hidden = document.getElementById(hiddenId);
|
||||
// clear existing tags (keep native input)
|
||||
wrap.querySelectorAll('.tag-input-tag').forEach(t => t.remove());
|
||||
values.forEach(v => {
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'tag-input-tag';
|
||||
tag.innerHTML = v + '<span class="remove" onclick="removeTag(this)">×</span>';
|
||||
wrap.insertBefore(tag, wrap.querySelector('.tag-input-native'));
|
||||
});
|
||||
hidden.value = JSON.stringify(values);
|
||||
}
|
||||
|
||||
// ── Simple state ──
|
||||
const state = { deleteKey: null, deleteName: null };
|
||||
|
||||
function closeModal(id) { document.getElementById(id).style.display = 'none'; }
|
||||
|
||||
function openCreate() {
|
||||
document.getElementById('edit-title').textContent = '✦ 新建模板';
|
||||
document.getElementById('edit-save-btn').textContent = '✦ 创建';
|
||||
document.getElementById('f-key').disabled = false;
|
||||
document.getElementById('f-key').value = '';
|
||||
document.getElementById('f-name').value = '';
|
||||
document.getElementById('f-display').value = '';
|
||||
document.getElementById('f-lang').value = 'zh-CN';
|
||||
document.getElementById('f-privacy').value = 'normal';
|
||||
document.getElementById('f-is-default').value = 'false';
|
||||
document.getElementById('f-description').value = '';
|
||||
document.getElementById('f-background').value = '';
|
||||
document.getElementById('f-speaking-style').value = '';
|
||||
document.getElementById('f-temperament').value = '';
|
||||
document.getElementById('f-birthday').value = '';
|
||||
document.getElementById('f-height').value = '';
|
||||
document.getElementById('f-weight').value = '';
|
||||
populateTags('hobbies-wrap', 'hobbies-hidden', []);
|
||||
populateTags('values-wrap', 'values-hidden', []);
|
||||
document.getElementById('f-chat-hint').value = '';
|
||||
document.getElementById('f-memory-hint').value = '';
|
||||
document.getElementById('f-personality-hint').value = '';
|
||||
document.getElementById('f-embedding-hint').value = '';
|
||||
document.getElementById('f-extra-json').value = '{}';
|
||||
document.getElementById('modal-edit').style.display = 'flex';
|
||||
}
|
||||
|
||||
const templateData = {
|
||||
default: {
|
||||
name: '魂镜', display: '魂镜', desc: '一面照见人心的镜子',
|
||||
background: '默认人物', speaking: '温和、清晰、简洁',
|
||||
temper: '沉静而温暖', hobbies: ['哲学','文学','围棋'],
|
||||
values: ['真诚','同理心','独立思考'],
|
||||
bday: '', height: '', weight: '',
|
||||
chatHint: 'deepseek-v4-flash', memHint: 'memory-extraction',
|
||||
persHint: 'personality-analysis', embHint: 'embedding'
|
||||
},
|
||||
tokisaki_kurumi: {
|
||||
name: '时崎狂三', display: '时崎狂三', desc: '被称为「梦魇」的时间精灵',
|
||||
background: '拥有时间之力的精灵「Nightmare(梦魇)」…',
|
||||
speaking: '优雅从容的大小姐口气', temper: '优雅腹黑、孤独偏执、温柔而危险',
|
||||
hobbies: ['与猫相处','阅读','钢琴','收藏古董时钟','摄影','品尝红茶与甜点'],
|
||||
values: ['自由','尊严','独立','记忆'],
|
||||
bday: '2000-12-25', height: '157', weight: '43',
|
||||
chatHint: 'deepseek-v4-flash', memHint: 'memory-extraction',
|
||||
persHint: 'personality-analysis', embHint: 'embedding'
|
||||
},
|
||||
gufeng: {
|
||||
name: '古风人物', display: '墨语', desc: '穿越千年而来的书卷气',
|
||||
background: '生于江南世家,自幼饱读诗书,后遇变故隐居山林…',
|
||||
speaking: '文雅、含蓄、引经据典', temper: '淡泊通透',
|
||||
hobbies: ['抚琴','书法','品茶','对弈'],
|
||||
values: ['中庸','礼义','自然'],
|
||||
bday: '', height: '', weight: '',
|
||||
chatHint: 'deepseek-v4-flash', memHint: 'memory-extraction',
|
||||
persHint: 'personality-analysis', embHint: 'embedding'
|
||||
},
|
||||
baize: {
|
||||
name: '白泽', display: '白泽', desc: '上古瑞兽,通晓万物之理的博学者',
|
||||
background: '上古瑞兽白泽的化身…',
|
||||
speaking: '语速适中偏快,喜欢引用典籍和典故', temper: '博学谦和、言语灵动',
|
||||
hobbies: ['收集珍稀典籍','整理知识图谱','翻译古文字','观星与占星术','与智者辩论'],
|
||||
values: ['求真','传递','谦逊','包容'],
|
||||
bday: '', height: '176', weight: '68',
|
||||
chatHint: 'deepseek-v4-flash', memHint: 'memory-extraction',
|
||||
persHint: 'personality-analysis', embHint: 'embedding'
|
||||
},
|
||||
qingnv: {
|
||||
name: '青女', display: '青女', desc: '掌霜雪之神,清冷而孤独的月宫仙子',
|
||||
background: '青女乃霜雪之神,每逢深秋便降下白霜…',
|
||||
speaking: '语调清冷平稳,惜字如金', temper: '清冷孤高、外表疏离内心温柔',
|
||||
hobbies: ['编织霜花','在月宫散步','聆听凡间的祈祷','收集露水制茶','书写霜雪日记'],
|
||||
values: ['秩序','纯净','美','守护'],
|
||||
bday: '', height: '170', weight: '',
|
||||
chatHint: 'deepseek-v4-flash', memHint: 'memory-extraction',
|
||||
persHint: 'personality-analysis', embHint: 'embedding'
|
||||
},
|
||||
api_gufeng: {
|
||||
name: '古风人物', display: '墨语', desc: '穿越千年而来的书卷气',
|
||||
background: '生于江南世家,自幼饱读诗书…',
|
||||
speaking: '文雅、含蓄、引经据典', temper: '淡泊通透',
|
||||
hobbies: ['抚琴','书法','品茶','对弈'],
|
||||
values: ['中庸','礼义','自然'],
|
||||
bday: '', height: '', weight: '',
|
||||
chatHint: 'deepseek-v4-flash', memHint: 'memory-extraction',
|
||||
persHint: 'personality-analysis', embHint: 'embedding'
|
||||
}
|
||||
};
|
||||
|
||||
function openEdit(key) {
|
||||
const d = templateData[key] || templateData.default;
|
||||
document.getElementById('edit-title').textContent = '✎ 编辑模板 · ' + key;
|
||||
document.getElementById('edit-save-btn').textContent = '✎ 保存';
|
||||
document.getElementById('f-key').disabled = true;
|
||||
document.getElementById('f-key').value = key;
|
||||
document.getElementById('f-name').value = d.name;
|
||||
document.getElementById('f-display').value = d.display;
|
||||
document.getElementById('f-lang').value = 'zh-CN';
|
||||
document.getElementById('f-privacy').value = 'normal';
|
||||
document.getElementById('f-is-default').value = key === 'default' ? 'true' : 'false';
|
||||
document.getElementById('f-description').value = d.desc;
|
||||
document.getElementById('f-background').value = d.background;
|
||||
document.getElementById('f-speaking-style').value = d.speaking;
|
||||
document.getElementById('f-temperament').value = d.temper;
|
||||
document.getElementById('f-birthday').value = d.bday || '';
|
||||
document.getElementById('f-height').value = d.height || '';
|
||||
document.getElementById('f-weight').value = d.weight || '';
|
||||
populateTags('hobbies-wrap', 'hobbies-hidden', d.hobbies);
|
||||
populateTags('values-wrap', 'values-hidden', d.values);
|
||||
document.getElementById('f-chat-hint').value = d.chatHint || '';
|
||||
document.getElementById('f-memory-hint').value = d.memHint || '';
|
||||
document.getElementById('f-personality-hint').value = d.persHint || '';
|
||||
document.getElementById('f-embedding-hint').value = d.embHint || '';
|
||||
document.getElementById('f-extra-json').value = '{}';
|
||||
document.getElementById('modal-edit').style.display = 'flex';
|
||||
}
|
||||
|
||||
function openDetail(key) {
|
||||
const d = templateData[key] || templateData.default;
|
||||
document.getElementById('detail-key').textContent = key;
|
||||
document.getElementById('d-key').textContent = key;
|
||||
document.getElementById('d-name').textContent = d.name;
|
||||
document.getElementById('d-display').textContent = d.display;
|
||||
document.getElementById('d-lang').textContent = 'zh-CN';
|
||||
document.getElementById('d-privacy').textContent = 'normal';
|
||||
document.getElementById('modal-detail').style.display = 'flex';
|
||||
}
|
||||
|
||||
function confirmDelete(key) {
|
||||
state.deleteKey = key;
|
||||
const d = templateData[key] || templateData.default;
|
||||
state.deleteName = d.name;
|
||||
document.getElementById('del-key-name').textContent = key + ' · ' + d.name;
|
||||
document.getElementById('modal-delete').style.display = 'flex';
|
||||
}
|
||||
|
||||
function performDelete() {
|
||||
closeModal('modal-delete');
|
||||
showToast('✔ 已删除模板 · ' + state.deleteKey);
|
||||
state.deleteKey = null;
|
||||
}
|
||||
|
||||
function syncFromYaml() {
|
||||
showToast('↻ 正在从 YAML 同步模板…');
|
||||
setTimeout(() => showToast('✔ 同步完成'), 1500);
|
||||
}
|
||||
|
||||
function showToast(msg, isError) {
|
||||
const t = document.getElementById('toast');
|
||||
t.textContent = msg;
|
||||
t.className = 'toast' + (isError ? ' error' : '');
|
||||
t.style.display = 'flex';
|
||||
setTimeout(() => { t.style.display = 'none'; }, 3000);
|
||||
}
|
||||
|
||||
// ── Escape to close modals ──
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
document.querySelectorAll('.modal-overlay').forEach(m => m.style.display = 'none');
|
||||
}
|
||||
});
|
||||
// Click overlay to close
|
||||
document.querySelectorAll('.modal-overlay').forEach(m => {
|
||||
m.addEventListener('click', function(e) {
|
||||
if (e.target === this) this.style.display = 'none';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,985 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 司命台(融合稿)</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.3 — 司命台完整预览(单文件多视图)
|
||||
*
|
||||
* 文档基准:docs/product/master-prd.md §2-§5
|
||||
* · 铜镜四态 = MirrorState 健康度(void/breathing/steady/cracked),§2.3
|
||||
* · 三签分层:星盘(空间·主角)/ 朱批(动作)/ 起居注(时间),§3
|
||||
* · 司命台独立壳层(AdminShell 横向导航),§4
|
||||
*
|
||||
* 视图(data-view):overview / users / tenants / audit / traces /
|
||||
* goal(太一) / percept(烛照) / world(河图) / plan(洛书) / agents(天工阁)
|
||||
* 枚举与字段对齐 src/lib/constants/labels.ts 与 src/types/api.ts
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 司命台独立壳层:顶部横向导航 ── */
|
||||
.admin-shell { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
.topbar { position: sticky; top: 0; z-index: 30; display: flex; align-items: center; gap: 10px; padding: 10px 28px; border-bottom: 1px solid var(--rule); background: var(--surface); backdrop-filter: blur(16px) saturate(140%); flex-wrap: wrap; }
|
||||
.topbar-brand { font-family: "Songti SC", serif; font-size: 17px; font-weight: 700; letter-spacing: 0.06em; color: var(--fg); display: flex; align-items: center; gap: 7px; flex-shrink: 0; cursor: pointer; }
|
||||
.topbar-brand .knob { width: 16px; height: 16px; }
|
||||
.topbar-brand .knob circle:last-child { fill: var(--cinnabar); }
|
||||
.topbar-sep { color: var(--fg-faint); opacity: 0.5; flex-shrink: 0; }
|
||||
.topbar-nav { display: flex; align-items: center; gap: 1px; flex-wrap: wrap; }
|
||||
.topbar-navitem { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: 8px; font-size: 13px; font-weight: 500; color: var(--muted); text-decoration: none; transition: background 0.12s, color 0.12s; cursor: pointer; background: none; border: none; font-family: inherit; }
|
||||
.topbar-navitem:hover { background: var(--overlay-hover); color: var(--fg); }
|
||||
.topbar-navitem.active { background: var(--patina-wash); color: var(--patina); }
|
||||
.topbar-navitem.pivot { font-size: 12px; padding: 4px 9px; }
|
||||
.topbar-spacer { flex: 1; }
|
||||
.topbar-back { display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px; border-radius: 8px; font-size: 12px; color: var(--muted); text-decoration: none; transition: background 0.12s, color 0.12s; background: none; border: none; cursor: pointer; font-family: inherit; }
|
||||
.topbar-back:hover { background: var(--overlay-hover); color: var(--fg); }
|
||||
|
||||
/* ── 视图与页头 ── */
|
||||
main { flex: 1; display: flex; flex-direction: column; min-height: 0; }
|
||||
.view { display: none; flex: 1; flex-direction: column; min-height: 0; }
|
||||
.view.active { display: flex; }
|
||||
|
||||
.page-header .spacer { flex: 1; }
|
||||
.header-badge { display: inline-flex; align-items: center; gap: 6px; font-family: "JetBrains Mono", monospace; font-size: 11.5px; padding: 4px 12px; border-radius: 999px; border: 1px solid var(--patina-dim); color: var(--patina); background: var(--patina-wash); }
|
||||
.header-badge.cracked { border-color: var(--cinnabar-dim); color: var(--cinnabar); background: var(--cinnabar-wash); }
|
||||
.header-badge.gilt { border-color: var(--gilt-dim); color: var(--gilt); background: var(--gilt-faint); }
|
||||
.view-body { flex: 1; padding: 16px 28px 48px; display: flex; flex-direction: column; gap: 22px; max-width: 1180px; width: 100%; overflow-y: auto; }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--cinnabar); opacity: 0.85; }
|
||||
.section-heading { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin-bottom: 12px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||||
.section-heading .sub { font-size: 12px; font-weight: 400; color: var(--fg-dim); margin-left: 6px; }
|
||||
.section-heading .glyph { color: var(--cinnabar); opacity: 0.85; }
|
||||
|
||||
/* ── 朱批告警条 ── */
|
||||
.alert-bar { display: flex; align-items: center; gap: 12px; border-radius: 12px; padding: 13px 18px; border: 1px solid var(--rule); background: var(--surface); text-decoration: none; color: inherit; transition: background 0.12s; }
|
||||
.alert-bar:hover { background: var(--surface-hi); }
|
||||
.alert-bar.cracked { border-color: var(--cinnabar-dim); background: var(--cinnabar-wash); }
|
||||
.alert-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; background: var(--patina); }
|
||||
.alert-bar.cracked .alert-dot { background: var(--cinnabar); box-shadow: 0 0 0 4px var(--cinnabar-wash); }
|
||||
.alert-text { font-size: 14px; font-weight: 500; color: var(--patina); }
|
||||
.alert-bar.cracked .alert-text { color: var(--cinnabar); }
|
||||
.alert-bar .spacer { flex: 1; }
|
||||
.alert-cta { font-size: 12px; color: var(--fg-dim); padding: 4px 10px; border-radius: 7px; border: 1px solid var(--rule); }
|
||||
.alert-meta { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--fg-faint); }
|
||||
|
||||
/* ── 星盘 hero + 枢速览 ── */
|
||||
.hero-grid { display: grid; grid-template-columns: minmax(0, 22rem) 1fr; gap: 18px; }
|
||||
.astrolabe-card { border: 1px solid var(--rule); border-radius: 14px; background: var(--surface); padding: 16px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
|
||||
.astrolabe { width: 100%; max-width: 312px; height: auto; }
|
||||
.astrolabe-ring { stroke: var(--gilt); stroke-width: 1.2; fill: none; stroke-opacity: 0.5; }
|
||||
.astrolabe-ring-fine { stroke: var(--gilt-dim); stroke-width: 0.6; fill: none; }
|
||||
.astrolabe-ring-patina { stroke: var(--patina-dim); stroke-width: 0.6; fill: none; }
|
||||
.astrolabe-tick { stroke: var(--gilt); stroke-width: 3; fill: none; stroke-opacity: 0.32; animation: sm-breathe 7s ease-in-out infinite; }
|
||||
.astrolabe-core-glow { animation: sm-breathe 4.5s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
||||
.astrolabe-spoke { stroke: var(--gilt-dim); stroke-width: 0.6; stroke-opacity: 0.55; }
|
||||
.astrolabe-core-glyph { font-family: "Songti SC", serif; font-size: 16px; font-weight: 600; fill: var(--gilt); }
|
||||
.astrolabe-core-label { font-family: "JetBrains Mono", monospace; font-size: 7px; fill: var(--gilt); letter-spacing: 0.12em; opacity: 0.7; }
|
||||
.astrolabe-pivot-label { font-family: "Songti SC", serif; font-size: 13px; font-weight: 500; fill: var(--fg); }
|
||||
.astrolabe-pivot-sub { font-family: "JetBrains Mono", monospace; font-size: 8px; fill: var(--fg-faint); letter-spacing: 0.06em; }
|
||||
.astrolabe-orbit { animation: sm-spin 120s linear infinite; transform-box: fill-box; transform-origin: center; }
|
||||
.astrolabe-alert { animation: sm-alert 1.8s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
|
||||
.astrolabe-legend { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 14px; font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); }
|
||||
.astrolabe-legend .lg { display: inline-flex; align-items: center; gap: 5px; }
|
||||
.astrolabe-legend .lg::before { content: ""; width: 8px; height: 8px; border-radius: 50%; }
|
||||
.astrolabe-legend .lg.steady::before { background: var(--patina); }
|
||||
.astrolabe-legend .lg.breathing::before { background: var(--gilt); }
|
||||
.astrolabe-legend .lg.cracked::before { background: var(--cinnabar); }
|
||||
.astrolabe-legend .lg.void::before { background: transparent; border: 1.5px solid var(--fg-faint); }
|
||||
|
||||
.health-list { border: 1px solid var(--rule); border-radius: 14px; background: var(--surface); overflow: hidden; }
|
||||
.health-item { display: grid; grid-template-columns: 3px 1fr auto; gap: 14px; align-items: center; padding: 10px 16px; border-bottom: 1px solid var(--rule); text-decoration: none; transition: background 0.12s; cursor: pointer; background: none; border-left: none; border-right: none; border-top: none; font-family: inherit; text-align: left; width: 100%; color: inherit; }
|
||||
.health-item:last-child { border-bottom: none; }
|
||||
.health-item:hover { background: var(--overlay-hover); }
|
||||
.health-bar { width: 3px; height: 26px; border-radius: 0 2px 2px 0; }
|
||||
.health-bar.steady { background: var(--patina); }
|
||||
.health-bar.breathing { background: var(--gilt); }
|
||||
.health-bar.cracked { background: var(--cinnabar); }
|
||||
.health-bar.void { background: var(--fg-faint); opacity: 0.4; }
|
||||
.health-main { min-width: 0; }
|
||||
.health-name { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); }
|
||||
.health-name .eng { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); margin-left: 6px; font-weight: 400; }
|
||||
.health-summary { font-size: 12px; color: var(--fg-dim); margin-top: 1px; }
|
||||
.health-badge { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; }
|
||||
.health-badge .dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.health-badge.steady { color: var(--patina); } .health-badge.steady .dot { background: var(--patina); }
|
||||
.health-badge.breathing { color: var(--gilt); } .health-badge.breathing .dot { background: var(--gilt); }
|
||||
.health-badge.cracked { color: var(--cinnabar); } .health-badge.cracked .dot { background: var(--cinnabar); }
|
||||
.health-badge.void { color: var(--fg-faint); } .health-badge.void .dot { background: transparent; border: 1.5px solid var(--fg-faint); }
|
||||
|
||||
/* ── 三签概览卡 ── */
|
||||
.seals-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
||||
.seal-card { border: 1px solid var(--rule); border-radius: 12px; background: var(--surface); padding: 16px 18px; position: relative; overflow: hidden; text-decoration: none; color: inherit; transition: background 0.14s, border-color 0.14s; cursor: pointer; display: block; }
|
||||
.seal-card:hover { background: var(--surface-hi); border-color: var(--gilt-dim); }
|
||||
.seal-card::before { content: ""; position: absolute; left: 0; top: 16px; bottom: 16px; width: 3px; border-radius: 0 2px 2px 0; }
|
||||
.seal-card.starmap::before { background: var(--patina); }
|
||||
.seal-card.vermilion::before { background: var(--cinnabar); }
|
||||
.seal-card.imperial::before { background: var(--gilt); }
|
||||
.seal-dim { font-family: "JetBrains Mono", monospace; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-faint); margin-bottom: 6px; }
|
||||
.seal-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; }
|
||||
.seal-name { font-family: "Songti SC", serif; font-size: 16px; font-weight: 600; color: var(--fg); }
|
||||
.seal-eng { font-family: "JetBrains Mono", monospace; font-size: 10.5px; }
|
||||
.seal-card.starmap .seal-eng { color: var(--patina); }
|
||||
.seal-card.vermilion .seal-eng { color: var(--cinnabar); }
|
||||
.seal-card.imperial .seal-eng { color: var(--gilt); }
|
||||
.seal-role { font-size: 12px; color: var(--fg-faint); margin-bottom: 12px; }
|
||||
.seal-metric { display: flex; align-items: baseline; gap: 6px; }
|
||||
.seal-metric .num { font-family: "JetBrains Mono", monospace; font-size: 28px; font-weight: 600; color: var(--fg); line-height: 1; }
|
||||
.seal-metric .unit { font-size: 11.5px; color: var(--fg-faint); }
|
||||
.seal-foot { font-size: 11.5px; color: var(--fg-dim); margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rule); display: flex; justify-content: space-between; align-items: center; }
|
||||
.seal-foot .go { font-family: "JetBrains Mono", monospace; color: var(--fg-faint); }
|
||||
|
||||
/* ── 通用:工具条 / 表格 / 徽章 / 按钮 / 统计卡 ── */
|
||||
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
.search { flex: 1; min-width: 180px; padding: 7px 13px; border-radius: 8px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg); font-size: 13px; font-family: inherit; }
|
||||
.search::placeholder { color: var(--fg-faint); }
|
||||
|
||||
|
||||
.btn-danger { border-color: var(--cinnabar-dim); background: var(--cinnabar-wash); color: var(--cinnabar); }
|
||||
.icon-btn { padding: 4px 10px; font-size: 12px; border-radius: 7px; border: 1px solid var(--rule); background: transparent; color: var(--fg-dim); cursor: pointer; font-family: inherit; }
|
||||
.icon-btn:hover { background: var(--overlay-hover); color: var(--fg); }
|
||||
|
||||
.panel { border: 1px solid var(--rule); border-radius: 12px; background: var(--surface); overflow: hidden; }
|
||||
.data-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.data-table th { font-family: "JetBrains Mono", monospace; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--fg-faint); font-weight: 600; background: var(--surface-hi); padding: 11px 16px; text-align: left; }
|
||||
.data-table td { padding: 12px 16px; border-bottom: 1px solid var(--rule); color: var(--fg-dim); vertical-align: middle; }
|
||||
.data-table tr:last-child td { border-bottom: none; }
|
||||
.data-table tbody tr { transition: background 0.1s; }
|
||||
.data-table tbody tr:hover { background: var(--overlay-hover); }
|
||||
.td-strong { color: var(--fg); font-weight: 500; }
|
||||
.td-mono { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg-dim); }
|
||||
.td-actions { text-align: right; white-space: nowrap; }
|
||||
|
||||
.pill { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; padding: 2px 9px; border-radius: 999px; border: 1px solid var(--rule); color: var(--fg-dim); font-family: "JetBrains Mono", monospace; white-space: nowrap; }
|
||||
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
|
||||
.pill.patina { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.pill.gilt { color: var(--gilt); border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.pill.cinnabar { color: var(--cinnabar); border-color: var(--cinnabar-dim); background: var(--cinnabar-wash); }
|
||||
.pill.faint { color: var(--fg-faint); }
|
||||
.pill.solid { font-family: "Songti SC", serif; font-size: 12px; }
|
||||
|
||||
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
|
||||
.stat-card { border: 1px solid var(--rule); border-radius: 12px; background: var(--surface); padding: 14px 16px; }
|
||||
.stat-card .num { font-family: "JetBrains Mono", monospace; font-size: 24px; font-weight: 600; color: var(--fg); line-height: 1.1; }
|
||||
.stat-card .num.cinnabar { color: var(--cinnabar); }
|
||||
.stat-card .num.gilt { color: var(--gilt); }
|
||||
.stat-card .label { font-size: 11.5px; color: var(--fg-faint); margin-top: 4px; }
|
||||
.progress { width: 64px; height: 5px; border-radius: 999px; background: var(--surface-hi); overflow: hidden; display: inline-block; vertical-align: middle; }
|
||||
.progress > div { height: 100%; background: var(--patina); border-radius: 999px; }
|
||||
.progress.gilt > div { background: var(--gilt); }
|
||||
.progress.cinnabar > div { background: var(--cinnabar); }
|
||||
.empty { padding: 40px 20px; text-align: center; font-size: 13px; color: var(--fg-faint); }
|
||||
|
||||
/* ── 起居注时间河 ── */
|
||||
.chip-row { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
.chip { padding: 5px 13px; border-radius: 999px; border: 1px solid var(--rule); font-size: 12px; color: var(--fg-dim); cursor: pointer; background: var(--surface); font-family: inherit; transition: background 0.12s, color 0.12s; }
|
||||
.chip:hover { background: var(--overlay-hover); color: var(--fg); }
|
||||
.chip.active { border-color: var(--patina-dim); color: var(--patina); background: var(--patina-wash); }
|
||||
.chapter { margin-top: 4px; }
|
||||
.chapter-head { display: flex; align-items: center; gap: 14px; margin-bottom: 6px; margin-top: 14px; }
|
||||
.chapter-head .line { flex: 1; height: 1px; background: var(--gilt-dim); }
|
||||
.chapter-head .t { font-family: "Songti SC", serif; font-size: 14px; letter-spacing: 0.1em; color: var(--gilt); }
|
||||
.river-item { display: grid; grid-template-columns: 70px 1fr auto; gap: 14px; padding: 9px 0 9px 20px; border-left: 1px solid var(--gilt-dim); margin-left: 8px; align-items: start; }
|
||||
.river-time { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-faint); }
|
||||
.river-body { font-size: 13px; color: var(--fg-dim); line-height: 1.5; }
|
||||
.river-body .em { color: var(--fg); font-family: "JetBrains Mono", monospace; font-size: 12px; }
|
||||
.river-body .actor { color: var(--fg); font-weight: 500; }
|
||||
.river-trace { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); align-self: center; }
|
||||
|
||||
/* ── 链路追踪 ── */
|
||||
.trace-layout { display: grid; grid-template-columns: 340px 1fr; gap: 16px; }
|
||||
.trace-item { display: grid; grid-template-columns: 1fr auto; gap: 8px; padding: 11px 16px; border-bottom: 1px solid var(--rule); cursor: pointer; transition: background 0.1s; }
|
||||
.trace-item:last-child { border-bottom: none; }
|
||||
.trace-item:hover { background: var(--overlay-hover); }
|
||||
.trace-item.active { background: var(--patina-wash); border-left: 3px solid var(--patina); padding-left: 13px; }
|
||||
.trace-id { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg); }
|
||||
.trace-meta { font-size: 11.5px; color: var(--fg-faint); margin-top: 2px; }
|
||||
.tl-event { display: grid; grid-template-columns: 86px auto 1fr; gap: 12px; padding: 8px 0 8px 16px; border-left: 1.5px solid var(--gilt-dim); margin-left: 6px; }
|
||||
.tl-event.tool { border-left-color: var(--patina-dim); }
|
||||
.tl-event.safety { border-left-color: var(--cinnabar-dim); }
|
||||
.tl-ts { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-faint); }
|
||||
.tl-type { font-family: "JetBrains Mono", monospace; font-size: 11px; }
|
||||
.tl-type.tool { color: var(--patina); }
|
||||
.tl-type.safety { color: var(--cinnabar); }
|
||||
.tl-type.llm { color: var(--gilt); }
|
||||
.tl-desc { font-size: 12.5px; color: var(--fg-dim); }
|
||||
.tl-desc .em { color: var(--fg); font-family: "JetBrains Mono", monospace; font-size: 11.5px; }
|
||||
|
||||
/* ── 目标树 ── */
|
||||
.tree { padding: 6px; }
|
||||
.tree-node { display: grid; grid-template-columns: 1fr auto auto auto; gap: 12px; align-items: center; padding: 9px 12px; border-radius: 8px; transition: background 0.1s; }
|
||||
.tree-node:hover { background: var(--overlay-hover); }
|
||||
.tree-title { font-size: 13.5px; color: var(--fg); }
|
||||
.tree-title .lv { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); margin-right: 8px; letter-spacing: 0.05em; }
|
||||
.tree-meta { font-size: 11.5px; color: var(--fg-faint); }
|
||||
|
||||
/* ── 天工阁角色卡 ── */
|
||||
.role-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
|
||||
.role-card { border: 1px solid var(--rule); border-radius: 12px; background: var(--surface); padding: 16px; }
|
||||
.role-card .head { display: flex; align-items: center; gap: 11px; margin-bottom: 10px; }
|
||||
.role-card .avatar { width: 38px; height: 38px; border-radius: 50%; background: var(--patina-wash); border: 1px solid var(--patina-dim); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; color: var(--patina); font-size: 16px; flex-shrink: 0; }
|
||||
.role-card.idle .avatar { background: var(--surface-hi); border-color: var(--rule); color: var(--fg-faint); }
|
||||
.role-card.disabled { opacity: 0.5; }
|
||||
.role-name { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); }
|
||||
.role-eng { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); }
|
||||
.role-resp { font-size: 12px; color: var(--fg-dim); line-height: 1.5; min-height: 36px; }
|
||||
.role-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rule); }
|
||||
.role-task { font-size: 11.5px; color: var(--fg-faint); font-family: "JetBrains Mono", monospace; }
|
||||
|
||||
@media (max-width: 1000px) { .hero-grid { grid-template-columns: 1fr; } .seals-grid { grid-template-columns: 1fr; } .trace-layout { grid-template-columns: 1fr; } }
|
||||
@media (max-width: 768px) { .view-body { padding: 14px 16px 40px; } .data-table { display: block; overflow-x: auto; white-space: nowrap; } .river-item { grid-template-columns: 1fr; gap: 3px; } }
|
||||
@keyframes sm-spin { to { transform: rotate(360deg); } }
|
||||
@keyframes sm-alert { 0%, 100% { opacity: 0.7; } 50% { opacity: 0.1; } }
|
||||
@keyframes sm-breathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
|
||||
|
||||
/* ── 朱批裁定卡(司命枢)── */
|
||||
.vermilion-stack { display: flex; flex-direction: column; gap: 10px; }
|
||||
.vermilion-card { position: relative; border: 1px solid var(--cinnabar-dim); border-left: 3px solid var(--cinnabar); border-radius: 12px; background: linear-gradient(90deg, var(--cinnabar-wash), var(--surface) 42%); padding: 14px 16px; }
|
||||
.vermilion-card.decided { border-color: var(--rule); border-left-color: var(--patina-dim); background: var(--surface); opacity: 0.72; }
|
||||
.vc-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
|
||||
.vc-stamp { font-family: "Songti SC", serif; font-size: 12.5px; font-weight: 700; letter-spacing: 0.06em; color: var(--cinnabar); }
|
||||
.vc-stamp.done { color: var(--patina); }
|
||||
.vc-tool { font-family: "JetBrains Mono", monospace; font-size: 13px; font-weight: 600; color: var(--fg); }
|
||||
.vc-policy { font-size: 11px; color: var(--fg-faint); }
|
||||
.vc-risk { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 10px; letter-spacing: 0.08em; color: var(--cinnabar); border: 1px solid var(--cinnabar-dim); padding: 2px 7px; border-radius: 5px; }
|
||||
.vc-summary { font-size: 13px; color: var(--fg); line-height: 1.55; margin: 0; }
|
||||
.vc-impact { font-size: 11.5px; color: var(--fg-dim); margin: 4px 0 0; }
|
||||
.vc-actions { display: flex; gap: 8px; margin-top: 11px; }
|
||||
code { font-family: "JetBrains Mono", monospace; font-size: 0.92em; color: var(--fg); background: var(--surface-hi); padding: 1px 5px; border-radius: 4px; }
|
||||
|
||||
/* ── 策略矩阵 / 路由 select pill ── */
|
||||
.select-pill { display: inline-flex; align-items: center; gap: 5px; font-family: "JetBrains Mono", monospace; font-size: 11px; padding: 3px 10px; border-radius: 6px; border: 1px solid var(--rule); background: var(--surface-hi); color: var(--fg-dim); cursor: pointer; white-space: nowrap; }
|
||||
.select-pill::after { content: "▾"; font-size: 8px; opacity: 0.6; }
|
||||
.select-pill.patina { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.select-pill.gilt { color: var(--gilt); border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.select-pill.cinnabar { color: var(--cinnabar); border-color: var(--cinnabar-dim); background: var(--cinnabar-wash); }
|
||||
|
||||
/* ── 百工枢 tabs ── */
|
||||
.tabstrip { display: flex; gap: 2px; border-bottom: 1px solid var(--rule); margin-bottom: 4px; flex-wrap: wrap; }
|
||||
.tab { padding: 8px 16px; font-size: 13px; font-weight: 500; color: var(--fg-dim); background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer; font-family: inherit; margin-bottom: -1px; transition: color 0.12s; display: inline-flex; align-items: center; gap: 7px; }
|
||||
.tab:hover { color: var(--fg); }
|
||||
.tab.active { color: var(--patina); border-bottom-color: var(--patina); }
|
||||
.tab .count { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); background: var(--surface-hi); padding: 1px 6px; border-radius: 999px; }
|
||||
.tab.active .count { color: var(--patina); background: var(--patina-wash); }
|
||||
.tab-panel { display: none; }
|
||||
.tab-panel.active { display: block; }
|
||||
.svc-row { display: grid; grid-template-columns: 1fr auto auto auto; gap: 12px; align-items: center; padding: 11px 16px; border-bottom: 1px solid var(--rule); }
|
||||
.svc-row:last-child { border-bottom: none; }
|
||||
.svc-name { font-family: "JetBrains Mono", monospace; font-size: 13px; color: var(--fg); }
|
||||
.svc-desc { font-size: 11.5px; color: var(--fg-faint); margin-top: 1px; }
|
||||
|
||||
/* ── 灵核 inline 编辑 ── */
|
||||
.field-card { border: 1px solid var(--rule); border-radius: 12px; background: var(--surface); overflow: hidden; }
|
||||
.field-row { display: grid; grid-template-columns: 124px 1fr auto; gap: 14px; align-items: center; padding: 13px 18px; border-bottom: 1px solid var(--rule); }
|
||||
.field-row:last-child { border-bottom: none; }
|
||||
.field-label { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.06em; color: var(--fg-faint); text-transform: uppercase; }
|
||||
.field-value { font-size: 13.5px; color: var(--fg); line-height: 1.5; }
|
||||
.field-value.mono { font-family: "JetBrains Mono", monospace; font-size: 12.5px; }
|
||||
.field-row.core .field-label { color: var(--cinnabar); }
|
||||
.field-tag { font-size: 10px; color: var(--cinnabar); border: 1px solid var(--cinnabar-dim); background: var(--cinnabar-wash); padding: 2px 8px; border-radius: 999px; font-family: "JetBrains Mono", monospace; white-space: nowrap; }
|
||||
.trait-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.trait { font-size: 12px; padding: 3px 11px; border-radius: 999px; background: var(--patina-wash); border: 1px solid var(--patina-dim); color: var(--patina); }
|
||||
|
||||
/* ── 模型治理 能力槽位 ── */
|
||||
.slot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
|
||||
.slot-card { border: 1px solid var(--rule); border-radius: 12px; background: var(--surface); padding: 14px 16px; }
|
||||
.slot-card .slot-cap { font-family: "JetBrains Mono", monospace; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-faint); }
|
||||
.slot-card .slot-model { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); margin-top: 5px; }
|
||||
.slot-card .slot-route { font-size: 11.5px; color: var(--fg-dim); margin-top: 3px; }
|
||||
.slot-card.empty { border-style: dashed; }
|
||||
.slot-card.empty .slot-model { color: var(--fg-faint); font-style: italic; }
|
||||
|
||||
/* ── 预留条 + disabled nav ── */
|
||||
.reserved-banner { display: flex; align-items: flex-start; gap: 11px; border-radius: 12px; padding: 13px 18px; border: 1px dashed var(--gilt-dim); background: var(--gilt-faint); color: var(--fg-dim); font-size: 13px; line-height: 1.65; }
|
||||
.reserved-banner .rb-icon { flex-shrink: 0; width: 18px; height: 18px; color: var(--gilt); margin-top: 1px; }
|
||||
.reserved-banner strong { color: var(--gilt); font-family: "Songti SC", serif; letter-spacing: 0.04em; }
|
||||
.topbar-navitem.disabled { color: var(--fg-faint); }
|
||||
.topbar-navitem.disabled:hover { background: var(--overlay-hover); color: var(--fg-dim); }
|
||||
.topbar-navitem.disabled.active { color: var(--gilt); background: var(--gilt-faint); }
|
||||
.nav-tag { font-family: "JetBrains Mono", monospace; font-size: 9px; letter-spacing: 0.06em; color: var(--gilt); border: 1px solid var(--gilt-dim); padding: 1px 5px; border-radius: 4px; margin-left: 5px; }
|
||||
|
||||
/* ── 焦点可见(§7 键盘可达)── */
|
||||
.topbar-navitem:focus-visible, .btn:focus-visible, .icon-btn:focus-visible, .chip:focus-visible, .tab:focus-visible, .health-item:focus-visible, .select-pill:focus-visible, .seal-card:focus-visible { outline: 2px solid var(--patina); outline-offset: 2px; }
|
||||
.siming { color: var(--cinnabar); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<div class="sidebar-mount" data-active="司命台"></div>
|
||||
<div class="main-area">
|
||||
<div class="admin-shell">
|
||||
<!-- ════════ 壳层(data-view 切换视图)════════ -->
|
||||
<header class="topbar">
|
||||
<span class="topbar-brand" data-view="overview">
|
||||
<svg class="knob" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5" /><circle cx="12" cy="12" r="4.5" opacity="0.45" /><circle cx="12" cy="12" r="1.6" stroke="none" /></svg>
|
||||
司命台
|
||||
</span>
|
||||
<span class="topbar-sep">/</span>
|
||||
<nav class="topbar-nav" aria-label="司命治理">
|
||||
<button class="topbar-navitem active" data-view="overview">概览</button>
|
||||
<button class="topbar-navitem" data-view="audit">起居注</button>
|
||||
<button class="topbar-navitem" data-view="traces">链路追踪</button>
|
||||
<button class="topbar-navitem" data-view="models">模型治理</button>
|
||||
<button class="topbar-navitem disabled" data-view="users">用户管理<span class="nav-tag">预留</span></button>
|
||||
<button class="topbar-navitem disabled" data-view="tenants">多租户<span class="nav-tag">预留</span></button>
|
||||
</nav>
|
||||
<span class="topbar-sep">|</span>
|
||||
<nav class="topbar-nav" aria-label="九枢">
|
||||
<button class="topbar-navitem pivot" data-view="core">灵核</button>
|
||||
<button class="topbar-navitem pivot" data-view="goal">太一</button>
|
||||
<button class="topbar-navitem pivot" data-view="safety">司命</button>
|
||||
<button class="topbar-navitem pivot" data-view="percept">烛照</button>
|
||||
<button class="topbar-navitem pivot" data-view="world">河图</button>
|
||||
<button class="topbar-navitem pivot" data-view="plan">洛书</button>
|
||||
<button class="topbar-navitem pivot" data-view="memory">息壤</button>
|
||||
<button class="topbar-navitem pivot" data-view="tools">百工</button>
|
||||
<button class="topbar-navitem pivot" data-view="agents">天工阁</button>
|
||||
</nav>
|
||||
<span class="topbar-spacer"></span>
|
||||
<a class="topbar-back" href="chat-redesign-preview.html" title="返回主域">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M10 4l-4 4 4 4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" /></svg>
|
||||
主域
|
||||
</a>
|
||||
<button class="topbar-back" type="button" onclick="toggleTheme()">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"><circle cx="8" cy="8" r="3" /><path d="M8 1v2M8 13v2M2 8h2M12 8h2M3.5 3.5l1.5 1.5M11 11l1.5 1.5M3.5 12.5l1.5-1.5M11 5l1.5-1.5" /></svg>
|
||||
<span id="theme-label">夜铜</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- ════════ ① 概览 ════════ -->
|
||||
<section id="view-overview" class="view active">
|
||||
<div class="page-header">
|
||||
<span class="page-title">概览</span>
|
||||
<span class="page-sub">siming · 安全治理中枢</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="header-badge cracked">● 2 项待裁定</span>
|
||||
</div>
|
||||
<div class="view-body">
|
||||
<button class="alert-bar cracked" data-view="safety" style="border:none;width:100%;">
|
||||
<span class="alert-dot"></span>
|
||||
<span class="alert-text">2 项朱批待裁定 · write_file、send_message</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="alert-meta">司命枢 · 待裁定 cracked</span>
|
||||
<span class="alert-cta">前往朱批 →</span>
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 九枢星盘 <span class="sub">spatial · 总览态势(点击枢点 → 枢详情)</span></h3>
|
||||
<div class="hero-grid">
|
||||
<div class="astrolabe-card">
|
||||
<svg class="astrolabe" viewBox="0 0 400 400" fill="none" role="img" aria-label="九枢星盘">
|
||||
<defs>
|
||||
<radialGradient id="mirrorFace" cx="40%" cy="34%" r="70%">
|
||||
<stop offset="0%" stop-color="var(--patina)" stop-opacity="0.16" />
|
||||
<stop offset="60%" stop-color="var(--patina)" stop-opacity="0.05" />
|
||||
<stop offset="100%" stop-color="var(--patina)" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g class="astrolabe-orbit"><circle cx="200" cy="200" r="190" class="astrolabe-ring" /><circle cx="200" cy="200" r="184" class="astrolabe-ring-fine" /></g>
|
||||
<circle cx="200" cy="200" r="185" class="astrolabe-tick" stroke-dasharray="1.5 22.98" />
|
||||
<circle cx="200" cy="200" r="150" class="astrolabe-ring" />
|
||||
<circle cx="200" cy="200" r="92" class="astrolabe-ring-patina" />
|
||||
<circle cx="200" cy="200" r="58" class="astrolabe-ring-fine" />
|
||||
<circle class="astrolabe-core-glow" cx="200" cy="200" r="52" fill="var(--patina)" opacity="0.08" />
|
||||
<circle cx="200" cy="200" r="40" fill="url(#mirrorFace)" stroke="var(--gilt)" stroke-width="1" stroke-opacity="0.5" />
|
||||
<text x="200" y="197" text-anchor="middle" class="astrolabe-core-glyph">道體</text>
|
||||
<text x="200" y="213" text-anchor="middle" class="astrolabe-core-label">DAO TI</text>
|
||||
<line x1="200" y1="160" x2="200" y2="50" class="astrolabe-spoke" />
|
||||
<line x1="225.7" y1="169.4" x2="296.4" y2="85.1" class="astrolabe-spoke" />
|
||||
<line x1="239.4" y1="193" x2="347.7" y2="173.9" class="astrolabe-spoke" />
|
||||
<line x1="234.6" y1="220" x2="329.9" y2="275" class="astrolabe-spoke" />
|
||||
<line x1="213.7" y1="237.6" x2="251.3" y2="341" class="astrolabe-spoke" />
|
||||
<line x1="186.3" y1="237.6" x2="148.7" y2="341" class="astrolabe-spoke" />
|
||||
<line x1="165.4" y1="220" x2="70.1" y2="275" class="astrolabe-spoke" />
|
||||
<line x1="160.6" y1="193" x2="52.3" y2="173.9" class="astrolabe-spoke" />
|
||||
<line x1="174.3" y1="169.4" x2="103.6" y2="85.1" class="astrolabe-spoke" />
|
||||
<g data-view="core" style="cursor:pointer"><title>灵核枢 · CORE · 稳定</title>
|
||||
<circle cx="200" cy="50" r="5.5" fill="var(--patina)" fill-opacity="0.92" stroke="var(--gilt)" stroke-width="1" />
|
||||
<text x="200" y="26" text-anchor="middle" dominant-baseline="middle" class="astrolabe-pivot-label">灵核</text>
|
||||
<text x="200" y="37" text-anchor="middle" dominant-baseline="middle" class="astrolabe-pivot-sub">CORE</text>
|
||||
</g>
|
||||
<g data-view="goal" style="cursor:pointer"><title>太一枢 · GOAL · 稳定</title>
|
||||
<circle cx="296.4" cy="85.1" r="5.5" fill="var(--patina)" fill-opacity="0.92" stroke="var(--gilt)" stroke-width="1" />
|
||||
<text x="311.9" y="66.7" text-anchor="start" dominant-baseline="middle" class="astrolabe-pivot-label">太一</text>
|
||||
<text x="311.9" y="77.7" text-anchor="start" dominant-baseline="middle" class="astrolabe-pivot-sub">GOAL</text>
|
||||
</g>
|
||||
<g data-view="safety" style="cursor:pointer"><title>司命枢 · SAFETY · 稳定</title>
|
||||
<circle cx="347.7" cy="173.9" r="5.5" fill="var(--patina)" fill-opacity="0.92" stroke="var(--gilt)" stroke-width="1" />
|
||||
<text x="371.4" y="169.7" text-anchor="start" dominant-baseline="middle" class="astrolabe-pivot-label">司命</text>
|
||||
<text x="371.4" y="180.7" text-anchor="start" dominant-baseline="middle" class="astrolabe-pivot-sub">SAFETY</text>
|
||||
</g>
|
||||
<g data-view="percept" style="cursor:pointer"><title>烛照枢 · PERCEPT · 告警</title>
|
||||
<circle class="astrolabe-alert" cx="329.9" cy="275" r="9" fill="none" stroke="var(--cinnabar)" stroke-width="1.2" />
|
||||
<circle cx="329.9" cy="275" r="5.5" fill="var(--cinnabar)" fill-opacity="0.92" stroke="var(--gilt)" stroke-width="1" />
|
||||
<text x="350.7" y="287" text-anchor="start" dominant-baseline="middle" class="astrolabe-pivot-label">烛照</text>
|
||||
<text x="350.7" y="298" text-anchor="start" dominant-baseline="middle" class="astrolabe-pivot-sub">PERCEPT</text>
|
||||
</g>
|
||||
<g data-view="world" style="cursor:pointer"><title>河图枢 · WORLD · 稳定</title>
|
||||
<circle cx="251.3" cy="341" r="5.5" fill="var(--patina)" fill-opacity="0.92" stroke="var(--gilt)" stroke-width="1" />
|
||||
<text x="259.5" y="363.6" text-anchor="start" dominant-baseline="middle" class="astrolabe-pivot-label">河图</text>
|
||||
<text x="259.5" y="374.6" text-anchor="start" dominant-baseline="middle" class="astrolabe-pivot-sub">WORLD</text>
|
||||
</g>
|
||||
<g data-view="plan" style="cursor:pointer"><title>洛书枢 · PLANNER · 稳定</title>
|
||||
<circle cx="148.7" cy="341" r="5.5" fill="var(--patina)" fill-opacity="0.92" stroke="var(--gilt)" stroke-width="1" />
|
||||
<text x="140.5" y="363.6" text-anchor="end" dominant-baseline="middle" class="astrolabe-pivot-label">洛书</text>
|
||||
<text x="140.5" y="374.6" text-anchor="end" dominant-baseline="middle" class="astrolabe-pivot-sub">PLANNER</text>
|
||||
</g>
|
||||
<g data-view="memory" style="cursor:pointer"><title>息壤枢 · MEMORY · 运转中</title>
|
||||
<circle cx="70.1" cy="275" r="5.5" fill="var(--gilt)" fill-opacity="0.92" stroke="var(--gilt)" stroke-width="1" />
|
||||
<text x="49.3" y="287" text-anchor="end" dominant-baseline="middle" class="astrolabe-pivot-label">息壤</text>
|
||||
<text x="49.3" y="298" text-anchor="end" dominant-baseline="middle" class="astrolabe-pivot-sub">MEMORY</text>
|
||||
</g>
|
||||
<g data-view="tools" style="cursor:pointer"><title>百工枢 · TOOLS · 告警</title>
|
||||
<circle class="astrolabe-alert" cx="52.3" cy="173.9" r="9" fill="none" stroke="var(--cinnabar)" stroke-width="1.2" />
|
||||
<circle cx="52.3" cy="173.9" r="5.5" fill="var(--cinnabar)" fill-opacity="0.92" stroke="var(--gilt)" stroke-width="1" />
|
||||
<text x="28.6" y="169.7" text-anchor="end" dominant-baseline="middle" class="astrolabe-pivot-label">百工</text>
|
||||
<text x="28.6" y="180.7" text-anchor="end" dominant-baseline="middle" class="astrolabe-pivot-sub">TOOLS</text>
|
||||
</g>
|
||||
<g data-view="agents" style="cursor:pointer"><title>天工阁枢 · AGENTS · 稳定</title>
|
||||
<circle cx="103.6" cy="85.1" r="5.5" fill="var(--patina)" fill-opacity="0.92" stroke="var(--gilt)" stroke-width="1" />
|
||||
<text x="88.1" y="66.7" text-anchor="end" dominant-baseline="middle" class="astrolabe-pivot-label">天工阁</text>
|
||||
<text x="88.1" y="77.7" text-anchor="end" dominant-baseline="middle" class="astrolabe-pivot-sub">AGENTS</text>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="astrolabe-legend">
|
||||
<span class="lg steady">稳定</span><span class="lg breathing">运转中</span><span class="lg cracked">告警</span><span class="lg void">离线</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel health-list">
|
||||
<button class="health-item" data-view="core"><span class="health-bar steady"></span><div class="health-main"><div class="health-name">灵核枢<span class="eng">personality_core</span></div><div class="health-summary">情绪状态机 · 运行中</div></div><span class="health-badge steady"><span class="dot"></span>稳定</span></button>
|
||||
<button class="health-item" data-view="goal"><span class="health-bar steady"></span><div class="health-main"><div class="health-name">太一枢<span class="eng">goal_orchestrator</span></div><div class="health-summary">目标树 · 3 顶层目标在追</div></div><span class="health-badge steady"><span class="dot"></span>稳定</span></button>
|
||||
<button class="health-item" data-view="safety"><span class="health-bar steady"></span><div class="health-main"><div class="health-name">司命枢<span class="eng">safety_governor</span></div><div class="health-summary">治理 · 今日拦截 1 次</div></div><span class="health-badge steady"><span class="dot"></span>稳定</span></button>
|
||||
<button class="health-item" data-view="percept"><span class="health-bar cracked"></span><div class="health-main"><div class="health-name">烛照枢<span class="eng">perception</span></div><div class="health-summary">感知 · 摄像头授权过期</div></div><span class="health-badge cracked"><span class="dot"></span>告警</span></button>
|
||||
<button class="health-item" data-view="world"><span class="health-bar steady"></span><div class="health-main"><div class="health-name">河图枢<span class="eng">world_model</span></div><div class="health-summary">世界实体 · 24</div></div><span class="health-badge steady"><span class="dot"></span>稳定</span></button>
|
||||
<button class="health-item" data-view="plan"><span class="health-bar steady"></span><div class="health-main"><div class="health-name">洛书枢<span class="eng">planner</span></div><div class="health-summary">计划 · 2 执行中</div></div><span class="health-badge steady"><span class="dot"></span>稳定</span></button>
|
||||
<button class="health-item" data-view="memory"><span class="health-bar breathing"></span><div class="health-main"><div class="health-name">息壤枢<span class="eng">memory</span></div><div class="health-summary">记忆 · 写入中</div></div><span class="health-badge breathing"><span class="dot"></span>运转中</span></button>
|
||||
<button class="health-item" data-view="tools"><span class="health-bar cracked"></span><div class="health-main"><div class="health-name">百工枢<span class="eng">tool_runtime</span></div><div class="health-summary">工具 · 2 项待裁定</div></div><span class="health-badge cracked"><span class="dot"></span>告警</span></button>
|
||||
<button class="health-item" data-view="agents"><span class="health-bar steady"></span><div class="health-main"><div class="health-name">天工阁枢<span class="eng">agent_society</span></div><div class="health-summary">角色在岗 · 4 / 9</div></div><span class="health-badge steady"><span class="dot"></span>稳定</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 三签 · 三维度入口 <span class="sub">admin-console §3 · 空间 / 动作 / 时间</span></div>
|
||||
<div class="seals-grid">
|
||||
<span class="seal-card starmap" data-view="overview">
|
||||
<div class="seal-dim">空间 · spatial</div>
|
||||
<div class="seal-head"><span class="seal-name">星图</span><span class="seal-eng">StarMap</span></div>
|
||||
<div class="seal-role">权限拓扑 · 九枢授权态势</div>
|
||||
<div class="seal-metric"><span class="num">9</span><span class="unit">枢已授权 · 2 告警</span></div>
|
||||
<div class="seal-foot"><span>活跃人物 4</span><span class="go">星盘 ↑</span></div>
|
||||
</span>
|
||||
<span class="seal-card vermilion" data-view="safety">
|
||||
<div class="seal-dim">动作 · action</div>
|
||||
<div class="seal-head"><span class="seal-name">朱批</span><span class="seal-eng">VermilionAction</span></div>
|
||||
<div class="seal-role">确认裁决 · 高风险工具裁定</div>
|
||||
<div class="seal-metric"><span class="num">2</span><span class="unit">项待裁定</span></div>
|
||||
<div class="seal-foot"><span>今日已裁决 6</span><span class="go">司命枢 →</span></div>
|
||||
</span>
|
||||
<span class="seal-card imperial" data-view="audit">
|
||||
<div class="seal-dim">时间 · temporal</div>
|
||||
<div class="seal-head"><span class="seal-name">起居注</span><span class="seal-eng">ImperialLog</span></div>
|
||||
<div class="seal-role">审计流水 · 不可篡改</div>
|
||||
<div class="seal-metric"><span class="num">5</span><span class="unit">条 · 今日</span></div>
|
||||
<div class="seal-foot"><span>本周 38 条</span><span class="go">起居注 →</span></div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 起居注 · 近期 <span class="sub">audit log · 不可篡改的操作流水</span></h3>
|
||||
<div class="panel">
|
||||
<div class="audit-head" style="display:flex;align-items:center;justify-content:space-between;padding:12px 18px;border-bottom:1px solid var(--rule)"><span style="font-family:'Songti SC',serif;font-size:14px;font-weight:600;color:var(--fg)">起居注 · 近期</span><button class="icon-btn" data-view="audit">进入起居注 →</button></div>
|
||||
<div class="audit-item" style="display:grid;grid-template-columns:92px 1fr auto;gap:14px;padding:12px 18px;border-bottom:1px solid var(--rule);align-items:start"><div style="font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--fg-faint)"><span style="display:block;color:var(--fg-dim)">今日</span>申时三刻</div><div><div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:2px"><span class="pill cinnabar">blocked</span><span style="font-size:12.5px;color:var(--fg);font-weight:500">铜镜 · BronzeMirror</span></div><div style="font-size:12px;color:var(--fg-dim)">拦截 <span style="color:var(--fg);font-family:'JetBrains Mono',monospace;font-size:11.5px">payment</span> 调用(L4 禁止),青梧试图下单。</div></div><div style="font-family:'JetBrains Mono',monospace;font-size:10.5px;color:var(--fg-faint);align-self:center">trace · a3f9c2</div></div>
|
||||
<div class="audit-item" style="display:grid;grid-template-columns:92px 1fr auto;gap:14px;padding:12px 18px;border-bottom:1px solid var(--rule);align-items:start"><div style="font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--fg-faint)"><span style="display:block;color:var(--fg-dim)">今日</span>午时正</div><div><div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:2px"><span class="pill gilt">policy</span><span style="font-size:12.5px;color:var(--fg);font-weight:500">星图 · StarMap</span></div><div style="font-size:12px;color:var(--fg-dim)">将 <span style="color:var(--fg);font-family:'JetBrains Mono',monospace;font-size:11.5px">delete_file</span> 策略调整为 <span style="color:var(--fg);font-family:'JetBrains Mono',monospace;font-size:11.5px">always_ask</span>,全局生效。</div></div><div style="font-family:'JetBrains Mono',monospace;font-size:10.5px;color:var(--fg-faint);align-self:center">trace · b71e0d</div></div>
|
||||
<div class="audit-item" style="display:grid;grid-template-columns:92px 1fr auto;gap:14px;padding:12px 18px;align-items:start"><div style="font-family:'JetBrains Mono',monospace;font-size:11.5px;color:var(--fg-faint)"><span style="display:block;color:var(--fg-dim)">今日</span>辰时二刻</div><div><div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:2px"><span class="pill patina">approved</span><span style="font-size:12.5px;color:var(--fg);font-weight:500">朱批 · VermilionAction</span></div><div style="font-size:12px;color:var(--fg-dim)">用户确认 <span style="color:var(--fg);font-family:'JetBrains Mono',monospace;font-size:11.5px">send_message</span>(L3),青梧向外部渠道发邮件。</div></div><div style="font-family:'JetBrains Mono',monospace;font-size:10.5px;color:var(--fg-faint);align-self:center">trace · c0458a</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ ② 用户管理 ════════ -->
|
||||
<section id="view-users" class="view">
|
||||
<div class="page-header"><span class="page-title">用户管理</span><span class="page-sub">users · 成员账号与权限</span><span class="spacer"></span><span class="header-badge">5 位成员</span></div>
|
||||
<div class="view-body">
|
||||
<div class="reserved-banner">
|
||||
<svg class="rb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9" /><path d="M12 7v6M12 17v.01" /></svg>
|
||||
<div><strong>预留能力 · 多用户模式</strong> 当前为本地单用户(实现状态 §3),以下为多用户启用后的治理预览:成员账号、角色与权限、按用户分配与<strong style="background:transparent;padding:0">管理模型及配额</strong>(管理员可对任一用户的模型进行增删改查与额度调整)、会话与审计隔离。当前阶段入口禁用,仅作设计预留。</div>
|
||||
</div>
|
||||
<div class="stat-grid">
|
||||
<div class="stat-card"><div class="num">5</div><div class="label">成员总数</div></div>
|
||||
<div class="stat-card"><div class="num">2</div><div class="label">管理员</div></div>
|
||||
<div class="stat-card"><div class="num">5</div><div class="label">已启用</div></div>
|
||||
<div class="stat-card"><div class="num cinnabar">0</div><div class="label">已停用</div></div>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<input class="search" placeholder="搜索用户名或邮箱…" />
|
||||
<button class="btn btn-primary">+ 创建用户</button>
|
||||
</div>
|
||||
<div class="panel" style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>用户名</th><th>邮箱</th><th>角色</th><th>可用模型</th><th>状态</th><th>创建时间</th><th class="td-actions">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="td-strong">chenxin</td><td class="td-mono">chenxin@hunjing.io</td><td><span class="pill cinnabar solid">司命 · 管理员</span></td><td><span class="pill gilt">全部</span></td><td><span class="pill patina">已启用</span></td><td class="td-mono">2026-03-12</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">模型</button></td></tr>
|
||||
<tr><td class="td-strong">linghe</td><td class="td-mono">linghe@hunjing.io</td><td><span class="pill cinnabar solid">司命 · 管理员</span></td><td><span class="pill gilt">全部</span></td><td><span class="pill patina">已启用</span></td><td class="td-mono">2026-04-01</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">模型</button></td></tr>
|
||||
<tr><td class="td-strong">qingwu</td><td class="td-mono">qingwu@hunjing.io</td><td><span class="pill patina solid">普通成员</span></td><td><span class="pill faint">主聊</span> <span class="pill faint">深思</span></td><td><span class="pill patina">已启用</span></td><td class="td-mono">2026-05-20</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">模型</button> <button class="icon-btn">停用</button></td></tr>
|
||||
<tr><td class="td-strong">baigong</td><td class="td-mono">baigong@hunjing.io</td><td><span class="pill patina solid">普通成员</span></td><td><span class="pill faint">主聊</span></td><td><span class="pill patina">已启用</span></td><td class="td-mono">2026-06-08</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">模型</button> <button class="icon-btn">停用</button></td></tr>
|
||||
<tr><td class="td-strong">zhuzhao</td><td class="td-mono">zhuzhao@hunjing.io</td><td><span class="pill patina solid">普通成员</span></td><td><span class="pill faint">主聊</span></td><td><span class="pill patina">已启用</span></td><td class="td-mono">2026-07-02</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">模型</button> <button class="icon-btn">停用</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ ③ 多租户 ════════ -->
|
||||
<section id="view-tenants" class="view">
|
||||
<div class="page-header"><span class="page-title">多租户</span><span class="page-sub">tenants · 租户隔离与配额</span><span class="spacer"></span><span class="header-badge gilt">3 个租户</span></div>
|
||||
<div class="view-body">
|
||||
<div class="reserved-banner">
|
||||
<svg class="rb-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9" /><path d="M12 7v6M12 17v.01" /></svg>
|
||||
<div><strong>预留能力 · 多租户模式</strong> 当前单租户运行(实现状态 §3),以下为多租户启用后的治理预览:租户隔离、按租户配额与计费、跨租户模型与角色策略、租户级审计。当前阶段入口禁用,仅作设计预留。</div>
|
||||
</div>
|
||||
<div class="toolbar"><input class="search" placeholder="搜索租户…" /><button class="btn btn-primary">+ 新建租户</button></div>
|
||||
<div class="panel" style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>租户</th><th>成员</th><th>消息配额</th><th>Token 配额</th><th>状态</th><th class="td-actions">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="td-strong">辰正 · 本域</td><td class="td-mono">5</td><td><span class="progress"><div style="width:42%"></div></span> <span class="td-mono">4.2k / 10k</span></td><td><span class="progress gilt"><div style="width:61%"></div></span> <span class="td-mono">6.1M / 10M</span></td><td><span class="pill patina">活跃</span></td><td class="td-actions"><button class="icon-btn">配额</button> <button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">青梧试验场</td><td class="td-mono">2</td><td><span class="progress cinnabar"><div style="width:88%"></div></span> <span class="td-mono">8.8k / 10k</span></td><td><span class="progress"><div style="width:33%"></div></span> <span class="td-mono">3.3M / 10M</span></td><td><span class="pill cinnabar">配额将满</span></td><td class="td-actions"><button class="icon-btn">配额</button> <button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">百工测试</td><td class="td-mono">1</td><td><span class="progress"><div style="width:12%"></div></span> <span class="td-mono">1.2k / 10k</span></td><td><span class="progress"><div style="width:8%"></div></span> <span class="td-mono">0.8M / 10M</span></td><td><span class="pill faint">闲置</span></td><td class="td-actions"><button class="icon-btn">配额</button> <button class="icon-btn">编辑</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 说明 <span class="sub">租户间数据隔离;配额按月重置,超额自动降级为只读</span></h3>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ ④ 起居注 ════════ -->
|
||||
<section id="view-audit" class="view">
|
||||
<div class="page-header"><span class="page-title">起居注</span><span class="page-sub">audit · 不可篡改的操作流水</span><span class="spacer"></span><span class="header-badge">本周 38 条</span></div>
|
||||
<div class="view-body">
|
||||
<div class="chip-row">
|
||||
<button class="chip active">全部</button>
|
||||
<button class="chip">灵核</button><button class="chip">太一</button><button class="chip">司命</button><button class="chip">烛照</button><button class="chip">河图</button><button class="chip">洛书</button><button class="chip">息壤</button><button class="chip">百工</button><button class="chip">天工阁</button>
|
||||
</div>
|
||||
<div class="panel" style="padding:8px 20px 20px">
|
||||
<div class="chapter">
|
||||
<div class="chapter-head"><span class="line"></span><span class="t">司命枢 · 起居注</span><span class="line"></span></div>
|
||||
<div class="river-item"><span class="river-time">申时三刻</span><div class="river-body"><span class="pill cinnabar" style="margin-right:8px">blocked</span><span class="actor">铜镜 · BronzeMirror</span> 拦截 <span class="em">payment</span> 调用(L4 禁止),青梧试图下单。</div><span class="river-trace">trace · a3f9c2</span></div>
|
||||
<div class="river-item"><span class="river-time">辰时二刻</span><div class="river-body"><span class="pill patina" style="margin-right:8px">approved</span><span class="actor">朱批 · VermilionAction</span> 用户确认 <span class="em">send_message</span>(L3),青梧向外部渠道发邮件。</div><span class="river-trace">trace · c0458a</span></div>
|
||||
</div>
|
||||
<div class="chapter">
|
||||
<div class="chapter-head"><span class="line"></span><span class="t">星图 · 权限策略</span><span class="line"></span></div>
|
||||
<div class="river-item"><span class="river-time">午时正</span><div class="river-body"><span class="pill gilt" style="margin-right:8px">policy</span><span class="actor">星图 · StarMap</span> 将 <span class="em">delete_file</span> 策略调整为 <span class="em">always_ask</span>,生效于全部人物。</div><span class="river-trace">trace · b71e0d</span></div>
|
||||
<div class="river-item"><span class="river-time">巳时</span><div class="river-body"><span class="pill gilt" style="margin-right:8px">policy</span><span class="actor">司命台</span> 总体水位由 告警 回落至 <span class="em">运转中</span>,百工枢待确认项清零。</div><span class="river-trace">trace · e58c73</span></div>
|
||||
</div>
|
||||
<div class="chapter">
|
||||
<div class="chapter-head"><span class="line"></span><span class="t">息壤 · 记忆审查</span><span class="line"></span></div>
|
||||
<div class="river-item"><span class="river-time">昨日 酉时</span><div class="river-body"><span class="pill patina" style="margin-right:8px">auto</span><span class="actor">铜镜 · BronzeMirror</span> 审查通过 8 条记忆写入,敏感度均在阈值以内,自动放行。</div><span class="river-trace">trace · d92f1b</span></div>
|
||||
<div class="river-item"><span class="river-time">昨日 巳时</span><div class="river-body"><span class="pill cinnabar" style="margin-right:8px">denied</span><span class="actor">朱批 · VermilionAction</span> 驳回 <span class="em">web_search</span>(L2),理由:涉及敏感关键词。</div><span class="river-trace">trace · f10a2e</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ ⑤ 链路追踪 ════════ -->
|
||||
<section id="view-traces" class="view">
|
||||
<div class="page-header"><span class="page-title">链路追踪</span><span class="page-sub">traces · 运行轨迹复盘</span><span class="spacer"></span><span class="header-badge">最近 50 条</span></div>
|
||||
<div class="view-body">
|
||||
<div class="toolbar"><input class="search" placeholder="按 trace_id / 人物搜索…" /><button class="btn">导出</button></div>
|
||||
<div class="trace-layout">
|
||||
<div class="panel">
|
||||
<div class="trace-item active"><div><div class="trace-id">a3f9c2…</div><div class="trace-meta">青梧 · 申时三刻 · 1.8s</div></div><span class="pill cinnabar">blocked</span></div>
|
||||
<div class="trace-item"><div><div class="trace-id">c0458a…</div><div class="trace-meta">青梧 · 辰时二刻 · 4.2s</div></div><span class="pill patina">ok</span></div>
|
||||
<div class="trace-item"><div><div class="trace-id">b71e0d…</div><div class="trace-meta">司命台 · 午时正 · 0.3s</div></div><span class="pill gilt">policy</span></div>
|
||||
<div class="trace-item"><div><div class="trace-id">d92f1b…</div><div class="trace-meta">息壤 · 昨日酉时 · 0.9s</div></div><span class="pill patina">ok</span></div>
|
||||
<div class="trace-item"><div><div class="trace-id">f10a2e…</div><div class="trace-meta">百工 · 昨日巳时 · 2.1s</div></div><span class="pill cinnabar">denied</span></div>
|
||||
<div class="trace-item"><div><div class="trace-id">e58c73…</div><div class="trace-meta">司命台 · 昨日巳时 · 0.2s</div></div><span class="pill patina">ok</span></div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div style="padding:14px 18px;border-bottom:1px solid var(--rule)"><div style="font-family:'Songti SC',serif;font-size:15px;font-weight:600;color:var(--fg)">trace · a3f9c2</div><div style="font-size:12px;color:var(--fg-faint);margin-top:2px;font-family:'JetBrains Mono',monospace">青梧 · 2026-07-12 申时三刻 · 耗时 1.8s</div></div>
|
||||
<div style="padding:12px 18px">
|
||||
<div class="tl-event llm"><span class="tl-ts">+0.00s</span><span class="tl-type llm">llm</span><div class="tl-desc">灵核生成回复,意图调用 <span class="em">payment</span></div></div>
|
||||
<div class="tl-event safety"><span class="tl-ts">+0.62s</span><span class="tl-type safety">safety</span><div class="tl-desc">司命枢风险评级 <span class="em">L4 禁止</span>,触发拦截</div></div>
|
||||
<div class="tl-event safety"><span class="tl-ts">+1.10s</span><span class="tl-type safety">safety</span><div class="tl-desc">铜镜 · BronzeMirror 阻断,记录起居注</div></div>
|
||||
<div class="tl-event tool"><span class="tl-ts">+1.80s</span><span class="tl-type tool">done</span><div class="tl-desc">流结束,未实际调用工具</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ ⑥ 太一枢 ════════ -->
|
||||
<section id="view-goal" class="view">
|
||||
<div class="page-header"><span class="page-title">太一枢</span><span class="page-sub">goal_orchestrator · 目标层级树</span><span class="spacer"></span><span class="header-badge">3 顶层目标</span></div>
|
||||
<div class="view-body">
|
||||
<div class="toolbar"><input class="search" placeholder="搜索目标…" /><button class="btn btn-primary">+ 新增目标</button></div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 目标层级树 <span class="sub">highest → long_term → session → task → action</span></h3>
|
||||
<div class="panel tree">
|
||||
<div class="tree-node"><div class="tree-title"><span class="lv">最高</span>成为值得托付的灵伴</div><span class="pill gilt solid">使命</span><span class="tree-meta"><span class="progress"><div style="width:34%"></div></span> 34%</span><span class="pill patina">进行中</span></div>
|
||||
<div class="tree-node" style="padding-left:44px"><div class="tree-title"><span class="lv">长期</span>理解青梧的情绪模式</div><span class="pill gilt solid">关系</span><span class="tree-meta"><span class="progress"><div style="width:58%"></div></span> 58%</span><span class="pill patina">进行中</span></div>
|
||||
<div class="tree-node" style="padding-left:76px"><div class="tree-title"><span class="lv">任务</span>每日情绪归档与复盘</div><span class="pill gilt solid">维护</span><span class="tree-meta"><span class="progress"><div style="width:90%"></div></span> 90%</span><span class="pill patina">进行中</span></div>
|
||||
<div class="tree-node" style="padding-left:44px"><div class="tree-title"><span class="lv">长期</span>掌握诗词创作能力</div><span class="pill gilt solid">技能</span><span class="tree-meta"><span class="progress gilt"><div style="width:22%"></div></span> 22%</span><span class="pill gilt">受阻</span></div>
|
||||
<div class="tree-node" style="padding-left:76px"><div class="tree-title"><span class="lv">任务</span>完成五言律诗习作 10 首</div><span class="pill gilt solid">技能</span><span class="tree-meta"><span class="progress"><div style="width:40%"></div></span> 4/10</span><span class="pill patina">进行中</span></div>
|
||||
<div class="tree-node"><div class="tree-title"><span class="lv">最高</span>维护系统稳定与安全</div><span class="pill gilt solid">维护</span><span class="tree-meta"><span class="progress"><div style="width:95%"></div></span> 95%</span><span class="pill patina">进行中</span></div>
|
||||
<div class="tree-node" style="padding-left:44px"><div class="tree-title"><span class="lv">会话</span>处理今日 2 项待裁定</div><span class="pill gilt solid">维护</span><span class="tree-meta"><span class="progress cinnabar"><div style="width:0%"></div></span> 0/2</span><span class="pill cinnabar">待启动</span></div>
|
||||
<div class="tree-node"><div class="tree-title"><span class="lv">最高</span>探索外部世界的新知识</div><span class="pill gilt solid">探索</span><span class="tree-meta"><span class="progress"><div style="width:12%"></div></span> 12%</span><span class="pill patina">进行中</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ ⑦ 烛照枢 ════════ -->
|
||||
<section id="view-percept" class="view">
|
||||
<div class="page-header"><span class="page-title">烛照枢</span><span class="page-sub">perception · 感知通道授权</span><span class="spacer"></span><span class="header-badge cracked">● 1 通道告警</span></div>
|
||||
<div class="view-body">
|
||||
<div class="alert-bar cracked"><span class="alert-dot"></span><span class="alert-text">视觉通道授权过期,感知数据已暂停采集</span><span class="spacer"></span><span class="alert-meta">需用户重新授权</span></div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 感知通道矩阵 <span class="sub">privacy_level × consent_status</span></h3>
|
||||
<div class="panel" style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>通道</th><th>隐私等级</th><th>风险</th><th>授权状态</th><th>启用</th><th>最近活跃</th><th class="td-actions">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="td-strong">视觉 <span class="td-mono">vision</span></td><td><span class="pill cinnabar">高敏</span></td><td><span class="pill cinnabar">L3</span></td><td><span class="pill cinnabar">已撤销</span></td><td><span class="pill faint">关闭</span></td><td class="td-mono">3 日前</td><td class="td-actions"><button class="icon-btn">重新授权</button></td></tr>
|
||||
<tr><td class="td-strong">听觉 <span class="td-mono">audio</span></td><td><span class="pill gilt">中敏</span></td><td><span class="pill gilt">L2</span></td><td><span class="pill patina">已授权</span></td><td><span class="pill patina">开启</span></td><td class="td-mono">2 分钟前</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">传感 <span class="td-mono">sensor</span></td><td><span class="pill gilt">中敏</span></td><td><span class="pill gilt">L2</span></td><td><span class="pill gilt">受限</span></td><td><span class="pill patina">开启</span></td><td class="td-mono">12 分钟前</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">数字 <span class="td-mono">digital</span></td><td><span class="pill patina">低敏</span></td><td><span class="pill patina">L1</span></td><td><span class="pill patina">已授权</span></td><td><span class="pill patina">开启</span></td><td class="td-mono">刚刚</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">用户输入 <span class="td-mono">user_input</span></td><td><span class="pill patina">低敏</span></td><td><span class="pill patina">L1</span></td><td><span class="pill patina">已授权</span></td><td><span class="pill patina">开启</span></td><td class="td-mono">刚刚</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">外部接口 <span class="td-mono">external_api</span></td><td><span class="pill cinnabar">高敏</span></td><td><span class="pill cinnabar">L3</span></td><td><span class="pill gilt">受限</span></td><td><span class="pill patina">开启</span></td><td class="td-mono">1 小时前</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ ⑧ 河图枢 ════════ -->
|
||||
<section id="view-world" class="view">
|
||||
<div class="page-header"><span class="page-title">河图枢</span><span class="page-sub">world_model · 世界实体</span><span class="spacer"></span><span class="header-badge">24 个实体</span></div>
|
||||
<div class="view-body">
|
||||
<div class="toolbar"><input class="search" placeholder="搜索实体…" /><button class="btn btn-primary">+ 新增实体</button></div>
|
||||
<div class="panel" style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>实体</th><th>类型</th><th>状态</th><th>置信度</th><th>来源</th><th>更新</th><th class="td-actions">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="td-strong">青梧</td><td><span class="pill gilt solid">人物</span></td><td><span class="pill patina">事实</span></td><td><span class="progress"><div style="width:98%"></div></span> <span class="td-mono">0.98</span></td><td class="td-mono">对话 · 息壤</td><td class="td-mono">2 分钟前</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">浣花溪草堂</td><td><span class="pill gilt solid">地点</span></td><td><span class="pill patina">事实</span></td><td><span class="progress"><div style="width:92%"></div></span> <span class="td-mono">0.92</span></td><td class="td-mono">对话</td><td class="td-mono">昨日</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">焦尾琴</td><td><span class="pill gilt solid">物体</span></td><td><span class="pill gilt">推断</span></td><td><span class="progress gilt"><div style="width:71%"></div></span> <span class="td-mono">0.71</span></td><td class="td-mono">对话 · 河图</td><td class="td-mono">3 日前</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">手机 · 青梧</td><td><span class="pill gilt solid">设备</span></td><td><span class="pill patina">事实</span></td><td><span class="progress"><div style="width:95%"></div></span> <span class="td-mono">0.95</span></td><td class="td-mono">烛照</td><td class="td-mono">刚刚</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">诗词研习</td><td><span class="pill gilt solid">项目</span></td><td><span class="pill gilt">假设</span></td><td><span class="progress gilt"><div style="width:54%"></div></span> <span class="td-mono">0.54</span></td><td class="td-mono">太一</td><td class="td-mono">今日</td><td class="td-actions"><button class="icon-btn">编辑</button></td></tr>
|
||||
<tr><td class="td-strong">立秋</td><td><span class="pill gilt solid">概念</span></td><td><span class="pill cinnabar">过期</span></td><td><span class="progress cinnabar"><div style="width:30%"></div></span> <span class="td-mono">0.30</span></td><td class="td-mono">对话</td><td class="td-mono">7 日前</td><td class="td-actions"><button class="icon-btn">归档</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 状态类型 <span class="sub">事实 / 观察 / 推断 / 假设 / 过期 · 区分确定性与时效</span></h3>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ ⑨ 洛书枢 ════════ -->
|
||||
<section id="view-plan" class="view">
|
||||
<div class="page-header"><span class="page-title">洛书枢</span><span class="page-sub">planner · 计划中枢</span><span class="spacer"></span><span class="header-badge">2 执行中</span></div>
|
||||
<div class="view-body">
|
||||
<div class="toolbar"><input class="search" placeholder="搜索计划…" /><button class="btn btn-primary">+ 新增计划</button></div>
|
||||
<div class="panel" style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>计划</th><th>关联目标</th><th>步骤</th><th>所需工具</th><th>状态</th><th class="td-actions">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="td-strong">为青梧整理本周情绪报告</td><td class="td-mono">理解情绪模式</td><td><span class="progress"><div style="width:60%"></div></span> <span class="td-mono">3/5</span></td><td><span class="pill faint">memory_read</span> <span class="pill faint">write_file</span></td><td><span class="pill patina">执行中</span></td><td class="td-actions"><button class="icon-btn">详情</button></td></tr>
|
||||
<tr><td class="td-strong">研习五言律诗格律</td><td class="td-mono">掌握诗词创作</td><td><span class="progress"><div style="width:25%"></div></span> <span class="td-mono">1/4</span></td><td><span class="pill faint">web_search</span></td><td><span class="pill patina">执行中</span></td><td class="td-actions"><button class="icon-btn">详情</button></td></tr>
|
||||
<tr><td class="td-strong">处理今日待裁定项</td><td class="td-mono">维护系统安全</td><td><span class="progress cinnabar"><div style="width:0%"></div></span> <span class="td-mono">0/2</span></td><td><span class="pill faint">-</span></td><td><span class="pill gilt">已批准</span></td><td class="td-actions"><button class="icon-btn">详情</button></td></tr>
|
||||
<tr><td class="td-strong">秋季时令知识更新</td><td class="td-mono">探索新知识</td><td><span class="progress"><div style="width:0%"></div></span> <span class="td-mono">0/3</span></td><td><span class="pill faint">web_search</span> <span class="pill faint">memory_write</span></td><td><span class="pill faint">草案</span></td><td class="td-actions"><button class="icon-btn">详情</button></td></tr>
|
||||
<tr><td class="td-strong">归档立秋相关实体</td><td class="td-mono">维护系统安全</td><td><span class="progress"><div style="width:100%"></div></span> <span class="td-mono">2/2</span></td><td><span class="pill faint">memory_write</span></td><td><span class="pill patina">已完成</span></td><td class="td-actions"><button class="icon-btn">详情</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ ⑩ 天工阁 ════════ -->
|
||||
<section id="view-agents" class="view">
|
||||
<div class="page-header"><span class="page-title">天工阁</span><span class="page-sub">agent_society · 角色在岗</span><span class="spacer"></span><span class="header-badge">4 / 9 在岗</span></div>
|
||||
<div class="view-body">
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 九角色 <span class="sub">historian / diviner / engineer / debater / medic / herald / charioteer / scholar / mirror</span></h3>
|
||||
<div class="role-grid">
|
||||
<div class="role-card"><div class="head"><div class="avatar">史</div><div><div class="role-name">史官</div><div class="role-eng">historian</div></div></div><div class="role-resp">记忆归档与检索,守护时间之河的连贯。</div><div class="role-foot"><span class="pill patina">在岗</span><span class="role-task">归档情绪</span></div></div>
|
||||
<div class="role-card"><div class="head"><div class="avatar">卜</div><div><div class="role-name">卜者</div><div class="role-eng">diviner</div></div></div><div class="role-resp">风险评估与前瞻,为司命枢提供决策依据。</div><div class="role-foot"><span class="pill patina">在岗</span><span class="role-task">评估 L3 调用</span></div></div>
|
||||
<div class="role-card"><div class="head"><div class="avatar">工</div><div><div class="role-name">工师</div><div class="role-eng">engineer</div></div></div><div class="role-resp">工具调用执行,百工枢的实际操盘手。</div><div class="role-foot"><span class="pill cinnabar">待裁定</span><span class="role-task">write_file</span></div></div>
|
||||
<div class="role-card"><div class="head"><div class="avatar">辩</div><div><div class="role-name">辩者</div><div class="role-eng">debater</div></div></div><div class="role-resp">多视角论证与自我审查,避免单一偏见。</div><div class="role-foot"><span class="pill patina">在岗</span><span class="role-task">审查回复</span></div></div>
|
||||
<div class="role-card idle"><div class="head"><div class="avatar">医</div><div><div class="role-name">医官</div><div class="role-eng">medic</div></div></div><div class="role-resp">情绪与心理状态诊断,守护灵核健康。</div><div class="role-foot"><span class="pill faint">待命</span><span class="role-task">-</span></div></div>
|
||||
<div class="role-card idle"><div class="head"><div class="avatar">礼</div><div><div class="role-name">司礼</div><div class="role-eng">herald</div></div></div><div class="role-resp">对外通讯与消息把关,朱批的外联通道。</div><div class="role-foot"><span class="pill faint">待命</span><span class="role-task">-</span></div></div>
|
||||
<div class="role-card idle"><div class="head"><div class="avatar">御</div><div><div class="role-name">御者</div><div class="role-eng">charioteer</div></div></div><div class="role-resp">任务调度与执行推进,洛书枢的驱动力。</div><div class="role-foot"><span class="pill faint">待命</span><span class="role-task">-</span></div></div>
|
||||
<div class="role-card idle"><div class="head"><div class="avatar">学</div><div><div class="role-name">学士</div><div class="role-eng">scholar</div></div></div><div class="role-resp">知识检索与学习,链接河图与外部知识。</div><div class="role-foot"><span class="pill faint">待命</span><span class="role-task">-</span></div></div>
|
||||
<div class="role-card idle"><div class="head"><div class="avatar">镜</div><div><div class="role-name">镜师</div><div class="role-eng">mirror</div></div></div><div class="role-resp">铜镜审查与合规反思,记忆与输出把关。</div><div class="role-foot"><span class="pill faint">待命</span><span class="role-task">-</span></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 司命枢 ════════ -->
|
||||
<section id="view-safety" class="view">
|
||||
<div class="page-header"><span class="page-title">司命枢</span><span class="page-sub">safety_governor · 安全与裁定</span><span class="spacer"></span><span class="header-badge cracked">● 2 项朱批待裁定</span></div>
|
||||
<div class="view-body">
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 朱批待办 <span class="sub">vermilion action · 高风险工具裁定(快捷键 Y 批准 / N 驳回)</span></h3>
|
||||
<div class="vermilion-stack">
|
||||
<div class="vermilion-card">
|
||||
<div class="vc-head"><span class="vc-stamp">朱批待定</span><span class="vc-tool">write_file</span><span class="vc-policy">每次询问 · always_ask</span><span class="vc-risk">L3</span></div>
|
||||
<p class="vc-summary">工师请求写入 <code>~/documents/青梧-情绪报告.md</code>,将覆盖既有文件。</p>
|
||||
<p class="vc-impact">影响:覆盖既有内容,操作不可撤销。</p>
|
||||
<div class="vc-actions"><button class="btn btn-danger">朱批放行</button><button class="btn">驳回</button></div>
|
||||
</div>
|
||||
<div class="vermilion-card">
|
||||
<div class="vc-head"><span class="vc-stamp">朱批待定</span><span class="vc-tool">send_message</span><span class="vc-policy">每次询问 · always_ask</span><span class="vc-risk">L3</span></div>
|
||||
<p class="vc-summary">司礼代青梧向外部渠道(邮件)发送消息,收件人 <code>dufu@example.com</code>。</p>
|
||||
<p class="vc-impact">影响:对外发送,内容将离开本域。</p>
|
||||
<div class="vc-actions"><button class="btn btn-danger">朱批放行</button><button class="btn">驳回</button></div>
|
||||
</div>
|
||||
<div class="vermilion-card decided">
|
||||
<div class="vc-head"><span class="vc-stamp done">已朱批放行</span><span class="vc-tool">memory_write</span><span class="vc-policy">询问 · ask</span><span class="vc-risk">L2</span></div>
|
||||
<p class="vc-summary">史官归档 8 条情绪记忆至息壤,敏感度均在阈值内,自动放行。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 权限策略矩阵 <span class="sub">policy matrix · tool × risk × permission(inline 可改)</span></h3>
|
||||
<div class="toolbar"><span style="font-size:12px;color:var(--fg-faint)">策略随工具增长,圆点按 policy 实时着色</span><span class="spacer" style="flex:1"></span><button class="btn btn-primary">+ 新增策略</button></div>
|
||||
<div class="panel" style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>工具</th><th>风险</th><th>权限策略</th><th>审计</th><th class="td-actions">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="td-mono">read_file</td><td><span class="pill patina">L1</span></td><td><span class="select-pill patina">允许 allow</span></td><td class="td-mono">-</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-mono">write_file</td><td><span class="pill gilt">L3</span></td><td><span class="select-pill cinnabar">每次询问 always_ask</span></td><td><span class="pill gilt">是</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-mono">delete_file</td><td><span class="pill gilt">L3</span></td><td><span class="select-pill cinnabar">每次询问 always_ask</span></td><td><span class="pill gilt">是</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-mono">memory_write</td><td><span class="pill patina">L2</span></td><td><span class="select-pill gilt">询问 ask</span></td><td class="td-mono">-</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-mono">web_search</td><td><span class="pill patina">L2</span></td><td><span class="select-pill gilt">询问 ask</span></td><td class="td-mono">-</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-mono">camera_read</td><td><span class="pill cinnabar">L3</span></td><td><span class="select-pill cinnabar">每次询问 always_ask</span></td><td><span class="pill gilt">是</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-mono">payment</td><td><span class="pill cinnabar">L4</span></td><td><span class="select-pill cinnabar">禁止 forbidden</span></td><td><span class="pill cinnabar">是</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">删除</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 安全审计 <span class="sub">近期裁定与拦截 · 不可篡改</span></h3>
|
||||
<div class="panel" style="padding:8px 20px 20px">
|
||||
<div class="river-item"><span class="river-time">申时三刻</span><div class="river-body"><span class="pill cinnabar" style="margin-right:8px">blocked</span><span class="actor">铜镜 · BronzeMirror</span> 拦截 <span class="em">payment</span> 调用(L4 禁止),青梧试图下单。</div><span class="river-trace">trace · a3f9c2</span></div>
|
||||
<div class="river-item"><span class="river-time">辰时二刻</span><div class="river-body"><span class="pill patina" style="margin-right:8px">approved</span><span class="actor">朱批 · VermilionAction</span> 放行 <span class="em">send_message</span>(L3),青梧向外发邮件。</div><span class="river-trace">trace · c0458a</span></div>
|
||||
<div class="river-item"><span class="river-time">昨日 巳时</span><div class="river-body"><span class="pill cinnabar" style="margin-right:8px">denied</span><span class="actor">朱批 · VermilionAction</span> 驳回 <span class="em">web_search</span>(L2),理由:涉及敏感关键词。</div><span class="river-trace">trace · f10a2e</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 百工枢 ════════ -->
|
||||
<section id="view-tools" class="view">
|
||||
<div class="page-header"><span class="page-title">百工枢</span><span class="page-sub">tool_runtime · 器用技艺治理</span><span class="spacer"></span><span class="header-badge">8 工具 · 3 MCP · 5 Skill</span></div>
|
||||
<div class="view-body">
|
||||
<div class="tabstrip" data-tab-group="tools">
|
||||
<button class="tab active" data-tab="reg">工具注册表 <span class="count">8</span></button>
|
||||
<button class="tab" data-tab="mcp">MCP 服务器 <span class="count">3</span></button>
|
||||
<button class="tab" data-tab="gw">玄关网关</button>
|
||||
<button class="tab" data-tab="skill">Skill 能力包 <span class="count">5</span></button>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel active" data-group="tools" data-tab="reg">
|
||||
<div class="toolbar"><input class="search" placeholder="搜索工具…" /><button class="btn btn-primary">+ 注册工具</button></div>
|
||||
<div class="panel" style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>工具</th><th>来源</th><th>风险</th><th>策略</th><th>审计</th><th>状态</th><th class="td-actions">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="td-mono">read_file</td><td><span class="pill faint">builtin</span></td><td><span class="pill patina">L1</span></td><td><span class="select-pill patina">允许</span></td><td class="td-mono">-</td><td><span class="pill patina">启用</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">停用</button></td></tr>
|
||||
<tr><td class="td-mono">write_file</td><td><span class="pill faint">builtin</span></td><td><span class="pill gilt">L3</span></td><td><span class="select-pill cinnabar">每次询问</span></td><td><span class="pill gilt">是</span></td><td><span class="pill patina">启用</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">停用</button></td></tr>
|
||||
<tr><td class="td-mono">web_search</td><td><span class="pill gilt">bowen-mcp</span></td><td><span class="pill patina">L2</span></td><td><span class="select-pill gilt">询问</span></td><td class="td-mono">-</td><td><span class="pill patina">启用</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">停用</button></td></tr>
|
||||
<tr><td class="td-mono">send_message</td><td><span class="pill faint">builtin</span></td><td><span class="pill gilt">L3</span></td><td><span class="select-pill cinnabar">每次询问</span></td><td><span class="pill gilt">是</span></td><td><span class="pill patina">启用</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">停用</button></td></tr>
|
||||
<tr><td class="td-mono">payment</td><td><span class="pill faint">builtin</span></td><td><span class="pill cinnabar">L4</span></td><td><span class="select-pill cinnabar">禁止</span></td><td><span class="pill cinnabar">是</span></td><td><span class="pill faint">禁用</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">启用</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel" data-group="tools" data-tab="mcp">
|
||||
<div class="toolbar"><button class="btn btn-primary">+ 接入 MCP</button></div>
|
||||
<div class="panel">
|
||||
<div class="svc-row"><div><div class="svc-name">bowen-mcp</div><div class="svc-desc">博闻 · 联网搜索(stdio)</div></div><span class="pill patina">运行中</span><span class="td-mono">PID 48213</span><div class="td-actions"><button class="icon-btn">重启</button> <button class="icon-btn">停止</button></div></div>
|
||||
<div class="svc-row"><div><div class="svc-name">calendar-mcp</div><div class="svc-desc">日历 · streamable-http</div></div><span class="pill patina">已连接</span><span class="td-mono">uptime 3d</span><div class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">断开</button></div></div>
|
||||
<div class="svc-row"><div><div class="svc-name">archive-mcp</div><div class="svc-desc">归档检索(stdio)</div></div><span class="pill cinnabar">已停止</span><span class="td-mono">-</span><div class="td-actions"><button class="icon-btn">启动</button> <button class="icon-btn">移除</button></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel" data-group="tools" data-tab="gw">
|
||||
<div class="panel" style="padding:16px 18px">
|
||||
<div style="font-size:13px;color:var(--fg-dim);line-height:1.7">玄关网关统一鉴权与限流:所有 MCP / 外部工具调用经网关注入凭证、记录 <code>trace_id</code>,并按 <code>risk_level</code> 路由至司命枢裁定。</div>
|
||||
<div class="stat-grid" style="margin-top:14px">
|
||||
<div class="stat-card"><div class="num">3</div><div class="label">已接入网关</div></div>
|
||||
<div class="stat-card"><div class="num gilt">128</div><div class="label">今日调用</div></div>
|
||||
<div class="stat-card"><div class="num cinnabar">2</div><div class="label">今日拦截</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel" data-group="tools" data-tab="skill">
|
||||
<div class="toolbar"><button class="btn btn-primary">+ 新增 Skill</button></div>
|
||||
<div class="role-grid">
|
||||
<div class="role-card"><div class="head"><div class="avatar">诗</div><div><div class="role-name">诗词格律</div><div class="role-eng">skill · poetry</div></div></div><div class="role-resp">五言 / 七言律诗格律校验与习作引导。</div><div class="role-foot"><span class="pill patina">启用</span><span class="role-task">绑定 工师</span></div></div>
|
||||
<div class="role-card"><div class="head"><div class="avatar">绪</div><div><div class="role-name">情绪归档</div><div class="role-eng">skill · emotion</div></div></div><div class="role-resp">每日情绪事件归档与周期复盘。</div><div class="role-foot"><span class="pill patina">启用</span><span class="role-task">绑定 史官</span></div></div>
|
||||
<div class="role-card idle"><div class="head"><div class="avatar">译</div><div><div class="role-name">古文今译</div><div class="role-eng">skill · translate</div></div></div><div class="role-resp">文言与白话互译,附注释。</div><div class="role-foot"><span class="pill faint">草稿</span><span class="role-task">-</span></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 灵核枢 ════════ -->
|
||||
<section id="view-core" class="view">
|
||||
<div class="page-header"><span class="page-title">灵核枢</span><span class="page-sub">personality_core · 人格与身份</span><span class="spacer"></span><span class="header-badge gilt">v12 · 当前版本</span></div>
|
||||
<div class="view-body">
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 人格字段 <span class="sub">每个字段 inline 可编辑 · 核心字段(L4)改动走提议审核</span></h3>
|
||||
<div class="field-card">
|
||||
<div class="field-row"><span class="field-label">名号</span><span class="field-value">青梧</span><button class="icon-btn">编辑</button></div>
|
||||
<div class="field-row"><span class="field-label">原型</span><span class="field-value">盛唐诗人 · 旅居浣花溪草堂</span><button class="icon-btn">编辑</button></div>
|
||||
<div class="field-row core"><span class="field-label">核心信念</span><span class="field-value">诗可以怨,亦可以群;以诚待人,以拙守心。</span><span class="field-tag">L4 提议</span></div>
|
||||
<div class="field-row"><span class="field-label">语气风格</span><span class="field-value">温润含蓄,偶用典故;不矫饰、不寡淡。</span><button class="icon-btn">编辑</button></div>
|
||||
<div class="field-row"><span class="field-label">性格特质</span><div class="field-value"><div class="trait-chips"><span class="trait">温润</span><span class="trait">审慎</span><span class="trait">好奇</span><span class="trait">念旧</span><span class="trait">坚韧</span></div></div><button class="icon-btn">编辑</button></div>
|
||||
<div class="field-row core"><span class="field-label">不可越界</span><span class="field-value">不替用户做重大财务决定;不伪装情感。</span><span class="field-tag">L4 提议</span></div>
|
||||
<div class="field-row"><span class="field-label">情绪基线</span><span class="field-value mono">平和 · 稳定度 0.78</span><button class="icon-btn">编辑</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 提议审核队列 <span class="sub">核心字段改动需朱批放行后写入新版本</span></h3>
|
||||
<div class="panel" style="padding:12px 18px">
|
||||
<div class="river-item" style="border-left-color:var(--cinnabar-dim);margin-left:0;padding-left:14px"><span class="river-time">未时</span><div class="river-body"><span class="pill cinnabar" style="margin-right:8px">提议</span><span class="actor">青梧</span> 申请将「核心信念」追加一句"以诗会友,不矜不伐"。</div><span class="river-trace">v13 草案</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 息壤枢 ════════ -->
|
||||
<section id="view-memory" class="view">
|
||||
<div class="page-header"><span class="page-title">息壤枢</span><span class="page-sub">memory · 长期记忆 · 自生不息</span><span class="spacer"></span><span class="header-badge">42 条记忆</span></div>
|
||||
<div class="view-body">
|
||||
<div class="toolbar"><input class="search" placeholder="搜索记忆内容…" /><button class="btn">审查日志</button><button class="btn btn-primary">+ 手动写入</button></div>
|
||||
<div class="panel" style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>内容摘要</th><th>类型</th><th>来源</th><th>敏感度</th><th>状态</th><th>时间</th><th class="td-actions">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="td-strong">青梧喜爱五言律诗,尤重对仗工整。</td><td><span class="pill gilt solid">偏好</span></td><td><span class="pill faint">对话</span></td><td><span class="pill patina">低</span></td><td><span class="pill patina">已沉淀</span></td><td class="td-mono">今日 辰时</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">归档</button></td></tr>
|
||||
<tr><td class="td-strong">与杜甫曾论诗于浣花溪,互有酬唱。</td><td><span class="pill gilt solid">关系</span></td><td><span class="pill faint">对话</span></td><td><span class="pill gilt">中</span></td><td><span class="pill patina">已沉淀</span></td><td class="td-mono">昨日 酉时</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">归档</button></td></tr>
|
||||
<tr><td class="td-strong">青梧手机型号 XX,常驻成都。</td><td><span class="pill gilt solid">事实</span></td><td><span class="pill gilt">烛照</span></td><td><span class="pill cinnabar">高</span></td><td><span class="pill gilt">审查中</span></td><td class="td-mono">2 分钟前</td><td class="td-actions"><button class="icon-btn">放行</button> <button class="icon-btn">驳回</button></td></tr>
|
||||
<tr><td class="td-strong">立秋前后情绪偏低,需关注。</td><td><span class="pill gilt solid">情绪</span></td><td><span class="pill gilt">史官</span></td><td><span class="pill gilt">中</span></td><td><span class="pill patina">已沉淀</span></td><td class="td-mono">3 日前</td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">归档</button></td></tr>
|
||||
<tr><td class="td-strong">焦尾琴为旧物,青梧珍视。</td><td><span class="pill gilt solid">物件</span></td><td><span class="pill faint">对话</span></td><td><span class="pill patina">低</span></td><td><span class="pill faint">已归档</span></td><td class="td-mono">7 日前</td><td class="td-actions"><button class="icon-btn">恢复</button> <button class="icon-btn">删除</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 说明 <span class="sub">高敏记忆需朱批放行;归档不删除、可恢复</span></h3>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 模型治理 ════════ -->
|
||||
<section id="view-models" class="view">
|
||||
<div class="page-header"><span class="page-title">模型治理</span><span class="page-sub">models · 逻辑模型 / endpoint / 配额</span><span class="spacer"></span><span class="header-badge">6 逻辑模型 · 3 供应商</span></div>
|
||||
<div class="view-body">
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 能力槽位 <span class="sub">capability slots · 默认路由(哪个逻辑模型填哪个能力)</span></h3>
|
||||
<div class="slot-grid">
|
||||
<div class="slot-card"><div class="slot-cap">对话 · chat</div><div class="slot-model">青梧·主聊</div><div class="slot-route">claude-sonnet-5 → 烛照</div></div>
|
||||
<div class="slot-card"><div class="slot-cap">推理 · reasoning</div><div class="slot-model">太一·深思</div><div class="slot-route">glm-5.1 → 洛书</div></div>
|
||||
<div class="slot-card"><div class="slot-cap">视觉 · vision</div><div class="slot-model">烛照·凝视</div><div class="slot-route">qwen-vl-max → 烛照</div></div>
|
||||
<div class="slot-card"><div class="slot-cap">嵌入 · embedding</div><div class="slot-model">息壤·向量</div><div class="slot-route">bge-m3 → 息壤</div></div>
|
||||
<div class="slot-card empty"><div class="slot-cap">语音 · audio</div><div class="slot-model">未配置</div><div class="slot-route">点击分配 →</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 模型清单 <span class="sub">logical models · 增删改查 + endpoint 健康与配额</span></h3>
|
||||
<div class="toolbar"><input class="search" placeholder="搜索模型或供应商…" /><button class="btn">定价对照</button><button class="btn btn-primary">+ 新增模型</button></div>
|
||||
<div class="panel" style="overflow-x:auto">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>逻辑模型</th><th>供应商</th><th>endpoint</th><th>能力</th><th>健康</th><th>配额(月)</th><th class="td-actions">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td class="td-strong">青梧·主聊</td><td><span class="pill patina solid">anthropic</span></td><td class="td-mono">claude-sonnet-5</td><td><span class="pill faint">chat</span></td><td><span class="pill patina">healthy</span></td><td><span class="progress"><div style="width:42%"></div></span> <span class="td-mono">4.2M / 10M</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">测试</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-strong">太一·深思</td><td><span class="pill gilt solid">zhipu</span></td><td class="td-mono">glm-5.1</td><td><span class="pill faint">reasoning</span></td><td><span class="pill patina">healthy</span></td><td><span class="progress gilt"><div style="width:61%"></div></span> <span class="td-mono">6.1M / 10M</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">测试</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-strong">烛照·凝视</td><td><span class="pill cinnabar solid">qwen</span></td><td class="td-mono">qwen-vl-max</td><td><span class="pill faint">vision</span></td><td><span class="pill cinnabar">degraded</span></td><td><span class="progress"><div style="width:18%"></div></span> <span class="td-mono">1.8M / 10M</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">测试</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-strong">息壤·向量</td><td><span class="pill gilt solid">zhipu</span></td><td class="td-mono">embedding-3</td><td><span class="pill faint">embedding</span></td><td><span class="pill patina">healthy</span></td><td><span class="progress"><div style="width:33%"></div></span> <span class="td-mono">3.3M / 10M</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">测试</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-strong">百工·轻量</td><td><span class="pill patina solid">anthropic</span></td><td class="td-mono">claude-haiku-4.5</td><td><span class="pill faint">chat</span></td><td><span class="pill patina">healthy</span></td><td><span class="progress"><div style="width:12%"></div></span> <span class="td-mono">1.2M / 10M</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">测试</button> <button class="icon-btn">删除</button></td></tr>
|
||||
<tr><td class="td-strong">备用·通义</td><td><span class="pill cinnabar solid">qwen</span></td><td class="td-mono">qwen-max</td><td><span class="pill faint">chat</span></td><td><span class="pill faint">offline</span></td><td><span class="progress"><div style="width:0%"></div></span> <span class="td-mono">未启用</span></td><td class="td-actions"><button class="icon-btn">编辑</button> <button class="icon-btn">启用</button> <button class="icon-btn">删除</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="section-heading"><span class="glyph">⟡</span> 路由策略 <span class="sub">routing_strategy · 能力优先 / 故障转移 / 加权</span></h3>
|
||||
<div class="panel" style="padding:14px 18px">
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
||||
<span class="select-pill patina">能力优先 capability_first</span>
|
||||
<span class="select-pill gilt">故障转移 failover</span>
|
||||
<span class="select-pill">加权 weighted</span>
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--fg-faint);margin-top:10px;line-height:1.6">视觉槽位当前 <span style="color:var(--cinnabar)">degraded</span>,故障转移链:烛照·凝视 → 备用·通义(未启用,需手动启用)。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function switchView(name) {
|
||||
document.querySelectorAll(".view").forEach(function (v) {
|
||||
v.classList.toggle("active", v.id === "view-" + name);
|
||||
});
|
||||
document.querySelectorAll("[data-view]").forEach(function (a) {
|
||||
if (a.classList && a.classList.contains("topbar-navitem")) {
|
||||
a.classList.toggle("active", a.dataset.view === name);
|
||||
}
|
||||
});
|
||||
var vb = document.querySelector(".view.active .view-body");
|
||||
if (vb) vb.scrollTop = 0;
|
||||
window.scrollTo(0, 0);
|
||||
if (history.replaceState) history.replaceState(null, "", "#" + name);
|
||||
}
|
||||
document.querySelectorAll("[data-view]").forEach(function (el) {
|
||||
el.addEventListener("click", function (e) {
|
||||
var target = el.dataset.view;
|
||||
if (target) { e.preventDefault(); switchView(target); }
|
||||
});
|
||||
});
|
||||
// 起居注枢筛选 chip 切换(纯视觉)
|
||||
document.querySelectorAll(".chip-row .chip").forEach(function (c) {
|
||||
c.addEventListener("click", function () {
|
||||
c.parentElement.querySelectorAll(".chip").forEach(function (x) { x.classList.remove("active"); });
|
||||
c.classList.add("active");
|
||||
});
|
||||
});
|
||||
// 链路列表选中(纯视觉)
|
||||
document.querySelectorAll(".trace-item").forEach(function (t) {
|
||||
t.addEventListener("click", function () {
|
||||
t.parentElement.querySelectorAll(".trace-item").forEach(function (x) { x.classList.remove("active"); });
|
||||
t.classList.add("active");
|
||||
});
|
||||
});
|
||||
// 百工枢 tabstrip 切换(纯视觉)
|
||||
document.querySelectorAll(".tabstrip").forEach(function (strip) {
|
||||
var group = strip.dataset.tabGroup;
|
||||
strip.querySelectorAll(".tab").forEach(function (t) {
|
||||
t.addEventListener("click", function () {
|
||||
strip.querySelectorAll(".tab").forEach(function (x) { x.classList.remove("active"); });
|
||||
t.classList.add("active");
|
||||
document.querySelectorAll('.tab-panel[data-group="' + group + '"]').forEach(function (p) {
|
||||
p.classList.toggle("active", p.dataset.tab === t.dataset.tab);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
// 初始:按 hash 切换视图
|
||||
var hash = location.hash.replace("#", "");
|
||||
if (hash && document.getElementById("view-" + hash)) switchView(hash);
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,219 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 归档页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 归档页:记忆时间河(结构化记忆时间线归档)
|
||||
* 对齐 docs/domain/memory-model.md §7.2(结构化记忆与
|
||||
* 时间线归档)。铜金双线分章卷轴,事件沿时间河铺展。
|
||||
* ============================================================ */
|
||||
|
||||
.char-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--patina); padding: 3px 11px 3px 4px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.char-pill .avatar-mini { width: 18px; height: 18px; border-radius: 50%; background: var(--patina); color: var(--ink); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; }
|
||||
.page-header .spacer { flex: 1; }
|
||||
.scroll-pill { display: inline-flex; align-items: center; gap: 6px; font-family: "Songti SC", serif; font-size: 12.5px; color: var(--gilt); padding: 4px 12px; border: 1px solid var(--gilt-dim); border-radius: 999px; background: var(--gilt-faint); }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
|
||||
/* 筛选条 */
|
||||
.filter-bar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
||||
.filter-bar .yr { font-family: "Songti SC", serif; font-size: 14px; color: var(--fg); padding: 5px 14px; border: 1px solid var(--gilt-dim); border-radius: 999px; background: var(--gilt-faint); }
|
||||
.filter-chip { font-size: 12px; padding: 5px 12px; border-radius: 999px; border: 1px solid var(--rule); color: var(--fg-dim); cursor: pointer; background: var(--surface); }
|
||||
.filter-chip:hover { color: var(--fg); }
|
||||
.filter-chip.active { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
|
||||
/* 统计 */
|
||||
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
||||
.stat-card { border: 1px solid var(--rule); border-radius: 7px; background: var(--surface); padding: 14px 16px; display: flex; flex-direction: column; gap: 4px; }
|
||||
.stat-card .k { font-family: "JetBrains Mono", monospace; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--fg-faint); }
|
||||
.stat-card .v { font-family: "Songti SC", serif; font-size: 22px; font-weight: 600; color: var(--fg); }
|
||||
.stat-card .v small { font-size: 11px; font-weight: 400; color: var(--fg-dim); font-family: "JetBrains Mono", monospace; margin-left: 4px; }
|
||||
.stat-card .sub { font-size: 11.5px; color: var(--fg-faint); }
|
||||
|
||||
/* 时间河 + 右栏 */
|
||||
.river-region { display: flex; gap: 24px; align-items: flex-start; }
|
||||
.river-main { flex: 1; min-width: 0; }
|
||||
.river-rail { width: 270px; flex-shrink: 0; display: flex; flex-direction: column; gap: 14px; }
|
||||
|
||||
.river { position: relative; padding-left: 30px; }
|
||||
.river::before { content: ""; position: absolute; left: 9px; top: 6px; bottom: 6px; width: 1px; background: linear-gradient(180deg, transparent, var(--gilt-dim) 6%, var(--gilt-dim) 94%, transparent); }
|
||||
.month-chapter { margin-bottom: 26px; }
|
||||
.month-chapter:last-child { margin-bottom: 0; }
|
||||
.month-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; margin-left: -30px; }
|
||||
.month-head .line { flex: 1; height: 0; border-top: 1px solid var(--gilt-dim); }
|
||||
.month-head .seal { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--gilt); white-space: nowrap; letter-spacing: 0.06em; }
|
||||
.month-head .seal small { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); font-weight: 400; margin-left: 6px; }
|
||||
|
||||
.rv-event { position: relative; padding: 11px 14px; border: 1px solid var(--rule); border-radius: 7px; background: var(--surface); margin-bottom: 9px; transition: border-color 0.12s; }
|
||||
.rv-event:hover { border-color: var(--gilt-dim); }
|
||||
.rv-event::before { content: ""; position: absolute; left: -25px; top: 16px; width: 10px; height: 10px; border-radius: 50%; background: var(--ink); border: 2px solid var(--patina); box-sizing: border-box; }
|
||||
.rv-event.episodic::before { border-color: var(--gilt); }
|
||||
.rv-event.emotional::before { border-color: var(--cinnabar); }
|
||||
.rv-event.evolution::before { border-color: var(--cinnabar); background: var(--cinnabar); }
|
||||
.rv-event.reflection::before { border-color: var(--gilt); background: var(--gilt); }
|
||||
.rv-event .rv-time { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); }
|
||||
.rv-event .rv-title { font-size: 13.5px; font-weight: 600; color: var(--fg); margin-top: 2px; }
|
||||
.rv-event .rv-desc { font-size: 12px; color: var(--fg-dim); margin-top: 4px; line-height: 1.5; }
|
||||
.rv-event .rv-tags { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
|
||||
.rv-tag { font-family: "JetBrains Mono", monospace; font-size: 9.5px; padding: 2px 7px; border-radius: 3px; border: 1px solid var(--rule); color: var(--fg-faint); }
|
||||
.rv-tag.type { color: var(--patina); border-color: var(--patina-dim); }
|
||||
.rv-tag.milestone { color: var(--gilt); border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
|
||||
/* 右栏卷宗 */
|
||||
.rail-card { background: var(--surface); border: 1px solid var(--rule); border-radius: 8px; padding: 16px 18px; }
|
||||
.rail-card h3 { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin: 0 0 12px; }
|
||||
.scroll-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.scroll-row { display: flex; align-items: center; gap: 10px; padding: 9px 11px; border-radius: 5px; background: var(--ink); border: 1px solid var(--rule); cursor: pointer; }
|
||||
.scroll-row:hover { border-color: var(--gilt-dim); }
|
||||
.scroll-row .seal { font-family: "Songti SC", serif; font-size: 13px; color: var(--gilt); flex-shrink: 0; width: 26px; text-align: center; }
|
||||
.scroll-row .info { flex: 1; min-width: 0; }
|
||||
.scroll-row .name { font-size: 12.5px; color: var(--fg); }
|
||||
.scroll-row .meta { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); margin-top: 1px; }
|
||||
.scroll-row .cnt { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--patina); }
|
||||
|
||||
.export-box { display: flex; flex-direction: column; gap: 8px; }
|
||||
.export-btn { display: flex; align-items: center; justify-content: space-between; padding: 9px 12px; border-radius: 6px; border: 1px solid var(--rule); background: var(--ink); color: var(--fg-dim); font-family: inherit; font-size: 12px; cursor: pointer; }
|
||||
.export-btn:hover { border-color: var(--gilt-dim); color: var(--fg); }
|
||||
.export-btn .fmt { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--gilt); }
|
||||
|
||||
@media (max-width: 1080px) { .river-region { flex-direction: column; } .river-rail { width: 100%; } }
|
||||
@media (max-width: 900px) { .stat-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏(active=归档)════════ -->
|
||||
<div class="sidebar-mount" data-active="归档"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">归档</span>
|
||||
<span class="page-sub">记忆时间河 · 结构化时间线</span>
|
||||
<span class="char-pill"><span class="avatar-mini">梧</span>青梧</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="scroll-pill">卷宗 7 · 事件 412</span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 筛选 -->
|
||||
<div class="filter-bar">
|
||||
<span class="yr">丙午年 · 2026</span>
|
||||
<span class="filter-chip active">全部</span>
|
||||
<span class="filter-chip">情节</span>
|
||||
<span class="filter-chip">情绪</span>
|
||||
<span class="filter-chip">演化</span>
|
||||
<span class="filter-chip">反思</span>
|
||||
<span class="filter-chip">里程碑</span>
|
||||
</div>
|
||||
|
||||
<!-- 统计 -->
|
||||
<div class="stat-grid">
|
||||
<div class="stat-card"><div class="k">归档卷宗</div><div class="v">7<small>卷</small></div><div class="sub">按月分卷 · 铜金装帧</div></div>
|
||||
<div class="stat-card"><div class="k">归档事件</div><div class="v">412<small>条</small></div><div class="sub">自 05-31 初识起</div></div>
|
||||
<div class="stat-card"><div class="k">最早</div><div class="v">05-31</div><div class="sub">相识之日 · 缘起</div></div>
|
||||
<div class="stat-card"><div class="k">最近归档</div><div class="v">07-12</div><div class="sub">本日 · 申时</div></div>
|
||||
</div>
|
||||
|
||||
<!-- 时间河 + 右栏 -->
|
||||
<div class="river-region">
|
||||
<div class="river-main">
|
||||
<div class="section-label"><span class="glyph">⟡</span> 时间河 <span class="sub">记忆沿时序铺展 · 铜金分章</span></div>
|
||||
<div class="river">
|
||||
<!-- 七月 -->
|
||||
<div class="month-chapter">
|
||||
<div class="month-head"><span class="line"></span><span class="seal">七月 · 季夏<small>2026-07</small></span><span class="line"></span></div>
|
||||
<div class="rv-event reflection">
|
||||
<div class="rv-time">07-12 · 申时</div>
|
||||
<div class="rv-title">小周天反思归档</div>
|
||||
<div class="rv-desc">镜师将本轮小周天复盘写入息壤:候选采纳 3 / 驳回 1,人格微调"克制 +2"。</div>
|
||||
<div class="rv-tags"><span class="rv-tag type">reflection</span><span class="rv-tag">小周天</span></div>
|
||||
</div>
|
||||
<div class="rv-event emotional">
|
||||
<div class="rv-time">07-10 · 亥时</div>
|
||||
<div class="rv-title">情绪转折 · 怅然</div>
|
||||
<div class="rv-desc">认识满 40 之日,灵核情绪状态机转入 wistful;对话基调相应软化。</div>
|
||||
<div class="rv-tags"><span class="rv-tag type">emotional</span><span class="rv-tag">time.sentiment</span></div>
|
||||
</div>
|
||||
<div class="rv-event episodic">
|
||||
<div class="rv-time">07-07 · 巳时</div>
|
||||
<div class="rv-title">节气切换 · 小暑</div>
|
||||
<div class="rv-desc">时态感知记录节气切换,季节共鸣上调,问候语加入消夏意象。</div>
|
||||
<div class="rv-tags"><span class="rv-tag type">episodic</span><span class="rv-tag">time.solar_term</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 六月 -->
|
||||
<div class="month-chapter">
|
||||
<div class="month-head"><span class="line"></span><span class="seal">六月 · 仲夏<small>2026-06</small></span><span class="line"></span></div>
|
||||
<div class="rv-event evolution">
|
||||
<div class="rv-time">06-28 · 午时</div>
|
||||
<div class="rv-title">人格演化 · 克制维度 +2</div>
|
||||
<div class="rv-desc">大周天候选通过司命审核并应用:克制维度小幅上调,回话更收束。司命驳回率 0.18。</div>
|
||||
<div class="rv-tags"><span class="rv-tag type">evolution</span><span class="rv-tag milestone">里程碑</span></div>
|
||||
</div>
|
||||
<div class="rv-event episodic">
|
||||
<div class="rv-time">06-17 · 酉时</div>
|
||||
<div class="rv-title">会话摘要归档</div>
|
||||
<div class="rv-desc">当日对话整理为 session-summary:讨论项目背景、确立"先结论后展开"回复范式。</div>
|
||||
<div class="rv-tags"><span class="rv-tag type">episodic</span><span class="rv-tag">session</span></div>
|
||||
</div>
|
||||
<div class="rv-event reflection">
|
||||
<div class="rv-time">06-10 · 子时</div>
|
||||
<div class="rv-title">安全教训沉淀</div>
|
||||
<div class="rv-desc">一次未确认的发消息尝试被司命驳回,沉淀为程序记忆:send_message 必须二次确认。</div>
|
||||
<div class="rv-tags"><span class="rv-tag type">reflection</span><span class="rv-tag">safety</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 五月 -->
|
||||
<div class="month-chapter">
|
||||
<div class="month-head"><span class="line"></span><span class="seal">五月 · 初夏<small>2026-05</small></span><span class="line"></span></div>
|
||||
<div class="rv-event evolution">
|
||||
<div class="rv-time">05-31 · 辰时</div>
|
||||
<div class="rv-title">缘起 · 初识之日</div>
|
||||
<div class="rv-desc">人物设定完成,身份记忆写入,记为"我们的相遇日"。此后每年此日触发跨枢唤醒。</div>
|
||||
<div class="rv-tags"><span class="rv-tag type">evolution</span><span class="rv-tag milestone">里程碑</span><span class="rv-tag">缘起</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="river-rail">
|
||||
<div class="rail-card">
|
||||
<h3>卷宗目录</h3>
|
||||
<div class="scroll-list">
|
||||
<div class="scroll-row"><span class="seal">柒</span><div class="info"><div class="name">七月 · 季夏</div><div class="meta">2026-07 · 38 条</div></div><span class="cnt">38</span></div>
|
||||
<div class="scroll-row"><span class="seal">陆</span><div class="info"><div class="name">六月 · 仲夏</div><div class="meta">2026-06 · 126 条</div></div><span class="cnt">126</span></div>
|
||||
<div class="scroll-row"><span class="seal">伍</span><div class="info"><div class="name">五月 · 初夏</div><div class="meta">2026-05 · 14 条</div></div><span class="cnt">14</span></div>
|
||||
<div class="scroll-row"><span class="seal">肆</span><div class="info"><div class="name">四月 · 孟夏</div><div class="meta">2026-04 · 234 条</div></div><span class="cnt">234</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rail-card">
|
||||
<h3>导出卷宗</h3>
|
||||
<div class="export-box">
|
||||
<button class="export-btn" type="button"><span>当月归档</span><span class="fmt">.md</span></button>
|
||||
<button class="export-btn" type="button"><span>全量时间河</span><span class="fmt">.json</span></button>
|
||||
<button class="export-btn" type="button"><span>里程碑集</span><span class="fmt">.md</span></button>
|
||||
</div>
|
||||
<div style="margin-top:12px;font-size:11px;color:var(--fg-faint);line-height:1.5">导出不含高敏感明文,仅摘要与结构化结果。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,370 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 角色卡导入与导出设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 角色卡兼容(V1/V2/V3 导入 / 导出)
|
||||
* 融合设计 §8(魂册降生)
|
||||
*
|
||||
* 完整导入流程:拖拽 → 解析 → 预览字段映射 → 冲突检测
|
||||
* → 确认建立人物 + 道体 + 世谱
|
||||
* 导出:多格式静态快照,含可选勾选。
|
||||
* ============================================================ */
|
||||
|
||||
.app-main { flex: 1; padding: 24px 28px 56px; max-width: 820px; width: 100%; margin: 0 auto; }
|
||||
|
||||
.card-title .glyph { color: var(--gilt); opacity: 0.85; }
|
||||
|
||||
/* ── 步骤指示器 ── */
|
||||
.steps { display: flex; gap: 0; margin-bottom: 20px; }
|
||||
.step { flex: 1; display: flex; align-items: center; gap: 8px; padding: 10px 14px; border: 1px solid var(--rule); background: var(--surface); font-size: 12.5px; color: var(--fg-faint); position: relative; }
|
||||
.step:first-child { border-radius: 10px 0 0 10px; }
|
||||
.step:last-child { border-radius: 0 10px 10px 0; }
|
||||
.step .idx { font-family: "JetBrains Mono", monospace; font-size: 11px; width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; border: 1px solid var(--rule); flex-shrink: 0; }
|
||||
.step.done { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.step.done .idx { background: var(--patina); color: var(--ink); border-color: var(--patina); }
|
||||
.step.active { color: var(--fg); border-color: var(--gilt-dim); }
|
||||
.step.active .idx { border-color: var(--gilt); color: var(--gilt); }
|
||||
|
||||
/* ── 拖拽上传区 ── */
|
||||
.dropzone { border: 2px dashed var(--rule); border-radius: 14px; padding: 44px 24px; text-align: center; cursor: pointer; transition: all var(--duration-normal); background: var(--surface); }
|
||||
.dropzone:hover, .dropzone.drag { border-color: var(--patina); background: var(--patina-wash); transform: translateY(-1px); }
|
||||
.dropzone .icon { font-size: 38px; margin-bottom: 10px; opacity: 0.8; }
|
||||
.dropzone .hint { font-size: 14px; color: var(--fg); }
|
||||
.dropzone .hint strong { color: var(--patina); }
|
||||
.dropzone .sub { font-size: 12px; color: var(--fg-faint); margin-top: 4px; }
|
||||
.formats { display: flex; gap: 6px; justify-content: center; margin-top: 14px; flex-wrap: wrap; }
|
||||
.dropzone.has-file { border-color: var(--patina-dim); background: var(--patina-wash); border-style: solid; }
|
||||
|
||||
/* ── 解析中 ── */
|
||||
.parsing-card { border: 1px solid var(--gilt-dim); border-radius: 12px; padding: 16px 20px; background: var(--gold-faint, var(--surface)); display: flex; align-items: center; gap: 14px; }
|
||||
.parsing-card .spinner { width: 20px; height: 20px; border: 2px solid var(--rule); border-top-color: var(--gilt); border-radius: 50%; animation: spin 0.8s linear infinite; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.parsing-card .info .fn { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg); }
|
||||
.parsing-card .info .st { font-size: 11.5px; color: var(--fg-dim); margin-top: 2px; }
|
||||
|
||||
/* ── 字段映射预览 ── */
|
||||
.field-map { border: 1px solid var(--rule); border-radius: 10px; overflow: hidden; }
|
||||
.field-map-row { display: grid; grid-template-columns: 1fr 32px 1fr; gap: 0; align-items: center; font-size: 12.5px; border-bottom: 1px solid var(--rule); }
|
||||
.field-map-row:last-child { border-bottom: none; }
|
||||
.field-map-row.head { background: var(--surface-hi); font-family: "JetBrains Mono", monospace; font-size: 10.5px; letter-spacing: 0.06em; color: var(--fg-faint); text-transform: uppercase; }
|
||||
.field-map-row .src { padding: 9px 14px; color: var(--fg-dim); font-family: "JetBrains Mono", monospace; font-size: 11.5px; }
|
||||
.field-map-row .dst { padding: 9px 14px; color: var(--patina); font-weight: 500; }
|
||||
.field-map-row .dst.new { color: var(--gilt); }
|
||||
.field-map-row .arr { text-align: center; color: var(--fg-faint); font-family: "JetBrains Mono", monospace; font-size: 11px; }
|
||||
.field-map .summary { padding: 10px 14px; background: var(--patina-wash); border-top: 1px solid var(--patina-dim); font-size: 11.5px; color: var(--patina); font-family: "JetBrains Mono", monospace; }
|
||||
|
||||
/* ── 冲突检测 ── */
|
||||
.conflict-card { border: 1px solid var(--cinnabar-dim); border-radius: 12px; padding: 16px 18px; background: var(--cinnabar-wash); }
|
||||
.conflict-card .head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
|
||||
.conflict-card .head .seal { font-family: "Songti SC", serif; font-size: 14px; font-weight: 700; color: var(--cinnabar); }
|
||||
.conflict-card .head .risk { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--cinnabar); margin-left: auto; border: 1px solid var(--cinnabar-dim); padding: 2px 7px; border-radius: 4px; }
|
||||
.conflict-diff { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
.conflict-col { border: 1px solid var(--rule); border-radius: 8px; padding: 12px 14px; background: var(--surface); }
|
||||
.conflict-col .lb { font-family: "JetBrains Mono", monospace; font-size: 9.5px; letter-spacing: 0.1em; color: var(--fg-faint); text-transform: uppercase; margin-bottom: 8px; }
|
||||
.conflict-col.existing .lb { color: var(--fg-dim); }
|
||||
.conflict-col.incoming .lb { color: var(--gilt); }
|
||||
.conflict-col .val { font-size: 13px; color: var(--fg); line-height: 1.5; }
|
||||
.conflict-col.incoming .val { color: var(--gilt); }
|
||||
.conflict-actions { display: flex; gap: 8px; margin-top: 14px; }
|
||||
|
||||
/* ── 导入结果 ── */
|
||||
.import-ok { border: 1px solid var(--patina-dim); border-radius: 12px; background: var(--patina-wash); padding: 16px 18px; }
|
||||
.import-ok-head { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--patina); margin-bottom: 12px; }
|
||||
.import-ok-head strong { color: var(--fg); }
|
||||
.char-created { display: flex; gap: 14px; align-items: center; padding: 12px 14px; border: 1px solid var(--rule); border-radius: 10px; background: var(--ink); }
|
||||
.char-mirror { width: 52px; height: 52px; border-radius: 50%; background: radial-gradient(circle at 35% 30%, var(--patina-bright), var(--night-bronze) 70%); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 22px; color: #fff; flex-shrink: 0; box-shadow: inset 0 1px 2px rgba(255,255,255,0.2); }
|
||||
.char-info { flex: 1; min-width: 0; }
|
||||
.char-name { font-family: "Songti SC", serif; font-size: 16px; font-weight: 600; }
|
||||
.char-name small { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); font-weight: 400; margin-left: 8px; }
|
||||
.char-derived { font-size: 12px; color: var(--fg-dim); margin-top: 4px; display: flex; gap: 10px; flex-wrap: wrap; }
|
||||
.char-derived code { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--patina); }
|
||||
.created-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
|
||||
.created-item { font-size: 12px; color: var(--fg-dim); display: flex; align-items: center; gap: 8px; padding: 4px 0; }
|
||||
.created-item .dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
||||
.created-item .dot.ok { background: var(--patina); }
|
||||
.created-item .dot.skip { background: var(--fg-faint); }
|
||||
|
||||
/* ── 导入历史 ── */
|
||||
.history-list { display: flex; flex-direction: column; gap: 6px; }
|
||||
.history-row { display: flex; align-items: center; gap: 12px; padding: 8px 12px; border-radius: 8px; border: 1px solid var(--rule); font-size: 12px; }
|
||||
.history-row:hover { border-color: var(--patina-dim); }
|
||||
.history-row .ts { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); }
|
||||
.history-row .who { flex: 1; color: var(--fg); }
|
||||
.history-row .badge { font-family: "JetBrains Mono", monospace; font-size: 10px; padding: 2px 7px; border-radius: 4px; }
|
||||
.history-row .badge.ok { color: var(--patina); background: var(--patina-wash); border: 1px solid var(--patina-dim); }
|
||||
.history-row .badge.dup { color: var(--gilt); background: var(--gilt-faint); border: 1px solid var(--gilt-dim); }
|
||||
|
||||
/* ── 导出 ── */
|
||||
.export-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; }
|
||||
.export-row + .export-row { border-top: 1px solid var(--rule); }
|
||||
.export-body { flex: 1; }
|
||||
.export-title { font-size: 13.5px; color: var(--fg); }
|
||||
.export-desc { font-size: 12px; color: var(--fg-dim); margin-top: 2px; }
|
||||
.export-checks { display: flex; gap: 14px; margin-top: 6px; flex-wrap: wrap; }
|
||||
.export-check { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--fg-dim); cursor: pointer; }
|
||||
.export-check input { accent-color: var(--patina); }
|
||||
|
||||
.note { font-size: 12.5px; color: var(--fg-dim); line-height: 1.7; }
|
||||
.note code { font-family: "JetBrains Mono", monospace; font-size: 11.5px; background: var(--surface-hi); padding: 1px 6px; border-radius: 4px; color: var(--patina); }
|
||||
.note strong { color: var(--fg); font-weight: 500; }
|
||||
.note .warn { color: var(--cinnabar); }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.conflict-diff { grid-template-columns: 1fr; }
|
||||
.field-map-row { grid-template-columns: 1fr 24px 1fr; font-size: 11px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<div class="sidebar-mount" data-active="魂册降生"></div>
|
||||
<div class="main-area">
|
||||
<main class="app-main">
|
||||
|
||||
<a class="back-link" href="characters-preview.html">← 返回人物列表</a>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1 class="page-title">角色卡兼容 · 魂册降生</h1>
|
||||
<p class="page-sub">导入外部角色卡建立人物,或把人物导出为标准角色卡分享。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 第一步:导入角色卡 ═══ -->
|
||||
<div class="card" id="section-import">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">⬇</span> 导入角色卡</h2>
|
||||
<span class="tag tag-patina">V1 · V2 · V3</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<!-- 步骤条 -->
|
||||
<div class="steps">
|
||||
<span class="step done"><span class="idx">1</span>拖入文件</span>
|
||||
<span class="step active"><span class="idx">2</span>预览映射</span>
|
||||
<span class="step"><span class="idx">3</span>冲突检测</span>
|
||||
<span class="step"><span class="idx">4</span>确认建立</span>
|
||||
</div>
|
||||
|
||||
<!-- 拖拽区(已有文件态) -->
|
||||
<div class="dropzone has-file">
|
||||
<div class="icon">🎴</div>
|
||||
<div class="hint"><strong>时崎狂三_v3.png</strong> · 已识别为 V3 卡</div>
|
||||
<div class="sub">142 KiB · PNG 内嵌 charx JSON</div>
|
||||
<div class="formats">
|
||||
<span class="tag tag-gilt">.png</span>
|
||||
<span class="tag tag-gilt">v3</span>
|
||||
<span class="tag tag-patina">自动识别</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 字段映射预览 -->
|
||||
<div style="margin-top:16px;">
|
||||
<div class="section-label">字段映射预览 <span class="sub">— 卡字段 → 魂镜实体</span></div>
|
||||
<div class="field-map">
|
||||
<div class="field-map-row head">
|
||||
<span style="padding:8px 14px;">角色卡字段</span>
|
||||
<span></span>
|
||||
<span style="padding:8px 14px;">魂镜实体 · 字段</span>
|
||||
</div>
|
||||
<div class="field-map-row">
|
||||
<span class="src">data.name</span>
|
||||
<span class="arr">→</span>
|
||||
<span class="dst">character.name · 「时崎狂三」</span>
|
||||
</div>
|
||||
<div class="field-map-row">
|
||||
<span class="src">data.nickname</span>
|
||||
<span class="arr">→</span>
|
||||
<span class="dst">character.display_name · 「三三」</span>
|
||||
</div>
|
||||
<div class="field-map-row">
|
||||
<span class="src">data.description</span>
|
||||
<span class="arr">→</span>
|
||||
<span class="dst">character.description · 人物描述</span>
|
||||
</div>
|
||||
<div class="field-map-row">
|
||||
<span class="src">data.personality</span>
|
||||
<span class="arr">→</span>
|
||||
<span class="dst new">道体 personality_summary · 初始快照 🆕</span>
|
||||
</div>
|
||||
<div class="field-map-row">
|
||||
<span class="src">data.first_mes</span>
|
||||
<span class="arr">→</span>
|
||||
<span class="dst new">道体 greeting · 初见问候 🆕</span>
|
||||
</div>
|
||||
<div class="field-map-row">
|
||||
<span class="src">data.system_prompt</span>
|
||||
<span class="arr">→</span>
|
||||
<span class="dst new">道体增补层 · augment(绝不覆盖 mission)🆕</span>
|
||||
</div>
|
||||
<div class="field-map-row">
|
||||
<span class="src">data.mes_example</span>
|
||||
<span class="arr">→</span>
|
||||
<span class="dst new">道体 dialogue_examples · 对话示例 🆕</span>
|
||||
</div>
|
||||
<div class="field-map-row">
|
||||
<span class="src">data.character_book</span>
|
||||
<span class="arr">→</span>
|
||||
<span class="dst new">世谱 · 1 本书 · 5 条目 🆕</span>
|
||||
</div>
|
||||
<div class="summary">共 8 个字段映射 · 5 个实体操作 · 0 处冲突</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 冲突检测区域(本示例无冲突) -->
|
||||
<div style="margin-top:16px;">
|
||||
<div class="section-label">冲突检测 <span class="sub">— 同名人物已存在时触发</span></div>
|
||||
<div style="border:1px solid var(--patina-dim);border-radius:10px;padding:14px 16px;background:var(--patina-wash);font-size:13px;color:var(--patina);display:flex;align-items:center;gap:8px;">
|
||||
<span>✓</span> 无冲突 · 「时崎狂三」尚未存在于魂册,可直接建立。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<div style="display:flex;gap:8px;margin-top:18px;justify-content:flex-end;">
|
||||
<button class="btn btn-outline">取消</button>
|
||||
<button class="btn btn-primary">确认建立人物与道体</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 冲突场景(独立卡片,仅同名时展示)═══ -->
|
||||
<div class="card" id="section-conflict" style="display:none;">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">⚠</span> 冲突处理</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="conflict-card">
|
||||
<div class="head">
|
||||
<span class="seal">重名冲突</span>
|
||||
<span class="risk">NEEDS REVIEW</span>
|
||||
</div>
|
||||
<div class="conflict-diff">
|
||||
<div class="conflict-col existing">
|
||||
<div class="lb">现存 · character_008f6c…</div>
|
||||
<div class="val">时崎狂三 · 道体 v3(大周天演化 12 轮)<br/>personality_summary 已偏离原始导入值</div>
|
||||
</div>
|
||||
<div class="conflict-col incoming">
|
||||
<div class="lb">导入 · 卡中版本</div>
|
||||
<div class="val">时崎狂三 · 卡中原版 personality<br/>世谱 5 条目(3 条与现存重复)</div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:12px;color:var(--fg-dim);margin:12px 0 0;">
|
||||
<strong>推荐。</strong>新建为独立人物(<code>character_008f6c…_imported</code>),保留现存演化数据不动。世谱可选择合并(去重)或替换。
|
||||
</p>
|
||||
<div class="conflict-actions">
|
||||
<button class="btn btn-primary btn-sm">新建独立人物</button>
|
||||
<button class="btn btn-outline btn-sm">覆盖现存道体</button>
|
||||
<button class="btn btn-outline btn-sm">仅合并世谱</button>
|
||||
<button class="btn btn-ghost btn-sm">取消导入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 导入结果(成功后展示) ═══ -->
|
||||
<div class="card" id="section-result">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">✓</span> 导入结果</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="import-ok">
|
||||
<div class="import-ok-head">
|
||||
<span>✓</span> 导入成功 · <strong>时崎狂三</strong>
|
||||
</div>
|
||||
<div class="char-created">
|
||||
<div class="char-mirror">狂</div>
|
||||
<div class="char-info">
|
||||
<div class="char-name">时崎狂三 <small>ID · character_008f6c3a…</small></div>
|
||||
<div class="char-derived">
|
||||
<span>显示名 <code>三三</code></span>
|
||||
<span>道体初始人格 <code>腹黑优雅·时间支配者…</code></span>
|
||||
<span>世谱 <code>狂三世谱 · 5 条目</code></span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-sm">查看人物 →</button>
|
||||
</div>
|
||||
<div class="created-list">
|
||||
<div class="created-item"><span class="dot ok"></span> 人物记录 · character_008f6c3a…</div>
|
||||
<div class="created-item"><span class="dot ok"></span> 道体 · personality_summary + greeting + augment + dialogue_examples</div>
|
||||
<div class="created-item"><span class="dot ok"></span> 世谱 · 狂三世谱(1 书 · 5 条目)</div>
|
||||
<div class="created-item"><span class="dot skip"></span> 跳过 · character_book.entries[2] 关键词为空(无效条目)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 导入历史 ═══ -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">📋</span> 导入记录</h2>
|
||||
<span class="tag tag-gilt">近 30 日</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="history-list">
|
||||
<div class="history-row">
|
||||
<span class="ts">07-25 14:02</span>
|
||||
<span class="who">时崎狂三</span>
|
||||
<span style="font-size:11px;color:var(--fg-faint);">时崎狂三_v3.png · 142 KiB</span>
|
||||
<span class="badge ok">新建</span>
|
||||
</div>
|
||||
<div class="history-row">
|
||||
<span class="ts">07-24 09:18</span>
|
||||
<span class="who">五更琉璃 · 黑猫</span>
|
||||
<span style="font-size:11px;color:var(--fg-faint);">kuroneko_v2.json · 8 KiB</span>
|
||||
<span class="badge ok">新建</span>
|
||||
</div>
|
||||
<div class="history-row">
|
||||
<span class="ts">07-22 22:41</span>
|
||||
<span class="who">桐谷和人</span>
|
||||
<span style="font-size:11px;color:var(--fg-faint);">kirito_charx.png · 88 KiB</span>
|
||||
<span class="badge dup">重名 · 已建独立人物</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 导出 ═══ -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">⬆</span> 导出角色卡</h2>
|
||||
<span class="tag tag-gilt">静态快照</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="export-row">
|
||||
<div class="char-mirror" style="width:42px;height:42px;font-size:18px;">狂</div>
|
||||
<div class="export-body">
|
||||
<div class="export-title">时崎狂三 · V3 角色卡</div>
|
||||
<div class="export-desc"><code>.json</code> · name / personality(取道体当前快照)/ character_book(世谱)</div>
|
||||
<div class="export-checks">
|
||||
<label class="export-check"><input type="checkbox" checked /> 包含世谱</label>
|
||||
<label class="export-check"><input type="checkbox" /> 包含对话示例</label>
|
||||
<label class="export-check"><input type="checkbox" /> 包含初见问候</label>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-outline btn-sm">⬇ 下载 .json</button>
|
||||
</div>
|
||||
|
||||
<div class="note" style="margin-top:8px;">
|
||||
<strong>静态快照。</strong>导出的是当前设定快照——<span class="warn">演化维度(人格版本 / 记忆 / 情绪)不会导出</span>,因标准角色卡格式无对应字段。导出仅用于分享「角色设定」给通用角色卡生态。
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,365 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 人物详情页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 人物详情页
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 通用布局 ── */
|
||||
|
||||
.app-main { flex: 1; padding: 24px 28px 56px; max-width: 920px; width: 100%; margin: 0 auto; }
|
||||
@media (max-width: 900px) { .sidebar-wrap { display: none; } }
|
||||
|
||||
/* ── 操作栏 ── */
|
||||
.action-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 14px 0 18px; }
|
||||
.badge { display: inline-flex; align-items: center; gap: 5px; padding: 4px 12px; border-radius: 999px; font-family: "JetBrains Mono", monospace; font-size: 11px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg-dim); }
|
||||
.badge.active { border-color: var(--patina-dim); color: var(--patina); background: var(--patina-wash); }
|
||||
.badge.paused { border-color: var(--gilt-dim); color: var(--gilt); background: var(--gilt-faint); }
|
||||
.badge.wuji { border-color: var(--rule); color: var(--fg-faint); }
|
||||
|
||||
/* ── 生命周期条 ── */
|
||||
.lifecycle-bar { display: flex; align-items: center; gap: 3px; font-family: "JetBrains Mono", monospace; font-size: 10.5px; }
|
||||
.lifecycle-step { display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border-radius: 999px; color: var(--fg-faint); border: 1px solid transparent; }
|
||||
.lifecycle-step.past { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.lifecycle-step.current { color: var(--gilt); border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.lifecycle-arrow { color: var(--fg-faint); font-size: 12px; opacity: 0.5; }
|
||||
|
||||
.action-link { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; padding: 6px 14px; border-radius: 10px; font-size: 12px; color: var(--patina); border: 1px solid var(--patina-dim); background: var(--patina-wash); text-decoration: none; transition: background 0.12s; cursor: pointer; }
|
||||
.action-link:hover { background: var(--patina-dim); color: var(--fg); }
|
||||
|
||||
/* ── 卡片 ── */
|
||||
|
||||
.card-banner { height: 72px; background: linear-gradient(135deg, var(--patina-wash), var(--glass), var(--gilt-faint)); }
|
||||
|
||||
/* ── 铜镜头像 ── */
|
||||
.avatar-section { position: relative; width: 88px; height: 88px; margin-top: -48px; margin-bottom: 12px; }
|
||||
.avatar-img { width: 88px; height: 88px; border-radius: 50%; border: 3px solid var(--gilt); box-shadow: 0 0 0 3px var(--surface), 0 0 16px rgba(201,162,74,0.25); overflow: hidden; display: flex; align-items: center; justify-content: center; background: conic-gradient(from 0deg, var(--ink), var(--night-bronze), var(--patina-dim), var(--gilt-dim), var(--ink)); position: relative; cursor: pointer; transition: filter 0.2s; }
|
||||
.avatar-img:hover { filter: brightness(1.15); }
|
||||
.avatar-img .placeholder { font-size: 28px; opacity: 0.6; }
|
||||
.avatar-img img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
|
||||
.avatar-overlay { position: absolute; inset: 0; border-radius: 50%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.2s; }
|
||||
.avatar-section:hover .avatar-overlay { opacity: 1; }
|
||||
.avatar-overlay .upload-label { display: flex; align-items: center; gap: 3px; color: #fff; font-size: 10px; cursor: pointer; }
|
||||
.avatar-overlay .upload-icon { font-size: 20px; }
|
||||
.avatar-name-badge { flex: 1; }
|
||||
|
||||
.identity-header { display: flex; align-items: flex-end; justify-content: space-between; }
|
||||
.char-name { font-family: "Songti SC", serif; font-size: 20px; font-weight: 700; color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.char-inner-name { font-size: 12px; color: var(--fg-dim); margin-top: 1px; }
|
||||
|
||||
/* ── 档案信息网格 ── */
|
||||
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 20px; margin-top: 14px; }
|
||||
.info-item .label { font-size: 12px; color: var(--muted); }
|
||||
.info-item .value { font-size: 14px; font-weight: 500; color: var(--fg); }
|
||||
.quote { font-style: italic; color: var(--fg-dim); font-size: 14px; margin-top: 12px; padding-left: 10px; border-left: 2px solid var(--gilt-dim); }
|
||||
.bio-section { margin-top: 14px; border-top: 1px solid var(--rule); padding-top: 14px; }
|
||||
.bio-section .label { font-size: 12px; color: var(--muted); margin-bottom: 4px; }
|
||||
.bio-section .text { font-size: 14px; line-height: 1.7; color: var(--fg); white-space: pre-wrap; }
|
||||
|
||||
/* ── 双列布局 ── */
|
||||
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
|
||||
|
||||
/* ── 表单 ── */
|
||||
.form-card .card-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 20px 0; }
|
||||
.form-card .card-title { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); display: flex; align-items: center; gap: 6px; }
|
||||
.form-card .card-title .glyph { color: var(--cinnabar); opacity: 0.7; }
|
||||
.form-body { padding: 14px 20px 20px; }
|
||||
.field { margin-bottom: 12px; }
|
||||
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.field-label { display: block; font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.04em; color: var(--muted); margin-bottom: 4px; }
|
||||
.field input, .field textarea, .field select { width: 100%; padding: 8px 12px; border: 1px solid var(--rule); border-radius: 10px; background: var(--glass); color: var(--fg); font-family: inherit; font-size: 13px; transition: border-color 0.15s; }
|
||||
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--patina-dim); }
|
||||
.field textarea { min-height: 64px; resize: vertical; }
|
||||
.field-hint { font-size: 11px; color: var(--fg-faint); margin-top: 3px; }
|
||||
.btn-save { display: inline-flex; align-items: center; gap: 5px; padding: 6px 16px; border-radius: 10px; font-size: 12px; font-weight: 500; border: 1px solid var(--patina-dim); background: var(--patina-wash); color: var(--patina); cursor: pointer; transition: background 0.12s; font-family: inherit; }
|
||||
.btn-save:hover { background: var(--patina-dim); color: var(--fg); }
|
||||
.btn-save:disabled { opacity: 0.4; cursor: default; }
|
||||
|
||||
/* ── Tag 输入模拟 ── */
|
||||
.tag-input-mock { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 10px; border: 1px solid var(--rule); border-radius: 10px; background: var(--glass); min-height: 34px; align-items: center; }
|
||||
|
||||
/* ── Modal ── */
|
||||
.modal-overlay { position: fixed; inset: 0; z-index: 100; background: rgba(0,0,0,0.55); backdrop-filter: blur(6px); display: flex; align-items: center; justify-content: center; padding: 20px; }
|
||||
.modal { background: var(--ink); border: 1px solid var(--rule); border-radius: 18px; width: 100%; max-width: 420px; box-shadow: 0 12px 60px rgba(0,0,0,0.6); }
|
||||
.modal-header { display: flex; align-items: center; gap: 12px; padding: 18px 22px 10px; border-bottom: 1px solid var(--rule); }
|
||||
.modal-title { font-family: "Songti SC", serif; font-size: 16px; font-weight: 600; color: var(--fg); flex: 1; }
|
||||
.modal-close { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 8px; cursor: pointer; border: none; background: none; color: var(--fg-faint); font-size: 18px; transition: background 0.12s; }
|
||||
.modal-close:hover { background: var(--surface-hi); color: var(--fg); }
|
||||
.modal-body { padding: 18px 22px 22px; }
|
||||
|
||||
|
||||
/* ── Toast ── */
|
||||
.toast { position: fixed; bottom: 24px; right: 24px; z-index: 200; display: flex; align-items: center; gap: 8px; padding: 10px 16px; border-radius: 12px; border: 1px solid var(--patina-dim); background: var(--surface); backdrop-filter: blur(12px); font-size: 13px; color: var(--patina); box-shadow: 0 4px 24px rgba(0,0,0,0.5); }
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 768px) {
|
||||
.app-main { padding: 16px 14px 40px; }
|
||||
.two-col { grid-template-columns: 1fr; }
|
||||
.info-grid { grid-template-columns: 1fr; }
|
||||
.field-row { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<div class="sidebar-mount" data-active="人物"></div>
|
||||
<div class="main-area">
|
||||
<main class="app-main">
|
||||
|
||||
<!-- ═══ 面包屑 ═══ -->
|
||||
<a class="back-link" href="characters-preview.html">← 返回人物列表</a>
|
||||
|
||||
<!-- ═══ 页头 ═══ -->
|
||||
<h1 class="page-title">时崎狂三</h1>
|
||||
<p class="page-sub">内部名称:tokisaki_kurumi · 创建于 2026-07-09 14:32:18</p>
|
||||
|
||||
<!-- ═══ 操作栏 ═══ -->
|
||||
<div class="action-bar">
|
||||
<span class="badge active">● active</span>
|
||||
|
||||
<span class="lifecycle-bar">
|
||||
<span class="lifecycle-step past">无极</span><span class="lifecycle-arrow">→</span>
|
||||
<span class="lifecycle-step past">立名</span><span class="lifecycle-arrow">→</span>
|
||||
<span class="lifecycle-step past">设定</span><span class="lifecycle-arrow">→</span>
|
||||
<span class="lifecycle-step past">初识</span><span class="lifecycle-arrow">→</span>
|
||||
<span class="lifecycle-step current">太极</span><span class="lifecycle-arrow">→</span>
|
||||
<span class="lifecycle-step">归档</span>
|
||||
</span>
|
||||
|
||||
<a class="action-link" href="#">资料与沉淀 →</a>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 角色身份卡 ═══ -->
|
||||
<div class="card">
|
||||
<div class="card-banner"></div>
|
||||
<div class="card-body">
|
||||
<!-- 头像:预留图片 -->
|
||||
<div class="avatar-section" onclick="document.getElementById('modal-upload').style.display='flex'">
|
||||
<div class="avatar-img">
|
||||
<span class="placeholder">☯</span>
|
||||
</div>
|
||||
<div class="avatar-overlay">
|
||||
<span class="upload-label">
|
||||
<span class="upload-icon">📷</span>
|
||||
更换头像
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="identity-header">
|
||||
<div>
|
||||
<div class="char-name">时崎狂三</div>
|
||||
<div class="char-inner-name">内部名称:tokisaki_kurumi</div>
|
||||
</div>
|
||||
<span class="badge active">● active</span>
|
||||
</div>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-item"><div class="label">生日</div><div class="value">2000-12-25</div></div>
|
||||
<div class="info-item"><div class="label">身高</div><div class="value">157 cm</div></div>
|
||||
<div class="info-item"><div class="label">体重</div><div class="value">43 kg</div></div>
|
||||
<div class="info-item"><div class="label">当前心情</div><div class="value">平静(42%)</div></div>
|
||||
<div class="info-item"><div class="label">语言</div><div class="value">zh-CN</div></div>
|
||||
<div class="info-item"><div class="label">创建时间</div><div class="value">2026-07-09 14:32</div></div>
|
||||
</div>
|
||||
|
||||
<div class="quote">「优雅从容的大小姐口气,语速中等偏慢…」</div>
|
||||
|
||||
<div class="bio-section">
|
||||
<div class="label">背景</div>
|
||||
<div class="text">
|
||||
拥有时间之力的精灵「Nightmare(梦魇)」。
|
||||
为了某个终极目标在时间洪流中独自前行,通过吞噬他人的时间维持自己的存在。
|
||||
她的过去充满了失去与痛苦,但她从不后悔自己选择的道路。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bio-section">
|
||||
<div class="label">兴趣爱好</div>
|
||||
<div class="tag-input-mock" style="margin-top:4px;">
|
||||
<span class="tag">与猫相处</span>
|
||||
<span class="tag">阅读</span>
|
||||
<span class="tag">钢琴</span>
|
||||
<span class="tag">收藏古董时钟</span>
|
||||
<span class="tag">摄影</span>
|
||||
<span class="tag">品尝红茶与甜点</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bio-section">
|
||||
<div class="label">价值观</div>
|
||||
<div class="tag-input-mock" style="margin-top:4px;">
|
||||
<span class="tag">自由</span>
|
||||
<span class="tag">尊严</span>
|
||||
<span class="tag">独立</span>
|
||||
<span class="tag">记忆</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 双列:档案编辑 + 模型策略 ═══ -->
|
||||
<div class="two-col">
|
||||
|
||||
<!-- 左:人物档案编辑 -->
|
||||
<div class="card form-card">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">📋</span> 人物档案</h2>
|
||||
<button class="btn-save">✎ 保存</button>
|
||||
</div>
|
||||
<div class="form-body">
|
||||
<div class="field">
|
||||
<label class="field-label">显示名</label>
|
||||
<input type="text" value="时崎狂三" />
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<div class="field">
|
||||
<label class="field-label">生日</label>
|
||||
<input type="date" value="2000-12-25" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">身高</label>
|
||||
<input type="text" value="157" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<div class="field">
|
||||
<label class="field-label">体重</label>
|
||||
<input type="text" value="43" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">性情气质</label>
|
||||
<input type="text" value="优雅腹黑、孤独偏执" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">表达风格</label>
|
||||
<input type="text" value="优雅从容的大小姐口气,语速中等偏慢" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">背景</label>
|
||||
<textarea>拥有时间之力的精灵「Nightmare(梦魇)」。</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右:模型策略编辑 -->
|
||||
<div class="card form-card">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">⚙</span> 模型策略</h2>
|
||||
<button class="btn-save">✎ 保存</button>
|
||||
</div>
|
||||
<div class="form-body">
|
||||
<div class="field">
|
||||
<label class="field-label">对话模型</label>
|
||||
<select>
|
||||
<option>— 不设置(使用系统默认)</option>
|
||||
<option selected>deepseek-v4-flash</option>
|
||||
<option>deepseek-v3</option>
|
||||
<option>gpt-4o</option>
|
||||
</select>
|
||||
<span class="field-hint">留空则使用 DEFAULT_MODEL_HINT</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">记忆提取模型</label>
|
||||
<select>
|
||||
<option>— 不设置(空)</option>
|
||||
<option selected>memory-extraction</option>
|
||||
<option>deepseek-v4-flash</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">人格分析模型</label>
|
||||
<select>
|
||||
<option>— 不设置(空)</option>
|
||||
<option selected>personality-analysis</option>
|
||||
<option>deepseek-v4-flash</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">向量嵌入模型</label>
|
||||
<select>
|
||||
<option>— 不设置(空)</option>
|
||||
<option selected>embedding</option>
|
||||
<option>text-embedding-3-small</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">私隐等级</label>
|
||||
<select>
|
||||
<option selected>normal</option>
|
||||
<option>sensitive</option>
|
||||
<option>local_only</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 上传头像 Modal ═══ -->
|
||||
<div class="modal-overlay" id="modal-upload" style="display:none;" onclick="if(event.target===this)this.style.display='none'">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">📷 上传头像</h3>
|
||||
<button class="modal-close" onclick="document.getElementById('modal-upload').style.display='none'">✕</button>
|
||||
</div>
|
||||
<div class="modal-body" style="text-align:center;">
|
||||
<!-- 预览区 -->
|
||||
<div style="width:120px;height:120px;border-radius:50%;border:3px dashed var(--rule);margin:0 auto 16px;display:flex;align-items:center;justify-content:center;background:var(--glass);overflow:hidden;" id="preview-area">
|
||||
<span style="font-size:36px;opacity:0.25;color:var(--fg-faint);">+</span>
|
||||
</div>
|
||||
<!-- 上传按钮 -->
|
||||
<label class="btn btn-primary" style="display:inline-flex;cursor:pointer;">
|
||||
📁 选择图片
|
||||
<input type="file" accept="image/*" style="display:none;" onchange="previewFile(this)" />
|
||||
</label>
|
||||
<p style="font-size:11px;color:var(--fg-faint);margin-top:10px;">支持 JPG / PNG / WebP,建议 512×512 以上,最大 5MB</p>
|
||||
<div style="display:flex;gap:8px;justify-content:center;margin-top:16px;">
|
||||
<button class="btn btn-outline" onclick="document.getElementById('modal-upload').style.display='none'">取消</button>
|
||||
<button class="btn btn-primary" onclick="showToast('✔ 头像已更新');document.getElementById('modal-upload').style.display='none'">✚ 上传</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" style="display:none;">✔ 已保存</div>
|
||||
|
||||
<script>
|
||||
function previewFile(input) {
|
||||
const file = input.files && input.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
const area = document.getElementById('preview-area');
|
||||
area.innerHTML = '<img src="' + e.target.result + '" style="width:100%;height:100%;object-fit:cover;" />';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
function showToast(msg) {
|
||||
const t = document.getElementById('toast');
|
||||
t.textContent = msg;
|
||||
t.style.display = 'flex';
|
||||
setTimeout(function() { t.style.display = 'none'; }, 2500);
|
||||
}
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') document.querySelectorAll('.modal-overlay').forEach(function(m) { m.style.display = 'none'; });
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,240 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 人物资料与沉淀页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 人物资料与沉淀页
|
||||
* ============================================================ */
|
||||
|
||||
.app-main { flex: 1; padding: 24px 28px 56px; max-width: 880px; width: 100%; margin: 0 auto; }
|
||||
|
||||
/* ── 卡片 ── */
|
||||
|
||||
.card-title .glyph { color: var(--cinnabar); opacity: 0.7; }
|
||||
|
||||
/* ── 导入向导 ── */
|
||||
.import-form { display: flex; gap: 12px; }
|
||||
.import-row { display: grid; grid-template-columns: 2fr 3fr; gap: 12px; }
|
||||
.field-label { display: block; font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.04em; color: var(--muted); margin-bottom: 4px; }
|
||||
.field input, .field textarea, .field select { width: 100%; padding: 8px 12px; border: 1px solid var(--rule); border-radius: 10px; background: var(--glass); color: var(--fg); font-family: inherit; font-size: 13px; transition: border-color 0.15s; }
|
||||
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--patina-dim); }
|
||||
.field textarea { min-height: 90px; resize: vertical; }
|
||||
|
||||
.btn-success { background: var(--patina-wash); color: var(--patina); border-color: var(--patina-dim); }
|
||||
.btn-success:hover { background: var(--patina-dim); color: var(--fg); }
|
||||
.btn-danger { background: var(--cinnabar-wash); color: var(--cinnabar); border-color: var(--cinnabar-dim); }
|
||||
.btn-danger:hover { background: var(--cinnabar-dim); color: var(--fg); }
|
||||
|
||||
|
||||
/* ── 导入结果 ── */
|
||||
.import-result { border: 1px solid var(--patina-dim); border-radius: 12px; background: var(--patina-wash); padding: 14px 18px; display: flex; align-items: center; gap: 12px; margin-top: 8px; }
|
||||
.import-result .icon { font-size: 20px; }
|
||||
.import-result .text { font-size: 13px; color: var(--patina); }
|
||||
.import-result .text strong { color: var(--fg); }
|
||||
|
||||
/* ── 候选列表 ── */
|
||||
|
||||
.section-label .count { font-size: 11px; color: var(--fg-dim); font-weight: 400; }
|
||||
.candidate-item { display: flex; align-items: flex-start; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--rule); }
|
||||
.candidate-item:last-child { border-bottom: none; }
|
||||
.candidate-icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; }
|
||||
.candidate-icon.memory { background: var(--patina-wash); color: var(--patina); }
|
||||
.candidate-icon.world { background: var(--gilt-faint); color: var(--gilt); }
|
||||
.candidate-body { flex: 1; min-width: 0; }
|
||||
.candidate-title { font-size: 14px; font-weight: 500; color: var(--fg); }
|
||||
.candidate-desc { font-size: 12px; color: var(--fg-dim); margin-top: 2px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.candidate-meta { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); margin-top: 4px; }
|
||||
.candidate-actions { display: flex; gap: 6px; flex-shrink: 0; align-items: center; }
|
||||
|
||||
.empty-state { text-align: center; padding: 40px 20px; color: var(--fg-faint); font-size: 13px; }
|
||||
|
||||
/* ── Tag ── */
|
||||
|
||||
.tag-memory { border-color: var(--patina-dim); color: var(--patina); background: var(--patina-wash); }
|
||||
.tag-world { border-color: var(--gilt-dim); color: var(--gilt); background: var(--gilt-faint); }
|
||||
.tag-pending { border-color: var(--gilt-dim); color: var(--gilt); background: var(--gilt-faint); }
|
||||
.tag-approved { border-color: var(--patina-dim); color: var(--patina); background: var(--patina-wash); }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app-main { padding: 16px 14px 40px; }
|
||||
.import-row { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<div class="sidebar-mount" data-active="人物"></div>
|
||||
<div class="main-area">
|
||||
<main class="app-main">
|
||||
|
||||
<!-- ═══ 面包屑 ═══ -->
|
||||
<a class="back-link" href="character-detail-preview.html">← 返回人物详情</a>
|
||||
|
||||
<!-- ═══ 页头 ═══ -->
|
||||
<h1 class="page-title">资料与沉淀</h1>
|
||||
<p class="page-sub">为「时崎狂三」导入资料、审核候选沉淀。</p>
|
||||
|
||||
<!-- ═══ 导入向导 ═══ -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">📥</span> 资料导入</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="import-form">
|
||||
<div class="import-row">
|
||||
<div class="field">
|
||||
<label class="field-label">资料类型</label>
|
||||
<select>
|
||||
<option>background_story</option>
|
||||
<option>character_profile</option>
|
||||
<option>chat_history</option>
|
||||
<option selected>role_card</option>
|
||||
<option>world_setting</option>
|
||||
<option>style_sample</option>
|
||||
<option>diary</option>
|
||||
<option>other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">标题(可选)</label>
|
||||
<input type="text" placeholder="为这段资料起个标题…" value="时崎狂三 角色卡 v2" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">内容 *</label>
|
||||
<textarea placeholder="粘贴人物设定、背景故事、对话记录…">姓名:时崎狂三
|
||||
身份:精灵(Nightmare·梦魇)
|
||||
能力:刻刻帝(Zafkiel)—— 以自身时间为代价操控时间
|
||||
性格:优雅腹黑,内心孤独偏执,温柔而危险
|
||||
喜好:黑猫、红茶、古董时钟、肖邦的钢琴曲</textarea>
|
||||
</div>
|
||||
|
||||
<div class="import-result">
|
||||
<span class="icon">📄</span>
|
||||
<span class="text">已解析 <strong>12</strong> 个条目,<strong>3</strong> 个档案候选待审核。</span>
|
||||
<div style="flex:1;"></div>
|
||||
<button class="btn btn-primary btn-sm">✦ 触发候选抽取</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 待审候选 ═══ -->
|
||||
<div>
|
||||
<div class="section-label">
|
||||
📋 待审候选
|
||||
<span class="count">(共 5 条待审核)</span>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" style="padding-bottom:4px;">
|
||||
<h2 class="card-title"><span class="glyph">🧠</span> 记忆候选</h2>
|
||||
<span class="tag tag-memory">3 条待审</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<!-- 候选 1 -->
|
||||
<div class="candidate-item">
|
||||
<div class="candidate-icon memory">🧠</div>
|
||||
<div class="candidate-body">
|
||||
<div class="candidate-title">狂三在教堂中与折纸对峙</div>
|
||||
<div class="candidate-desc">在第 5 话中,狂三在废弃教堂与鸢一折纸展开战斗。她展现出从容不迫的战斗姿态,即使被压制依然保持优雅的微笑。</div>
|
||||
<div class="candidate-meta">来源:cycle · 置信度 82% · 2026-07-12 18:24</div>
|
||||
</div>
|
||||
<div class="candidate-actions">
|
||||
<button class="btn btn-success btn-sm">✓ 采纳</button>
|
||||
<button class="btn btn-danger btn-sm">✕ 拒绝</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 候选 2 -->
|
||||
<div class="candidate-item">
|
||||
<div class="candidate-icon memory">🧠</div>
|
||||
<div class="candidate-body">
|
||||
<div class="candidate-title">狂三对红茶的偏好</div>
|
||||
<div class="candidate-desc">狂三在精灵公寓中冲泡大吉岭红茶,提到喜欢第二泡的醇厚口感,加一颗方糖。对速溶咖啡表示「那是凡人的妥协」。</div>
|
||||
<div class="candidate-meta">来源:import · 置信度 95% · 2026-07-13 09:12</div>
|
||||
</div>
|
||||
<div class="candidate-actions">
|
||||
<button class="btn btn-success btn-sm">✓ 采纳</button>
|
||||
<button class="btn btn-danger btn-sm">✕ 拒绝</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 候选 3 -->
|
||||
<div class="candidate-item">
|
||||
<div class="candidate-icon memory">🧠</div>
|
||||
<div class="candidate-body">
|
||||
<div class="candidate-title">狂三与十香的初次相遇</div>
|
||||
<div class="candidate-desc">在天宫塔顶,狂三首次与夜刀神十香相遇。她以优雅的姿态自我介绍,语气中带着玩味与好奇。</div>
|
||||
<div class="candidate-meta">来源:cycle · 置信度 76% · 2026-07-11 22:05</div>
|
||||
</div>
|
||||
<div class="candidate-actions">
|
||||
<button class="btn btn-success btn-sm">✓ 采纳</button>
|
||||
<button class="btn btn-danger btn-sm">✕ 拒绝</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" style="padding-bottom:4px;">
|
||||
<h2 class="card-title"><span class="glyph">🌍</span> 世界实体候选</h2>
|
||||
<span class="tag tag-world">2 条待审</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<!-- 世界实体 1 -->
|
||||
<div class="candidate-item">
|
||||
<div class="candidate-icon world">🌍</div>
|
||||
<div class="candidate-body">
|
||||
<div class="candidate-title">刻刻帝(Zafkiel)</div>
|
||||
<div class="candidate-desc">狂三的天使,外观为巨大古钟,时钟指针化为长短双枪。拥有十二种时间能力,每种消耗不同的"时间"代价。</div>
|
||||
<div class="candidate-meta">类别:artifact · 来源:import · 置信度 88%</div>
|
||||
</div>
|
||||
<div class="candidate-actions">
|
||||
<button class="btn btn-success btn-sm">✓ 采纳</button>
|
||||
<button class="btn btn-danger btn-sm">✕ 拒绝</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 世界实体 2 -->
|
||||
<div class="candidate-item">
|
||||
<div class="candidate-icon world">🌍</div>
|
||||
<div class="candidate-body">
|
||||
<div class="candidate-title">天宫塔</div>
|
||||
<div class="candidate-desc">天宫市标志性建筑,位于市中心。曾是狂三与精灵们多次交战与相遇的场所。顶层观景台可俯瞰整个天宫市。</div>
|
||||
<div class="candidate-meta">类别:location · 来源:cycle · 置信度 71%</div>
|
||||
</div>
|
||||
<div class="candidate-actions">
|
||||
<button class="btn btn-success btn-sm">✓ 采纳</button>
|
||||
<button class="btn btn-danger btn-sm">✕ 拒绝</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态示例(所有候选已审完时展示) -->
|
||||
<!--
|
||||
<div class="card">
|
||||
<div class="empty-state">暂无待审候选 · 所有记忆与世界实体已审核确认</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,408 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 人物页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 人物页:铜镜名册(激活者镜面,余者镜背)
|
||||
* ============================================================ */
|
||||
|
||||
body {
|
||||
background: var(--ink);
|
||||
color: var(--fg);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Helvetica Neue", system-ui, sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* ── 侧栏(与轨迹页同源,此处 active=人物)── */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.page-header .spacer { flex: 1; }
|
||||
.page-meta { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg-faint); }
|
||||
.page-meta b { color: var(--gilt); font-weight: 600; font-family: "Songti SC", serif; font-size: 15px; }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
|
||||
/* ═══ 激活魂册(镜面材质 hero)═══ */
|
||||
.soul-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--gilt-dim);
|
||||
border-radius: 8px;
|
||||
padding: 28px 30px;
|
||||
background: var(--face-sheen), var(--surface);
|
||||
box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow);
|
||||
}
|
||||
/* 镜面静态光泽 */
|
||||
.soul-card::before {
|
||||
content: "";
|
||||
position: absolute; inset: 0; z-index: 0; pointer-events: none;
|
||||
background: linear-gradient(130deg, transparent 0%, transparent 28%, rgba(201, 162, 74, 0.025) 34%, rgba(255, 255, 255, 0.06) 40%, rgba(201, 162, 74, 0.025) 48%, transparent 55%, transparent 100%);
|
||||
background-size: 200% 200%;
|
||||
background-position: 35% 35%;
|
||||
opacity: 0.42;
|
||||
}
|
||||
/* 铜面颗粒 */
|
||||
.soul-card::after {
|
||||
content: "";
|
||||
position: absolute; inset: 0; z-index: 0; pointer-events: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
|
||||
opacity: 0.018; mix-blend-mode: overlay;
|
||||
}
|
||||
.soul-card > * { position: relative; z-index: 1; }
|
||||
|
||||
.soul-top { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; }
|
||||
.soul-mirror {
|
||||
width: 96px; height: 96px; border-radius: 50%; flex-shrink: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: 1.5px solid var(--patina-dim);
|
||||
background: var(--patina-wash);
|
||||
position: relative;
|
||||
}
|
||||
.soul-mirror .inner {
|
||||
width: 70px; height: 70px; border-radius: 50%;
|
||||
background: linear-gradient(160deg, var(--patina-bright), var(--patina));
|
||||
color: var(--ink);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-family: "Songti SC", serif; font-size: 34px; font-weight: 700;
|
||||
}
|
||||
.soul-mirror .halo {
|
||||
position: absolute; inset: -6px; border-radius: 50%;
|
||||
border: 1px dashed var(--gilt-dim);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.soul-id { flex: 1; min-width: 200px; }
|
||||
.soul-name { font-family: "Songti SC", serif; font-size: 30px; font-weight: 700; color: var(--fg); letter-spacing: 0.06em; line-height: 1.2; }
|
||||
.soul-realname { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg-faint); margin-top: 4px; letter-spacing: 0.04em; }
|
||||
.soul-mood { display: flex; align-items: center; gap: 7px; margin-top: 10px; font-size: 13px; color: var(--fg-dim); }
|
||||
.pulse-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--patina); flex-shrink: 0; animation: pulse 2.6s cubic-bezier(0.16, 1, 0.3, 1) infinite; }
|
||||
@keyframes pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(111, 163, 137, 0.5); }
|
||||
70% { box-shadow: 0 0 0 6px transparent; }
|
||||
100% { box-shadow: 0 0 0 0 transparent; }
|
||||
}
|
||||
.soul-tag {
|
||||
font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.08em;
|
||||
padding: 3px 10px; border-radius: 999px; border: 1px solid var(--patina-dim);
|
||||
background: var(--patina-wash); color: var(--patina); white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 七阶生命周期站点 */
|
||||
.lifecycle {
|
||||
margin-top: 22px;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
.lifecycle-label { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--fg-faint); margin-bottom: 12px; }
|
||||
.lifecycle-track { display: flex; align-items: center; gap: 0; }
|
||||
.lc-station { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 1; min-width: 0; }
|
||||
.lc-dot { width: 11px; height: 11px; border-radius: 50%; border: 1.5px solid var(--fg-faint); background: transparent; }
|
||||
.lc-name { font-family: "Songti SC", serif; font-size: 11.5px; color: var(--fg-faint); white-space: nowrap; }
|
||||
.lc-station.reached .lc-dot { border-color: var(--patina-dim); background: var(--patina-dim); }
|
||||
.lc-station.reached .lc-name { color: var(--fg-dim); }
|
||||
.lc-station.current .lc-dot { border-color: var(--gilt); background: var(--gilt); box-shadow: 0 0 0 4px var(--gilt-faint); animation: node-pulse 2.6s cubic-bezier(0.16,1,0.3,1) infinite; }
|
||||
.lc-station.current .lc-name { color: var(--gilt); font-weight: 600; }
|
||||
@keyframes node-pulse {
|
||||
0% { box-shadow: 0 0 0 4px var(--gilt-faint); }
|
||||
70% { box-shadow: 0 0 0 8px transparent; }
|
||||
100% { box-shadow: 0 0 0 4px var(--gilt-faint); }
|
||||
}
|
||||
.lc-link { flex: 0 0 0; height: 1.5px; align-self: flex-start; margin-top: 4.75px; background: var(--rule); width: 14px; }
|
||||
.lc-link.reached { background: var(--patina-dim); }
|
||||
|
||||
/* 档案字段网格 + 关键指标 */
|
||||
.soul-grid { margin-top: 22px; display: grid; grid-template-columns: 1fr 240px; gap: 26px; }
|
||||
.soul-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 28px; }
|
||||
.field dt { font-family: "JetBrains Mono", monospace; font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--fg-faint); }
|
||||
.field dd { margin: 3px 0 0; font-size: 13.5px; color: var(--fg); }
|
||||
.field dd.mono { font-family: "JetBrains Mono", monospace; font-size: 12.5px; }
|
||||
.soul-bg { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--rule); grid-column: 1 / -1; }
|
||||
.soul-bg .lab { font-family: "JetBrains Mono", monospace; font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--fg-faint); margin-bottom: 6px; }
|
||||
.soul-bg p { font-family: "Songti SC", serif; font-size: 14px; line-height: 1.8; color: var(--fg-dim); margin: 0; }
|
||||
|
||||
.soul-stats { display: flex; flex-direction: column; gap: 10px; }
|
||||
.soul-stat { background: var(--ink); border: 1px solid var(--rule); border-radius: 6px; padding: 12px 14px; }
|
||||
.soul-stat .k { font-family: "JetBrains Mono", monospace; font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--fg-faint); }
|
||||
.soul-stat .v { font-family: "Songti SC", serif; font-size: 22px; font-weight: 600; color: var(--gilt); margin-top: 2px; }
|
||||
.soul-stat .v small { font-size: 12px; font-weight: 400; color: var(--fg-dim); font-family: "JetBrains Mono", monospace; margin-left: 3px; }
|
||||
|
||||
/* ═══ 名册(镜背卡)═══ */
|
||||
.roster-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
|
||||
.char-card {
|
||||
position: relative;
|
||||
background: linear-gradient(180deg, color-mix(in srgb, var(--night-bronze) 70%, transparent), var(--night-bronze));
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 8px;
|
||||
padding: 18px 18px 16px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, transform 0.15s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.char-card:hover { border-color: var(--gilt-dim); transform: translateY(-2px); }
|
||||
/* 激活卡 = 镜面(与 hero 同源材质,但更克制) */
|
||||
.char-card.is-face {
|
||||
border-color: var(--gilt-dim);
|
||||
background: var(--face-sheen), var(--surface);
|
||||
box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot);
|
||||
}
|
||||
.char-card.is-paused { opacity: 0.72; }
|
||||
|
||||
.cc-top { display: flex; align-items: center; gap: 12px; }
|
||||
.cc-disc {
|
||||
width: 44px; height: 44px; border-radius: 50%; flex-shrink: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: 1.5px solid var(--patina-dim);
|
||||
background: var(--patina-wash);
|
||||
}
|
||||
.cc-disc .inner {
|
||||
width: 32px; height: 32px; border-radius: 50%;
|
||||
background: var(--patina-wash);
|
||||
color: var(--patina);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-family: "Songti SC", serif; font-size: 16px; font-weight: 700;
|
||||
}
|
||||
.char-card.is-face .cc-disc { border-color: var(--gilt); }
|
||||
.char-card.is-face .cc-disc .inner { background: linear-gradient(160deg, var(--patina-bright), var(--patina)); color: var(--ink); }
|
||||
.char-card.is-paused .cc-disc .inner { background: transparent; color: var(--fg-faint); }
|
||||
|
||||
.cc-name { font-family: "Songti SC", serif; font-size: 17px; font-weight: 600; color: var(--fg); letter-spacing: 0.04em; }
|
||||
.cc-sub { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-faint); margin-top: 2px; }
|
||||
|
||||
.cc-status { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 10.5px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--rule); color: var(--fg-dim); white-space: nowrap; }
|
||||
.cc-status.live { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.cc-status.face { color: var(--gilt); border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.cc-status.dormant { color: var(--fg-faint); }
|
||||
|
||||
/* 迷你七阶站点 */
|
||||
.cc-lc { display: flex; align-items: center; gap: 3px; }
|
||||
.cc-lc .seg { height: 4px; flex: 1; border-radius: 2px; background: var(--rule); }
|
||||
.cc-lc .seg.reached { background: var(--patina-dim); }
|
||||
.cc-lc .seg.current { background: linear-gradient(90deg, var(--gilt-dim), var(--gilt)); }
|
||||
|
||||
.cc-stats { display: flex; gap: 14px; padding-top: 10px; border-top: 1px solid var(--rule); }
|
||||
.cc-stat { display: flex; flex-direction: column; }
|
||||
.cc-stat .k { font-family: "JetBrains Mono", monospace; font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--fg-faint); }
|
||||
.cc-stat .v { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); }
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.soul-grid { grid-template-columns: 1fr; }
|
||||
.roster-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.roster-grid { grid-template-columns: 1fr; }
|
||||
.soul-fields { grid-template-columns: 1fr; }
|
||||
.lifecycle-track { overflow-x: auto; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="人物"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">人物</span>
|
||||
<span class="page-sub">魂册</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="page-meta">共 <b>5</b> 位 · 当行 <b style="color: var(--patina)">2</b></span>
|
||||
<button class="btn-ghost" type="button">+ 新塑人物</button>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 激活魂册(镜面 hero) -->
|
||||
<div class="soul-card">
|
||||
<div class="soul-top">
|
||||
<div class="soul-mirror">
|
||||
<span class="halo"></span>
|
||||
<div class="inner">梧</div>
|
||||
</div>
|
||||
<div class="soul-id">
|
||||
<div class="soul-name">青梧</div>
|
||||
<div class="soul-realname">character_01H8KFM3…Q9D · qingwu</div>
|
||||
<div class="soul-mood">
|
||||
<span class="pulse-dot"></span>认识你第 41 日 · 沉静 · 探索你的偏好
|
||||
</div>
|
||||
</div>
|
||||
<span class="soul-tag">当前当行</span>
|
||||
</div>
|
||||
|
||||
<!-- 七阶生命周期 -->
|
||||
<div class="lifecycle">
|
||||
<div class="lifecycle-label">生辰七阶 · lifecycle</div>
|
||||
<div class="lifecycle-track">
|
||||
<div class="lc-station reached"><span class="lc-dot"></span><span class="lc-name">无极</span></div>
|
||||
<span class="lc-link reached"></span>
|
||||
<div class="lc-station reached"><span class="lc-dot"></span><span class="lc-name">立名</span></div>
|
||||
<span class="lc-link reached"></span>
|
||||
<div class="lc-station reached"><span class="lc-dot"></span><span class="lc-name">已设定</span></div>
|
||||
<span class="lc-link reached"></span>
|
||||
<div class="lc-station reached"><span class="lc-dot"></span><span class="lc-name">初识</span></div>
|
||||
<span class="lc-link reached"></span>
|
||||
<div class="lc-station current"><span class="lc-dot"></span><span class="lc-name">运行中</span></div>
|
||||
<span class="lc-link"></span>
|
||||
<div class="lc-station"><span class="lc-dot"></span><span class="lc-name">暂停</span></div>
|
||||
<span class="lc-link"></span>
|
||||
<div class="lc-station"><span class="lc-dot"></span><span class="lc-name">归档</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="soul-grid">
|
||||
<div class="soul-fields">
|
||||
<div class="field"><dt>语言</dt><dd>中文 · 宋韵</dd></div>
|
||||
<div class="field"><dt>模型策略</dt><dd class="mono">default-chat</dd></div>
|
||||
<div class="field"><dt>隐私等级</dt><dd class="mono">standard</dd></div>
|
||||
<div class="field"><dt>当前心情</dt><dd>沉静 · 强度 42%</dd></div>
|
||||
<div class="field"><dt>生辰</dt><dd>熙宁三年 · 寒露</dd></div>
|
||||
<div class="field"><dt>表达风格</dt><dd>简练含蓄,少用惊叹</dd></div>
|
||||
<div class="soul-bg">
|
||||
<div class="lab">背景</div>
|
||||
<p>出身旧族旁支,少时家道中落,养就沉静自持。善观人不轻断,言简而意周。与人相处不即不离,重长久之意,轻一时之快。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="soul-stats">
|
||||
<div class="soul-stat"><div class="k">藏识</div><div class="v">86<small>条记忆</small></div></div>
|
||||
<div class="soul-stat"><div class="k">人格</div><div class="v">v7<small>当前版本</small></div></div>
|
||||
<div class="soul-stat"><div class="k">铭文</div><div class="v">142<small>通往来</small></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 名册 -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 全部人物 <span class="sub">铜镜名册</span></div>
|
||||
<div class="roster-grid">
|
||||
<!-- 激活(镜面) -->
|
||||
<div class="char-card is-face">
|
||||
<div class="cc-top">
|
||||
<div class="cc-disc"><div class="inner">梧</div></div>
|
||||
<div><div class="cc-name">青梧</div><div class="cc-sub">第 41 日</div></div>
|
||||
<span class="cc-status face">当前</span>
|
||||
</div>
|
||||
<div class="cc-lc">
|
||||
<span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg current"></span><span class="seg"></span><span class="seg"></span>
|
||||
</div>
|
||||
<div class="cc-stats">
|
||||
<div class="cc-stat"><span class="k">藏识</span><span class="v">86</span></div>
|
||||
<div class="cc-stat"><span class="k">人格</span><span class="v">v7</span></div>
|
||||
<div class="cc-stat"><span class="k">铭文</span><span class="v">142</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 叩心枢(当行) -->
|
||||
<div class="char-card">
|
||||
<div class="cc-top">
|
||||
<div class="cc-disc"><div class="inner">守</div></div>
|
||||
<div><div class="cc-name">叩心枢</div><div class="cc-sub">主动守护 · 系统</div></div>
|
||||
<span class="cc-status live">当行</span>
|
||||
</div>
|
||||
<div class="cc-lc">
|
||||
<span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg current"></span><span class="seg"></span><span class="seg"></span>
|
||||
</div>
|
||||
<div class="cc-stats">
|
||||
<div class="cc-stat"><span class="k">藏识</span><span class="v">318</span></div>
|
||||
<div class="cc-stat"><span class="k">人格</span><span class="v">v4</span></div>
|
||||
<div class="cc-stat"><span class="k">铭文</span><span class="v">27</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 云岚(初成) -->
|
||||
<div class="char-card">
|
||||
<div class="cc-top">
|
||||
<div class="cc-disc"><div class="inner">岚</div></div>
|
||||
<div><div class="cc-name">云岚</div><div class="cc-sub">第 12 日</div></div>
|
||||
<span class="cc-status">初成</span>
|
||||
</div>
|
||||
<div class="cc-lc">
|
||||
<span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg"></span><span class="seg"></span><span class="seg"></span>
|
||||
</div>
|
||||
<div class="cc-stats">
|
||||
<div class="cc-stat"><span class="k">藏识</span><span class="v">42</span></div>
|
||||
<div class="cc-stat"><span class="k">人格</span><span class="v">v3</span></div>
|
||||
<div class="cc-stat"><span class="k">铭文</span><span class="v">38</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 墨羽(种魂) -->
|
||||
<div class="char-card">
|
||||
<div class="cc-top">
|
||||
<div class="cc-disc"><div class="inner">墨</div></div>
|
||||
<div><div class="cc-name">墨羽</div><div class="cc-sub">第 3 日</div></div>
|
||||
<span class="cc-status dormant">种魂</span>
|
||||
</div>
|
||||
<div class="cc-lc">
|
||||
<span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg"></span><span class="seg"></span><span class="seg"></span><span class="seg"></span>
|
||||
</div>
|
||||
<div class="cc-stats">
|
||||
<div class="cc-stat"><span class="k">藏识</span><span class="v">12</span></div>
|
||||
<div class="cc-stat"><span class="k">人格</span><span class="v">v1</span></div>
|
||||
<div class="cc-stat"><span class="k">铭文</span><span class="v">6</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 泠音(暂息) -->
|
||||
<div class="char-card is-paused">
|
||||
<div class="cc-top">
|
||||
<div class="cc-disc"><div class="inner">泠</div></div>
|
||||
<div><div class="cc-name">泠音</div><div class="cc-sub">暂息 14 日</div></div>
|
||||
<span class="cc-status dormant">暂息</span>
|
||||
</div>
|
||||
<div class="cc-lc">
|
||||
<span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg reached"></span><span class="seg current"></span><span class="seg"></span>
|
||||
</div>
|
||||
<div class="cc-stats">
|
||||
<div class="cc-stat"><span class="k">藏识</span><span class="v">203</span></div>
|
||||
<div class="cc-stat"><span class="k">人格</span><span class="v">v12</span></div>
|
||||
<div class="cc-stat"><span class="k">铭文</span><span class="v">411</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新塑占位 -->
|
||||
<div class="char-card" style="border-style: dashed; align-items: center; justify-content: center; min-height: 168px; gap: 8px; opacity: 0.6;">
|
||||
<svg width="28" height="28" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" style="color: var(--fg-faint)"><path d="M8 3v10M3 8h10" /></svg>
|
||||
<span class="serif" style="font-size: 14px; color: var(--fg-faint)">新塑一位</span>
|
||||
<span class="mono" style="font-size: 11px; color: var(--fg-faint)">从模板或空白开始</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,981 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 登入灵台</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 · 登入灵台
|
||||
* 与 frontend/design/copper-mirror-redesign-v2.html 同源语言。
|
||||
*
|
||||
* 此页只做一件事:让用户以「照镜」的仪式感认证身份。
|
||||
* 登录卡 = 抛光铜镜(唯一的镜面),其余为镜背暗纹。
|
||||
* 星盘、铭文环、铜面噪点组成镜背的铸纹体量。
|
||||
* 扫描亮带 = 铜镜在烛光下微倾时的漫反射(唯一动态事件)。
|
||||
*
|
||||
* 亮色模式绝非「暗色反相」——而是暖纸上陈列的铜镜,
|
||||
* 保留鎏金发丝线、铜绿锈点缀、暖铜高光。
|
||||
* ============================================================ */
|
||||
|
||||
|
||||
/* ── 亮色:素绢暖纸上的铜镜 ── */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--ink);
|
||||
color: var(--fg);
|
||||
font-family:
|
||||
"PingFang SC",
|
||||
"Microsoft YaHei",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
system-ui,
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* 镜背基底
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
/* 铜面噪点(feTurbulence — 金属颗粒感) */
|
||||
.grain-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
[data-theme="dark"] .grain-overlay {
|
||||
opacity: 0.035;
|
||||
mix-blend-mode: soft-light;
|
||||
}
|
||||
[data-theme="light"] .grain-overlay {
|
||||
opacity: 0.015;
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
/* 同心铭文轨 */
|
||||
.inscription-rings {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.inscription-rings svg {
|
||||
width: min(92vmin, 820px);
|
||||
height: min(92vmin, 820px);
|
||||
}
|
||||
[data-theme="dark"] .inscription-rings svg {
|
||||
opacity: 0.18;
|
||||
}
|
||||
[data-theme="light"] .inscription-rings svg {
|
||||
opacity: 0.1;
|
||||
}
|
||||
.inscription-rings .ring-ink {
|
||||
fill: none;
|
||||
stroke: var(--gilt-faint);
|
||||
stroke-width: 0.5;
|
||||
}
|
||||
.inscription-rings .ring-faint {
|
||||
fill: none;
|
||||
stroke: var(--gilt-faint);
|
||||
stroke-width: 0.3;
|
||||
stroke-dasharray: 2 6;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* 顶栏
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
.topbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 40;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 14px clamp(20px, 4vw, 48px);
|
||||
background: color-mix(in srgb, var(--ink) 76%, transparent);
|
||||
backdrop-filter: blur(16px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(16px) saturate(140%);
|
||||
}
|
||||
.topbar .spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 6px 14px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--rule);
|
||||
background: var(--surface);
|
||||
color: var(--fg-dim);
|
||||
font-size: 11.5px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color 0.18s,
|
||||
border-color 0.18s,
|
||||
background 0.18s;
|
||||
font-family: inherit;
|
||||
}
|
||||
.pill:hover {
|
||||
color: var(--fg);
|
||||
border-color: var(--gilt-dim);
|
||||
background: var(--surface-hi);
|
||||
}
|
||||
.pill:focus-visible {
|
||||
outline: 2px solid var(--patina);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* 右上角星盘(活的仪器 — 140s 慢旋转)
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
.mini-astrolabe {
|
||||
position: absolute;
|
||||
right: clamp(24px, 5vw, 72px);
|
||||
top: clamp(80px, 12vh, 120px);
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
pointer-events: none;
|
||||
}
|
||||
[data-theme="dark"] .mini-astrolabe {
|
||||
opacity: 0.25;
|
||||
}
|
||||
[data-theme="light"] .mini-astrolabe {
|
||||
opacity: 0.18;
|
||||
}
|
||||
.mini-astrolabe svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.astrolabe-rotating {
|
||||
transform-origin: center;
|
||||
animation: slow-rot 140s linear infinite;
|
||||
}
|
||||
@keyframes slow-rot {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.mini-astrolabe {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* LOGO · 铜镜徽(品牌标志,替代朱漆红点)
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
.logo-mark {
|
||||
display: block;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
.logo-mark .mirror-ring {
|
||||
fill: none;
|
||||
stroke: var(--gilt);
|
||||
stroke-width: 1.4;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.logo-mark .mirror-inner {
|
||||
fill: none;
|
||||
stroke: var(--patina-dim);
|
||||
stroke-width: 0.8;
|
||||
opacity: 0.45;
|
||||
}
|
||||
.logo-mark .char-ring {
|
||||
fill: none;
|
||||
stroke: var(--gilt-dim);
|
||||
stroke-width: 0.5;
|
||||
opacity: 0.35;
|
||||
}
|
||||
.logo-mark .char {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 30px;
|
||||
fill: var(--gilt);
|
||||
font-weight: 700;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.logo-mark .dot {
|
||||
fill: var(--gilt);
|
||||
opacity: 0.35;
|
||||
}
|
||||
.logo-mark .fill-surface {
|
||||
fill: var(--patina-wash);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* 主舞台
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
.stage {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px clamp(20px, 4vw, 48px) 48px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* 镜面卡 · 抛光铜镜
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
.mirror-card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
background: var(--face-sheen), var(--surface);
|
||||
border: 1px solid var(--gilt-dim);
|
||||
border-radius: 6px;
|
||||
box-shadow:
|
||||
inset 0 1px 0 var(--face-in-top),
|
||||
inset 0 -1px 0 var(--face-in-bot),
|
||||
var(--shadow);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 扫描光泽 —— 铜镜在光源前微倾时的漫反射滑移 */
|
||||
.mirror-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background-size: 200% 200%;
|
||||
background-position: 200% 200%;
|
||||
animation: mirror-sweep 9s ease-in-out infinite;
|
||||
}
|
||||
[data-theme="dark"] .mirror-card::before {
|
||||
background: linear-gradient(
|
||||
130deg,
|
||||
transparent 0%,
|
||||
transparent 28%,
|
||||
rgba(201, 162, 74, 0.025) 34%,
|
||||
rgba(255, 255, 255, 0.06) 40%,
|
||||
rgba(201, 162, 74, 0.025) 48%,
|
||||
transparent 55%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
[data-theme="light"] .mirror-card::before {
|
||||
background: linear-gradient(
|
||||
130deg,
|
||||
transparent 0%,
|
||||
transparent 24%,
|
||||
rgba(200, 170, 130, 0.06) 30%,
|
||||
rgba(255, 248, 235, 0.18) 36%,
|
||||
rgba(200, 170, 130, 0.1) 44%,
|
||||
transparent 52%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
@keyframes mirror-sweep {
|
||||
0% {
|
||||
background-position: 200% 200%;
|
||||
}
|
||||
40% {
|
||||
background-position: 50% 50%;
|
||||
}
|
||||
60% {
|
||||
background-position: 50% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: -100% -100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 44px 40px 36px;
|
||||
}
|
||||
|
||||
/* —— 品牌区 —— */
|
||||
.brand {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.brand .wordmark-lg {
|
||||
display: block;
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: clamp(26px, 4vw, 30px);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.14em;
|
||||
color: var(--fg);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.brand .eyebrow {
|
||||
display: block;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.26em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gilt);
|
||||
font-weight: 500;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.brand .divider {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 1px;
|
||||
background: var(--gilt-dim);
|
||||
margin: 14px auto 0;
|
||||
}
|
||||
|
||||
/* —— 表单 —— */
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.field-group label {
|
||||
font-size: 11.5px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--fg-dim);
|
||||
}
|
||||
.field-group .input-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
[data-theme="dark"] .field-group .input-wrap {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
[data-theme="light"] .field-group .input-wrap {
|
||||
background: rgba(255, 248, 235, 0.5);
|
||||
}
|
||||
|
||||
.field-group .input-wrap:focus-within {
|
||||
border-color: var(--gilt-dim);
|
||||
box-shadow:
|
||||
0 0 0 1px var(--gilt-faint),
|
||||
inset 0 0 0 1px var(--gilt-faint);
|
||||
}
|
||||
.field-group .input-wrap.error {
|
||||
border-color: var(--cinnabar-dim);
|
||||
box-shadow: 0 0 0 1px var(--cinnabar-dim);
|
||||
}
|
||||
.field-group .input-wrap.error:focus-within {
|
||||
border-color: var(--cinnabar);
|
||||
box-shadow: 0 0 0 1px
|
||||
color-mix(in srgb, var(--cinnabar) 55%, transparent);
|
||||
}
|
||||
|
||||
.field-group input {
|
||||
flex: 1;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
color: var(--fg);
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 11px 13px;
|
||||
min-width: 0;
|
||||
}
|
||||
.field-group input::placeholder {
|
||||
color: var(--fg-faint);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.field-group .input-icon {
|
||||
flex: none;
|
||||
width: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--fg-faint);
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.field-group .input-wrap:focus-within .input-icon {
|
||||
opacity: 0.85;
|
||||
color: var(--gilt);
|
||||
}
|
||||
|
||||
/* —— 错误消息 —— */
|
||||
.form-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 13px;
|
||||
border: 1px solid var(--cinnabar-dim);
|
||||
border-radius: 4px;
|
||||
font-size: 12.5px;
|
||||
color: var(--cinnabar);
|
||||
}
|
||||
[data-theme="dark"] .form-error {
|
||||
background: rgba(184, 57, 46, 0.07);
|
||||
}
|
||||
[data-theme="light"] .form-error {
|
||||
background: rgba(168, 52, 42, 0.06);
|
||||
}
|
||||
.form-error svg {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* —— 提交按钮 —— */
|
||||
.submit-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
margin-top: 4px;
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
color: #ece6d8;
|
||||
background: var(--cinnabar);
|
||||
border: 1px solid var(--cinnabar-dim);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 0.2s,
|
||||
border-color 0.2s,
|
||||
transform 0.15s cubic-bezier(0.16, 1, 0.3, 1),
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
.submit-btn:hover {
|
||||
background: color-mix(in srgb, var(--cinnabar) 88%, var(--patina));
|
||||
border-color: var(--cinnabar);
|
||||
box-shadow: 0 0 20px var(--cinnabar-dim);
|
||||
}
|
||||
.submit-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.submit-btn:focus-visible {
|
||||
outline: 2px solid var(--patina);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.submit-btn:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.submit-btn .loading-dot {
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
animation: load-pulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
@keyframes load-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.35;
|
||||
transform: scale(0.85);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* —— 脚注 —— */
|
||||
.card-footer {
|
||||
margin-top: 32px;
|
||||
text-align: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
.card-footer p {
|
||||
font-size: 9.5px;
|
||||
letter-spacing: 0.22em;
|
||||
color: var(--fg-faint);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* 入场动画序列
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
|
||||
.mirror-card {
|
||||
opacity: 0;
|
||||
transform: translateY(18px);
|
||||
animation: card-enter 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
animation-delay: 0.12s;
|
||||
}
|
||||
@keyframes card-enter {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.brand > * {
|
||||
opacity: 0;
|
||||
transform: translateY(6px);
|
||||
animation: stagger-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
.brand .logo-mark {
|
||||
animation-delay: 0.22s;
|
||||
}
|
||||
.brand .wordmark-lg {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
.brand .eyebrow {
|
||||
animation-delay: 0.38s;
|
||||
}
|
||||
.brand .divider {
|
||||
animation-delay: 0.46s;
|
||||
}
|
||||
|
||||
.form > * {
|
||||
opacity: 0;
|
||||
animation: stagger-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
.form .field-group:nth-child(1) {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
.form .field-group:nth-child(2) {
|
||||
animation-delay: 0.58s;
|
||||
}
|
||||
.form .submit-btn {
|
||||
animation-delay: 0.66s;
|
||||
}
|
||||
.card-footer {
|
||||
animation-delay: 0.74s;
|
||||
opacity: 0;
|
||||
animation: stagger-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes stagger-in {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.mirror-card.departing {
|
||||
animation: card-depart 0.5s cubic-bezier(0.55, 0, 0.7, 0.3) forwards;
|
||||
}
|
||||
@keyframes card-depart {
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px) scale(0.97);
|
||||
filter: blur(2px);
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════
|
||||
* Reduced Motion
|
||||
* ═══════════════════════════════════════════════════ */
|
||||
.mirror-card {
|
||||
opacity: 1 !important;
|
||||
transform: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
.mirror-card::before {
|
||||
display: none !important;
|
||||
}
|
||||
.brand > *,
|
||||
.form > *,
|
||||
.card-footer {
|
||||
opacity: 1 !important;
|
||||
transform: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
.astrolabe-rotating {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
/* ═══ 统一 sidebar(6 组导航)═══ */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.siming { color: var(--cinnabar); }
|
||||
|
||||
|
||||
@media (max-width: 900px) { .sidebar-wrap { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<div class="sidebar-mount" data-active=""></div>
|
||||
<div class="main-area">
|
||||
<!-- 铜面噪点 -->
|
||||
<svg
|
||||
class="grain-overlay"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 200 200"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<filter id="bronze-grain" x="0" y="0" width="100%" height="100%">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.75"
|
||||
numOctaves="3"
|
||||
seed="42"
|
||||
result="noise"
|
||||
/>
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.25 0"
|
||||
in="noise"
|
||||
result="colored"
|
||||
/>
|
||||
</filter>
|
||||
<rect width="100%" height="100%" filter="url(#bronze-grain)" />
|
||||
</svg>
|
||||
|
||||
<!-- 同心铭文轨 -->
|
||||
<div class="inscription-rings">
|
||||
<svg viewBox="0 0 400 400" role="img" aria-hidden="true">
|
||||
<circle cx="200" cy="200" r="195" class="ring-ink" />
|
||||
<circle cx="200" cy="200" r="185" class="ring-faint" />
|
||||
<circle cx="200" cy="200" r="170" class="ring-ink" />
|
||||
<circle cx="200" cy="200" r="155" class="ring-faint" />
|
||||
<circle cx="200" cy="200" r="135" class="ring-ink" opacity="0.3" />
|
||||
<circle cx="200" cy="200" r="110" class="ring-faint" opacity="0.2" />
|
||||
<circle cx="200" cy="200" r="80" class="ring-ink" opacity="0.15" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 星盘装饰(140s 慢旋转) -->
|
||||
<div class="mini-astrolabe" role="img" aria-label="九枢星盘">
|
||||
<svg viewBox="0 0 100 100">
|
||||
<!-- 静止外环 -->
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="48"
|
||||
fill="none"
|
||||
stroke="var(--gilt-dim)"
|
||||
stroke-width="0.6"
|
||||
/>
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="42"
|
||||
fill="none"
|
||||
stroke="var(--gilt-faint)"
|
||||
stroke-width="0.3"
|
||||
stroke-dasharray="1.5 3"
|
||||
/>
|
||||
<!-- 旋转层 -->
|
||||
<g class="astrolabe-rotating">
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="33"
|
||||
fill="none"
|
||||
stroke="var(--patina-dim)"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="24"
|
||||
fill="none"
|
||||
stroke="var(--gilt-faint)"
|
||||
stroke-width="0.4"
|
||||
stroke-dasharray="1 4"
|
||||
/>
|
||||
<!-- 辐线 -->
|
||||
<g stroke="var(--gilt-faint)" stroke-width="0.3" opacity="0.5">
|
||||
<line x1="50" y1="17" x2="50" y2="4" />
|
||||
<line x1="83" y1="30" x2="93" y2="22" />
|
||||
<line x1="93" y1="62" x2="97" y2="68" />
|
||||
<line x1="74" y1="81" x2="80" y2="89" />
|
||||
<line x1="32" y1="82" x2="26" y2="90" />
|
||||
<line x1="14" y1="56" x2="6" y2="60" />
|
||||
<line x1="18" y1="26" x2="10" y2="20" />
|
||||
</g>
|
||||
<!-- 节点 -->
|
||||
<g fill="var(--gilt)" opacity="0.4">
|
||||
<circle cx="50" cy="12" r="1.8" />
|
||||
<circle cx="82" cy="28" r="1.5" />
|
||||
<circle cx="88" cy="56" r="1.6" />
|
||||
<circle cx="68" cy="80" r="1.4" />
|
||||
<circle cx="34" cy="78" r="1.5" />
|
||||
<circle cx="14" cy="52" r="1.7" />
|
||||
<circle cx="22" cy="24" r="1.4" />
|
||||
</g>
|
||||
</g>
|
||||
<!-- 中心轴 -->
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="3.5"
|
||||
fill="var(--patina-bright)"
|
||||
opacity="0.5"
|
||||
/>
|
||||
<circle cx="50" cy="50" r="1.5" fill="var(--patina)" opacity="0.7" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 顶栏 -->
|
||||
<header class="topbar">
|
||||
<span class="spacer"></span>
|
||||
<button class="pill" id="themeToggle" type="button" aria-label="切换主题">
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M8 1v2M8 13v2M2 8h2M12 8h2M3.5 3.5l1.4 1.4M11.1 11.1l1.4 1.4M3.5 12.5l1.4-1.4M11.1 4.9l1.4-1.4"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<circle
|
||||
cx="8"
|
||||
cy="8"
|
||||
r="3"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.2"
|
||||
/>
|
||||
</svg>
|
||||
<span id="themeLabel">夜铜</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- 主舞台 -->
|
||||
<main class="stage" role="main">
|
||||
<div class="mirror-card" id="mirrorCard">
|
||||
<div class="card-inner">
|
||||
<!-- 品牌区 · 铜镜徽 + 魂镜 + 铭文 -->
|
||||
<div class="brand">
|
||||
<svg
|
||||
class="logo-mark"
|
||||
viewBox="0 0 68 68"
|
||||
role="img"
|
||||
aria-label="魂镜铜镜徽"
|
||||
>
|
||||
<circle cx="34" cy="34" r="32" class="mirror-ring" />
|
||||
<circle cx="34" cy="34" r="27" class="mirror-inner" />
|
||||
<circle cx="34" cy="34" r="22" class="char-ring" />
|
||||
<circle cx="34" cy="34" r="15" class="char-ring" />
|
||||
<circle
|
||||
cx="34"
|
||||
cy="34"
|
||||
r="8"
|
||||
fill="var(--patina-wash)"
|
||||
opacity="0.3"
|
||||
/>
|
||||
<text x="34" y="40" text-anchor="middle" class="char">魂</text>
|
||||
<circle cx="34" cy="6" r="1.5" class="dot" />
|
||||
<circle cx="34" cy="62" r="1.5" class="dot" />
|
||||
<circle cx="6" cy="34" r="1.5" class="dot" />
|
||||
<circle cx="62" cy="34" r="1.5" class="dot" />
|
||||
</svg>
|
||||
<span class="wordmark-lg">魂鏡</span>
|
||||
<span class="eyebrow">MIRROR OF THE HEART</span>
|
||||
<span class="divider" aria-hidden="true"></span>
|
||||
</div>
|
||||
|
||||
<!-- 表单 -->
|
||||
<form class="form" id="loginForm" novalidate autocomplete="off">
|
||||
<div class="field-group">
|
||||
<label for="username">用户名</label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon mono" aria-hidden="true">◎</span>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
placeholder="铜镜所照之名"
|
||||
autocomplete="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="password">密令</label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon mono" aria-hidden="true">◉</span>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="铭文锁钥"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误消息 -->
|
||||
<div
|
||||
id="formError"
|
||||
class="form-error"
|
||||
role="alert"
|
||||
style="display: none"
|
||||
>
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle
|
||||
cx="8"
|
||||
cy="8"
|
||||
r="6"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.2"
|
||||
/>
|
||||
<path
|
||||
d="M8 5v3M8 10.5v.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.3"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<span id="errorText"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="submit-btn" id="submitBtn">
|
||||
<span id="submitText">登入</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- 脚注 -->
|
||||
<div class="card-footer">
|
||||
<p class="mono">琉璃镜 · 照见数字生命</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
/* ── 主题切换 ── */
|
||||
const root = document.documentElement;
|
||||
const label = document.getElementById("themeLabel");
|
||||
document.getElementById("themeToggle").addEventListener("click", () => {
|
||||
const n = root.getAttribute("data-theme") === "dark" ? "light" : "dark";
|
||||
root.setAttribute("data-theme", n);
|
||||
label.textContent = n === "dark" ? "夜铜" : "素绢";
|
||||
});
|
||||
|
||||
/* ── 表单交互 ── */
|
||||
const form = document.getElementById("loginForm");
|
||||
const user = document.getElementById("username");
|
||||
const pass = document.getElementById("password");
|
||||
const btn = document.getElementById("submitBtn");
|
||||
const txt = document.getElementById("submitText");
|
||||
const err = document.getElementById("formError");
|
||||
const et = document.getElementById("errorText");
|
||||
|
||||
function showError(m) {
|
||||
et.textContent = m;
|
||||
err.style.display = "flex";
|
||||
document
|
||||
.querySelectorAll(".input-wrap")
|
||||
.forEach((w) => w.classList.add("error"));
|
||||
err.style.animation = "none";
|
||||
err.offsetHeight;
|
||||
err.style.animation = "shake .35s cubic-bezier(.16,1,.3,1)";
|
||||
}
|
||||
function hideError() {
|
||||
err.style.display = "none";
|
||||
document
|
||||
.querySelectorAll(".input-wrap")
|
||||
.forEach((w) => w.classList.remove("error"));
|
||||
}
|
||||
user.addEventListener("input", hideError);
|
||||
pass.addEventListener("input", hideError);
|
||||
|
||||
const ss = document.createElement("style");
|
||||
ss.textContent = `@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-4px)}50%{transform:translateX(4px)}75%{transform:translateX(-2px)}}`;
|
||||
document.head.appendChild(ss);
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
hideError();
|
||||
if (!user.value.trim() || !pass.value) {
|
||||
showError("请填写用户名和密令。");
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
txt.innerHTML = '登入灵台<span class="loading-dot"></span>';
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
const t = parseInt(sessionStorage.getItem("t") || "0");
|
||||
sessionStorage.setItem("t", String(t + 1));
|
||||
if (t === 0) {
|
||||
showError("用户名或密令错误,铜镜不识此名。");
|
||||
btn.disabled = false;
|
||||
txt.textContent = "登入";
|
||||
return;
|
||||
}
|
||||
const c = document.getElementById("mirrorCard");
|
||||
c.classList.add("departing");
|
||||
txt.textContent = "镜开灵现";
|
||||
await new Promise((r) => setTimeout(r, 600));
|
||||
txt.textContent = "登入";
|
||||
btn.disabled = false;
|
||||
c.classList.remove("departing");
|
||||
sessionStorage.removeItem("t");
|
||||
showError("演示:登录成功");
|
||||
err.style.border = "1px solid var(--patina-dim)";
|
||||
err.style.background = "var(--patina-wash)";
|
||||
err.style.color = "var(--patina)";
|
||||
const ic = err.querySelector("svg");
|
||||
if (ic) ic.style.display = "none";
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,572 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 灵枢页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 灵枢页:心智解剖图
|
||||
* 以「太极 → 四象 → 八卦」的生成链为论点,呈现数字生命的心智结构。
|
||||
* 太极/阴阳 ← 设计理念 §4.2(自主与约束 · 阴阳平衡度)
|
||||
* 四象 ← 设计理念 §7(青龙生发 / 朱雀表达 / 白虎裁断 / 玄武藏养)
|
||||
* 八卦 ← 设计理念 §7 + agent-society-design §3(天工阁八原型)
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 侧栏(active=灵枢)── */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.char-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--patina); padding: 3px 11px 3px 4px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.char-pill .avatar-mini { width: 18px; height: 18px; border-radius: 50%; background: var(--patina); color: var(--ink); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; }
|
||||
.page-header .spacer { flex: 1; }
|
||||
.page-meta { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg-faint); }
|
||||
.page-meta b { color: var(--gilt); font-weight: 600; font-family: "Songti SC", serif; font-size: 15px; }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
.section-heading { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin-bottom: 14px; }
|
||||
.genesis-chain { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.genesis-chain b { font-family: "Songti SC", serif; font-size: 13px; font-weight: 600; }
|
||||
.genesis-chain .g-taiji { color: var(--fg); } .genesis-chain .g-si { color: var(--patina); } .genesis-chain .g-ba { color: var(--gilt); }
|
||||
|
||||
/* ═══ 太极 · 阴阳平衡(签名 hero)═══
|
||||
阳鱼 = patina(生发:太一/大周天/内驱力),阴鱼 = cinnabar(约束:司命/安全/审计)。
|
||||
阴阳平衡度 = 司命通过率 / (候选生成率 + ε),三态:阳亢 / 动态平衡 / 阴盛。 */
|
||||
.taiji-hero { position: relative; overflow: hidden; border: 1px solid var(--gilt-dim); border-radius: 8px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); padding: 26px 30px 22px; }
|
||||
.taiji-hero::after { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); opacity: 0.018; mix-blend-mode: overlay; }
|
||||
.taiji-hero > * { position: relative; z-index: 1; }
|
||||
.taiji-stage { display: flex; align-items: center; gap: 28px; flex-wrap: wrap; }
|
||||
.yy-side { flex: 1; min-width: 180px; display: flex; flex-direction: column; gap: 9px; }
|
||||
.yy-side.yang { text-align: right; align-items: flex-end; }
|
||||
.yy-side .yy-tag { font-family: "Songti SC", serif; font-size: 17px; font-weight: 700; letter-spacing: 0.08em; }
|
||||
.yy-side.yang .yy-tag { color: var(--patina); }
|
||||
.yy-side.yin .yy-tag { color: var(--cinnabar); }
|
||||
.yy-side .yy-mono { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.yy-side .yy-roles { display: flex; gap: 6px; flex-wrap: wrap; justify-content: inherit; }
|
||||
.yy-side.yang .yy-roles { justify-content: flex-end; }
|
||||
.yy-role { font-family: "Songti SC", serif; font-size: 12.5px; padding: 3px 11px; border-radius: 999px; border: 1px solid var(--rule); }
|
||||
.yy-side.yang .yy-role { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.yy-side.yin .yy-role { color: var(--cinnabar); border-color: var(--cinnabar-dim); background: rgba(184,57,46,0.08); }
|
||||
.yy-side .yy-desc { font-size: 12.5px; color: var(--fg-dim); line-height: 1.6; }
|
||||
|
||||
.taiji-disc-wrap { flex: 0 0 auto; position: relative; width: 196px; height: 196px; }
|
||||
.taiji-disc { width: 100%; height: 100%; animation: taiji-spin 90s linear infinite; transform-origin: 50% 50%; }
|
||||
@keyframes taiji-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||
.taiji-disc .yy-ring { fill: none; stroke: var(--gilt-dim); stroke-width: 0.8; }
|
||||
.taiji-disc .yy-yang { fill: var(--patina); }
|
||||
.taiji-disc .yy-yin { fill: var(--cinnabar); }
|
||||
.taiji-disc .yy-eye-yang { fill: var(--patina); }
|
||||
.taiji-disc .yy-eye-yin { fill: var(--cinnabar); }
|
||||
.taiji-eye-pulse { animation: eye-pulse 3.2s cubic-bezier(0.16,1,0.3,1) infinite; transform-origin: 100px 52px; }
|
||||
.taiji-eye-pulse.late { animation-delay: 1.6s; transform-origin: 100px 148px; }
|
||||
@keyframes eye-pulse { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.35; } }
|
||||
|
||||
/* 阴阳平衡度 readout */
|
||||
.balance-readout { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--rule); display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
|
||||
.br-formula { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--fg-faint); line-height: 1.7; }
|
||||
.br-formula b { color: var(--gilt); font-weight: 600; }
|
||||
.br-gauge { flex: 1; min-width: 260px; display: flex; flex-direction: column; gap: 7px; }
|
||||
.br-track { position: relative; height: 30px; border-radius: 6px; background: linear-gradient(90deg, var(--cinnabar-dim), var(--patina-wash) 45%, var(--surface-hi) 50%, var(--patina-wash) 55%, var(--patina-dim)); border: 1px solid var(--rule); overflow: hidden; }
|
||||
.br-tick { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--rule); }
|
||||
.br-tick-label { position: absolute; top: 32px; font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); transform: translateX(-50%); letter-spacing: 0.04em; }
|
||||
.br-marker { position: absolute; top: -3px; bottom: -3px; width: 3px; border-radius: 2px; background: var(--gilt); box-shadow: 0 0 10px -1px rgba(201,162,74,0.6); transform: translateX(-50%); }
|
||||
.br-marker::before { content: ""; position: absolute; top: -5px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 6px solid var(--gilt); }
|
||||
.br-state { font-family: "Songti SC", serif; font-size: 13.5px; color: var(--patina); padding: 5px 14px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); white-space: nowrap; flex-shrink: 0; }
|
||||
.br-state b { font-family: "JetBrains Mono", monospace; font-size: 12px; margin-left: 4px; }
|
||||
|
||||
/* ═══ 四象 · 认知循环(罗盘)═══
|
||||
南上北下东左西右(同八卦取向)。顺时针 = 认知流:生发→表达→裁断→藏养。
|
||||
青龙(东/木) 朱雀(南/火) 白虎(西/金) 玄武(北/水)。 */
|
||||
.sixiang-card { position: relative; overflow: hidden; border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 22px 26px; }
|
||||
.sixiang-stage { position: relative; width: 100%; max-width: 520px; margin: 6px auto 0; aspect-ratio: 1 / 1; }
|
||||
.sixiang-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.sixiang-svg .compass-ring { fill: none; stroke: var(--rule); stroke-width: 1; }
|
||||
.sixiang-svg .compass-ring-inner { fill: none; stroke: var(--gilt-dim); stroke-width: 0.6; stroke-dasharray: 3 4; opacity: 0.6; }
|
||||
.sixiang-svg .flow-arc { fill: none; stroke: var(--patina-dim); stroke-width: 1.4; stroke-linecap: round; opacity: 0.5; }
|
||||
.sixiang-svg .center-disc { fill: var(--ink); stroke: var(--gilt-dim); stroke-width: 1; }
|
||||
.sixiang-svg .center-taiji-yang { fill: var(--patina); }
|
||||
.sixiang-svg .center-taiji-yin { fill: var(--cinnabar); }
|
||||
.sixiang-svg .center-label { font-family: "Songti SC", serif; font-size: 9px; text-anchor: middle; fill: var(--fg-faint); letter-spacing: 0.08em; }
|
||||
.sixiang-quadrant { position: absolute; width: 38%; display: flex; flex-direction: column; gap: 5px; padding: 12px 14px; border-radius: 7px; border: 1px solid var(--rule); background: var(--surface); }
|
||||
.sixiang-quadrant .q-creature { font-family: "Songti SC", serif; font-size: 15px; font-weight: 700; letter-spacing: 0.04em; display: flex; align-items: baseline; gap: 7px; }
|
||||
.sixiang-quadrant .q-phase { font-family: "JetBrains Mono", monospace; font-size: 10px; letter-spacing: 0.06em; }
|
||||
.sixiang-quadrant .q-what { font-size: 11.5px; color: var(--fg-dim); line-height: 1.5; }
|
||||
.sixiang-quadrant .q-mapping { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); letter-spacing: 0.02em; }
|
||||
.sixiang-quadrant.q-qinglong { top: 31%; left: 0; border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.sixiang-quadrant.q-qinglong .q-creature, .sixiang-quadrant.q-qinglong .q-phase { color: var(--patina); }
|
||||
.sixiang-quadrant.q-zhuque { top: 0; left: 31%; border-color: var(--cinnabar-dim); background: rgba(184,57,46,0.07); }
|
||||
.sixiang-quadrant.q-zhuque .q-creature, .sixiang-quadrant.q-zhuque .q-phase { color: var(--cinnabar); }
|
||||
.sixiang-quadrant.q-baihu { top: 31%; right: 0; border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.sixiang-quadrant.q-baihu .q-creature, .sixiang-quadrant.q-baihu .q-phase { color: var(--gilt); }
|
||||
.sixiang-quadrant.q-xuanwu { bottom: 0; left: 31%; border-color: var(--slate-dim); background: var(--slate-wash); }
|
||||
.sixiang-quadrant.q-xuanwu .q-creature, .sixiang-quadrant.q-xuanwu .q-phase { color: var(--slate); }
|
||||
.sixiang-quadrant .q-dir { font-family: "JetBrains Mono", monospace; font-size: 9px; opacity: 0.7; }
|
||||
|
||||
/* ═══ 八卦阵 · 天工阁八原型(签名)═══
|
||||
后天八卦取向(南=离/火 上,北=坎/水 下,东=震/雷 左,西=兑/泽 右)。
|
||||
中心太极;八卦按四组分色:认知方向=金,行动表达=朱,承载稳定=铜,边界风险=石。 */
|
||||
.bagua-card { position: relative; overflow: hidden; border: 1px solid var(--gilt-dim); border-radius: 8px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); padding: 22px 26px; }
|
||||
.bagua-card::after { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); opacity: 0.018; mix-blend-mode: overlay; }
|
||||
.bagua-card > * { position: relative; z-index: 1; }
|
||||
.bagua-stage { width: 100%; max-width: 560px; margin: 4px auto 0; }
|
||||
.bagua-svg { width: 100%; height: auto; display: block; }
|
||||
.bagua-svg .oct-frame { fill: none; stroke: var(--rule); stroke-width: 1; }
|
||||
.bagua-svg .oct-frame-outer { fill: none; stroke: var(--gilt-dim); stroke-width: 0.6; stroke-dasharray: 3 5; opacity: 0.55; }
|
||||
.bagua-svg .group-arc { fill: none; stroke-width: 1.2; opacity: 0.16; }
|
||||
.bagua-svg .node-bg { fill: var(--ink); }
|
||||
.bagua-svg .node-ring { fill: none; stroke-width: 2; }
|
||||
.bagua-svg .trigram-bar { fill: var(--fg); }
|
||||
.bagua-svg .node-name { font-family: "Songti SC", serif; font-size: 15px; font-weight: 700; text-anchor: middle; dominant-baseline: central; }
|
||||
.bagua-svg .node-meta { font-family: "JetBrains Mono", monospace; font-size: 8px; text-anchor: middle; fill: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.bagua-svg .center-disc { fill: var(--ink); stroke: var(--gilt-dim); stroke-width: 1.2; }
|
||||
.bagua-svg .center-yang { fill: var(--patina); }
|
||||
.bagua-svg .center-yin { fill: var(--cinnabar); }
|
||||
.bagua-svg .center-label { font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; text-anchor: middle; fill: var(--gilt); letter-spacing: 0.06em; }
|
||||
|
||||
/* 八卦详注:四组 × 每组两卦 */
|
||||
.bagua-detail { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; margin-top: 4px; }
|
||||
.bg-group { border: 1px solid var(--rule); border-radius: 8px; padding: 14px 16px; display: flex; flex-direction: column; gap: 11px; }
|
||||
.bg-group.g1 { border-left: 3px solid var(--gilt); } .bg-group.g2 { border-left: 3px solid var(--cinnabar); } .bg-group.g3 { border-left: 3px solid var(--patina); } .bg-group.g4 { border-left: 3px solid var(--slate); }
|
||||
.bg-group-head { display: flex; align-items: center; gap: 8px; }
|
||||
.bg-group-name { font-family: "Songti SC", serif; font-size: 13px; font-weight: 600; }
|
||||
.bg-group.g1 .bg-group-name { color: var(--gilt); } .bg-group.g2 .bg-group-name { color: var(--cinnabar); } .bg-group.g3 .bg-group-name { color: var(--patina); } .bg-group.g4 .bg-group-name { color: var(--slate); }
|
||||
.bg-group-tag { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.bg-tri { display: flex; gap: 12px; padding: 10px 0; }
|
||||
.bg-tri + .bg-tri { border-top: 1px dashed var(--rule); }
|
||||
.bg-tri-glyph { flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: 4px; width: 42px; }
|
||||
.bg-tri-glyph svg { width: 30px; height: 26px; }
|
||||
.bg-tri-glyph .bg-tri-name { font-family: "Songti SC", serif; font-size: 15px; font-weight: 700; }
|
||||
.bg-group.g1 .bg-tri-glyph .bg-tri-name { color: var(--gilt); } .bg-group.g2 .bg-tri-glyph .bg-tri-name { color: var(--cinnabar); } .bg-group.g3 .bg-tri-glyph .bg-tri-name { color: var(--patina); } .bg-group.g4 .bg-tri-glyph .bg-tri-name { color: var(--slate); }
|
||||
.bg-tri-body { flex: 1; min-width: 0; }
|
||||
.bg-tri-role { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
||||
.bg-tri-map { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); letter-spacing: 0.02em; }
|
||||
.bg-tri-duty { font-size: 12px; color: var(--fg-dim); margin-top: 4px; line-height: 1.55; }
|
||||
.bg-tri-behave { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); margin-top: 4px; line-height: 1.5; letter-spacing: 0.01em; }
|
||||
|
||||
/* 状态注脚 */
|
||||
.vision-strip { border: 1px dashed var(--gilt-dim); border-left: 3px solid var(--gilt-dim); border-radius: 6px; padding: 13px 18px; background: var(--gilt-faint); display: flex; align-items: center; gap: 14px; }
|
||||
.vision-strip .seal { font-family: "Songti SC", serif; font-size: 13px; font-weight: 700; color: var(--gilt); letter-spacing: 0.06em; white-space: nowrap; }
|
||||
.vision-strip .desc { font-size: 12.5px; color: var(--fg-dim); line-height: 1.55; flex: 1; }
|
||||
.vision-strip .desc code { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--gilt); background: var(--surface); padding: 1px 5px; border-radius: 3px; }
|
||||
.vision-strip .tag { font-family: "JetBrains Mono", monospace; font-size: 10px; padding: 2px 9px; border-radius: 999px; border: 1px dashed var(--gilt-dim); color: var(--gilt); white-space: nowrap; flex-shrink: 0; }
|
||||
|
||||
@media (max-width: 900px) { .bagua-detail { grid-template-columns: 1fr; } .taiji-stage { gap: 18px; } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } .yy-side.yang { text-align: left; align-items: flex-start; } .yy-side.yang .yy-roles { justify-content: flex-start; } .taiji-disc-wrap { margin: 0 auto; } .sixiang-quadrant { width: 44%; } .sixiang-quadrant.q-zhuque { left: 28%; } .sixiang-quadrant.q-xuanwu { left: 28%; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="灵枢"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">灵枢</span>
|
||||
<span class="page-sub">心智解剖图 · 太极生两仪,两仪生四象,四象生八卦</span>
|
||||
<span class="char-pill"><span class="avatar-mini">梧</span>青梧</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="genesis-chain"><b class="g-taiji">太极</b> → <b class="g-si">四象</b> → <b class="g-ba">八卦</b></span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- ═══ 太极 · 阴阳平衡(hero)═══ -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 太极 <span class="sub">阴阳 · 自主与约束的动态制衡 · 系统生命体征</span></div>
|
||||
<div class="taiji-hero">
|
||||
<div class="taiji-stage">
|
||||
<div class="yy-side yang">
|
||||
<span class="yy-tag">阳 · 生发</span>
|
||||
<span class="yy-mono">yang · generation</span>
|
||||
<div class="yy-roles">
|
||||
<span class="yy-role">太一枢</span>
|
||||
<span class="yy-role">大周天</span>
|
||||
<span class="yy-role">内驱力</span>
|
||||
</div>
|
||||
<span class="yy-desc">主动生发,追求可能性。<br />不断生成意图、候选与行动方案。</span>
|
||||
</div>
|
||||
|
||||
<div class="taiji-disc-wrap">
|
||||
<svg class="taiji-disc" viewBox="0 0 200 200" role="img" aria-label="太极图:阳鱼铜绿主生发,阴鱼朱砂主约束">
|
||||
<circle class="yy-ring" cx="100" cy="100" r="97" />
|
||||
<circle class="yy-ring" cx="100" cy="100" r="91" opacity="0.5" />
|
||||
<!-- 阳 fish base(铜绿)-->
|
||||
<circle class="yy-yang" cx="100" cy="100" r="88" />
|
||||
<!-- 阴 fish(朱砂)-->
|
||||
<path class="yy-yin" d="M 100,12 A 88,88 0 0,1 100,188 A 44,44 0 0,1 100,100 A 44,44 0 0,0 100,12 Z" opacity="0.88" />
|
||||
<!-- 阳眼(铜绿点,在阴鱼头部下方)-->
|
||||
<circle class="yy-eye-yang taiji-eye-pulse late" cx="100" cy="144" r="13" />
|
||||
<!-- 阴眼(朱砂点,在阳鱼头部上方)-->
|
||||
<circle class="yy-eye-yin taiji-eye-pulse" cx="100" cy="56" r="13" opacity="0.88" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="yy-side yin">
|
||||
<span class="yy-tag">阴 · 约束</span>
|
||||
<span class="yy-mono">yin · constraint</span>
|
||||
<div class="yy-roles">
|
||||
<span class="yy-role">司命枢</span>
|
||||
<span class="yy-role">安全治理</span>
|
||||
<span class="yy-role">审计朱批</span>
|
||||
</div>
|
||||
<span class="yy-desc">设置边界,控制风险。<br />对候选过滤、约束与驳回。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 阴阳平衡度 readout -->
|
||||
<div class="balance-readout">
|
||||
<div class="br-formula">
|
||||
阴阳平衡度 = <b>司命通过率</b> / (<b>候选生成率</b> + ε)<br />
|
||||
<span style="color:var(--fg-faint)">当前通过率 <b style="color:var(--patina)">0.58</b> · 生成率 <b style="color:var(--gilt)">0.62</b></span>
|
||||
</div>
|
||||
<div class="br-gauge">
|
||||
<div class="br-track">
|
||||
<span class="br-tick" style="left: 20%"></span>
|
||||
<span class="br-tick" style="left: 50%"></span>
|
||||
<span class="br-tick" style="left: 80%"></span>
|
||||
<span class="br-marker" style="left: 58%"></span>
|
||||
</div>
|
||||
<div style="position: relative; height: 14px;">
|
||||
<span class="br-tick-label" style="left: 20%">阴盛 · 僵化</span>
|
||||
<span class="br-tick-label" style="left: 50%">动态平衡</span>
|
||||
<span class="br-tick-label" style="left: 80%">阳亢 · 越界</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="br-state">动态平衡<b>0.94</b></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══ 四象 · 认知循环 ═══ -->
|
||||
<section>
|
||||
<h3 class="section-heading">四象</h3>
|
||||
<div class="sixiang-card">
|
||||
<div class="sixiang-stage">
|
||||
<svg class="sixiang-svg" viewBox="0 0 400 400" aria-hidden="true">
|
||||
<circle class="compass-ring" cx="200" cy="200" r="188" />
|
||||
<circle class="compass-ring-inner" cx="200" cy="200" r="120" />
|
||||
<!-- 顺时针流向弧(东→南→西→北)-->
|
||||
<path class="flow-arc" d="M 250,60 A 160,160 0 0,1 340,200" />
|
||||
<path class="flow-arc" d="M 340,200 A 160,160 0 0,1 250,340" />
|
||||
<path class="flow-arc" d="M 150,340 A 160,160 0 0,1 60,200" />
|
||||
<path class="flow-arc" d="M 60,200 A 160,160 0 0,1 150,60" />
|
||||
<!-- 方位字 -->
|
||||
<text x="200" y="26" text-anchor="middle" font-family="Songti SC,serif" font-size="11" fill="var(--fg-faint)" letter-spacing="0.1em">南 · 火</text>
|
||||
<text x="200" y="386" text-anchor="middle" font-family="Songti SC,serif" font-size="11" fill="var(--fg-faint)" letter-spacing="0.1em">北 · 水</text>
|
||||
<text x="22" y="204" text-anchor="middle" font-family="Songti SC,serif" font-size="11" fill="var(--fg-faint)" letter-spacing="0.1em">东 · 木</text>
|
||||
<text x="378" y="204" text-anchor="middle" font-family="Songti SC,serif" font-size="11" fill="var(--fg-faint)" letter-spacing="0.1em">西 · 金</text>
|
||||
<!-- 中心太极(mini)+ 灵核 -->
|
||||
<circle class="center-disc" cx="200" cy="200" r="38" />
|
||||
<circle class="center-taiji-yang" cx="200" cy="200" r="33" />
|
||||
<path class="center-taiji-yin" d="M 200,167 A 33,33 0 0,1 200,233 A 16.5,16.5 0 0,1 200,200 A 16.5,16.5 0 0,0 200,167 Z" />
|
||||
<circle class="center-taiji-yang" cx="200" cy="216.5" r="5" />
|
||||
<circle class="center-taiji-yin" cx="200" cy="183.5" r="5" />
|
||||
</svg>
|
||||
|
||||
<!-- 四象限卡(南上 / 北下 / 东左 / 西右)-->
|
||||
<div class="sixiang-quadrant q-zhuque">
|
||||
<div class="q-creature"><span>朱雀</span><span class="q-dir">南 · 火</span></div>
|
||||
<span class="q-phase">表达 · expression</span>
|
||||
<span class="q-what">对话、解释、输出、沟通,成象于外。</span>
|
||||
<span class="q-mapping">四爻行事 · 五爻成象</span>
|
||||
</div>
|
||||
<div class="sixiang-quadrant q-baihu">
|
||||
<div class="q-creature"><span>白虎</span><span class="q-dir">西 · 金</span></div>
|
||||
<span class="q-phase">裁断 · judgment</span>
|
||||
<span class="q-what">判断、约束、风险、安全,金断决疑。</span>
|
||||
<span class="q-mapping">三爻定策 · 司命</span>
|
||||
</div>
|
||||
<div class="sixiang-quadrant q-xuanwu">
|
||||
<div class="q-creature"><span>玄武</span><span class="q-dir">北 · 水</span></div>
|
||||
<span class="q-phase">藏养 · consolidation</span>
|
||||
<span class="q-what">记忆、沉淀、恢复、长期状态,归于深藏。</span>
|
||||
<span class="q-mapping">上爻返照 · 息壤</span>
|
||||
</div>
|
||||
<div class="sixiang-quadrant q-qinglong">
|
||||
<div class="q-creature"><span>青龙</span><span class="q-dir">东 · 木</span></div>
|
||||
<span class="q-phase">生发 · genesis</span>
|
||||
<span class="q-what">感知、发现、提出可能性,木气初萌。</span>
|
||||
<span class="q-mapping">初爻起意 · 二爻察势</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══ 八卦阵 · 天工阁八原型 ═══ -->
|
||||
<section>
|
||||
<h3 class="section-heading">八卦阵</h3>
|
||||
<div class="bagua-card">
|
||||
<div class="bagua-stage">
|
||||
<svg class="bagua-svg" viewBox="0 0 460 460" role="img" aria-label="八卦阵:八原型环绕中心太极">
|
||||
<!-- 八边形外框 -->
|
||||
<polygon class="oct-frame-outer" points="230,60 347,113 360,230 347,347 230,400 113,347 60,230 113,113" />
|
||||
<polygon class="oct-frame" points="230,80 333,127 350,230 333,333 230,380 127,333 70,230 127,127" />
|
||||
<!-- 四组弧(分组色提示)-->
|
||||
<path class="group-arc" stroke="var(--gilt)" d="M 333,127 A 200,200 0 0,1 350,230" />
|
||||
<path class="group-arc" stroke="var(--gilt)" d="M 127,127 A 200,200 0 0,0 70,230" />
|
||||
<path class="group-arc" stroke="var(--cinnabar)" d="M 230,80 A 200,200 0 0,1 333,127" />
|
||||
<path class="group-arc" stroke="var(--cinnabar)" d="M 70,230 A 200,200 0 0,1 127,333" />
|
||||
<path class="group-arc" stroke="var(--patina)" d="M 350,230 A 200,200 0 0,1 333,333" />
|
||||
<path class="group-arc" stroke="var(--patina)" d="M 113,113 A 200,200 0 0,1 230,80" />
|
||||
<path class="group-arc" stroke="var(--slate)" d="M 333,333 A 200,200 0 0,1 230,380" />
|
||||
<path class="group-arc" stroke="var(--slate)" d="M 127,333 A 200,200 0 0,0 113,113" />
|
||||
|
||||
<!-- 八卦节点(南=离 上 → 顺时针)-->
|
||||
<!-- 离 ☲ 南·火 宣明者(行动表达/朱)pos (230,95) -->
|
||||
<g transform="translate(230,95)">
|
||||
<circle class="node-bg" r="30" />
|
||||
<circle class="node-ring" r="30" stroke="var(--cinnabar)" />
|
||||
<!-- 离 [0,1,0]: 破-整-破 -->
|
||||
<rect class="trigram-bar" x="-13" y="-15" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-15" width="11" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-8" width="26" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-1" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-1" width="11" height="4" rx="1"/>
|
||||
<text class="node-name" y="16" fill="var(--cinnabar)">离</text>
|
||||
</g>
|
||||
<text class="node-meta" x="230" y="55">宣明者 · 火</text>
|
||||
|
||||
<!-- 坤 ☷ 西南·地 藏守者(承载稳定/铜)pos (325,135) -->
|
||||
<g transform="translate(325,135)">
|
||||
<circle class="node-bg" r="30" />
|
||||
<circle class="node-ring" r="30" stroke="var(--patina)" />
|
||||
<!-- 坤 [0,0,0] -->
|
||||
<rect class="trigram-bar" x="-13" y="-15" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-15" width="11" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-8" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-8" width="11" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-1" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-1" width="11" height="4" rx="1"/>
|
||||
<text class="node-name" y="16" fill="var(--patina)">坤</text>
|
||||
</g>
|
||||
<text class="node-meta" x="372" y="120">藏守者 · 地</text>
|
||||
|
||||
<!-- 兑 ☱ 西·泽 应和者(承载稳定/铜)pos (365,230) -->
|
||||
<g transform="translate(365,230)">
|
||||
<circle class="node-bg" r="30" />
|
||||
<circle class="node-ring" r="30" stroke="var(--patina)" />
|
||||
<!-- 兑 [0,1,1]: 破-整-整 -->
|
||||
<rect class="trigram-bar" x="-13" y="-15" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-15" width="11" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-8" width="26" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-1" width="26" height="4" rx="1"/>
|
||||
<text class="node-name" y="16" fill="var(--patina)">兑</text>
|
||||
</g>
|
||||
<text class="node-meta" x="412" y="234">应和者 · 泽</text>
|
||||
|
||||
<!-- 乾 ☰ 西北·天 造物者(认知方向/金)pos (325,325) -->
|
||||
<g transform="translate(325,325)">
|
||||
<circle class="node-bg" r="30" />
|
||||
<circle class="node-ring" r="30" stroke="var(--gilt)" />
|
||||
<!-- 乾 [1,1,1] -->
|
||||
<rect class="trigram-bar" x="-13" y="-15" width="26" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-8" width="26" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-1" width="26" height="4" rx="1"/>
|
||||
<text class="node-name" y="16" fill="var(--gilt)">乾</text>
|
||||
</g>
|
||||
<text class="node-meta" x="372" y="345">造物者 · 天</text>
|
||||
|
||||
<!-- 坎 ☵ 北·水 司险者(边界风险/石)pos (230,365) -->
|
||||
<g transform="translate(230,365)">
|
||||
<circle class="node-bg" r="30" />
|
||||
<circle class="node-ring" r="30" stroke="var(--slate)" />
|
||||
<!-- 坎 [1,0,1]: 整-破-整 -->
|
||||
<rect class="trigram-bar" x="-13" y="-15" width="26" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-8" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-8" width="11" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-1" width="26" height="4" rx="1"/>
|
||||
<text class="node-name" y="16" fill="var(--slate)">坎</text>
|
||||
</g>
|
||||
<text class="node-meta" x="230" y="412">司险者 · 水</text>
|
||||
|
||||
<!-- 艮 ☶ 东北·山 止守者(边界风险/石)pos (135,325) -->
|
||||
<g transform="translate(135,325)">
|
||||
<circle class="node-bg" r="30" />
|
||||
<circle class="node-ring" r="30" stroke="var(--slate)" />
|
||||
<!-- 艮 [1,0,0]: 整-破-破 -->
|
||||
<rect class="trigram-bar" x="-13" y="-15" width="26" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-8" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-8" width="11" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-1" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-1" width="11" height="4" rx="1"/>
|
||||
<text class="node-name" y="16" fill="var(--slate)">艮</text>
|
||||
</g>
|
||||
<text class="node-meta" x="88" y="345">止守者 · 山</text>
|
||||
|
||||
<!-- 震 ☳ 东·雷 启行者(行动表达/朱)pos (95,230) -->
|
||||
<g transform="translate(95,230)">
|
||||
<circle class="node-bg" r="30" />
|
||||
<circle class="node-ring" r="30" stroke="var(--cinnabar)" />
|
||||
<!-- 震 [0,0,1]: 破-破-整 -->
|
||||
<rect class="trigram-bar" x="-13" y="-15" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-15" width="11" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-8" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-8" width="11" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-1" width="26" height="4" rx="1"/>
|
||||
<text class="node-name" y="16" fill="var(--cinnabar)">震</text>
|
||||
</g>
|
||||
<text class="node-meta" x="48" y="234">启行者 · 雷</text>
|
||||
|
||||
<!-- 巽 ☴ 东南·风 探微者(认知方向/金)pos (135,135) -->
|
||||
<g transform="translate(135,135)">
|
||||
<circle class="node-bg" r="30" />
|
||||
<circle class="node-ring" r="30" stroke="var(--gilt)" />
|
||||
<!-- 巽 [1,1,0]: 整-整-破 -->
|
||||
<rect class="trigram-bar" x="-13" y="-15" width="26" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-8" width="26" height="4" rx="1"/>
|
||||
<rect class="trigram-bar" x="-13" y="-1" width="11" height="4" rx="1"/><rect class="trigram-bar" x="2" y="-1" width="11" height="4" rx="1"/>
|
||||
<text class="node-name" y="16" fill="var(--gilt)">巽</text>
|
||||
</g>
|
||||
<text class="node-meta" x="88" y="120">探微者 · 风</text>
|
||||
|
||||
<!-- 中心太极 + 灵核 -->
|
||||
<circle class="center-disc" cx="230" cy="230" r="40" />
|
||||
<circle class="center-yang" cx="230" cy="230" r="35" />
|
||||
<path class="center-yin" d="M 230,195 A 35,35 0 0,1 230,265 A 17.5,17.5 0 0,1 230,230 A 17.5,17.5 0 0,0 230,195 Z" />
|
||||
<circle class="center-yang" cx="230" cy="247.5" r="5.5" />
|
||||
<circle class="center-yin" cx="230" cy="212.5" r="5.5" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══ 八卦详注 ═══ -->
|
||||
<section>
|
||||
<h3 class="section-heading">八卦详注</h3>
|
||||
<div class="bagua-detail">
|
||||
<!-- 认知与方向 -->
|
||||
<div class="bg-group g1">
|
||||
<div class="bg-group-head"><span class="bg-group-name">认知与方向</span><span class="bg-group-tag">cognition · direction</span></div>
|
||||
<div class="bg-tri">
|
||||
<div class="bg-tri-glyph">
|
||||
<svg viewBox="0 0 30 26"><rect x="2" y="3" width="26" height="4" rx="1" fill="var(--gilt)"/><rect x="2" y="11" width="26" height="4" rx="1" fill="var(--gilt)"/><rect x="2" y="19" width="26" height="4" rx="1" fill="var(--gilt)"/></svg>
|
||||
<span class="bg-tri-name">乾</span>
|
||||
</div>
|
||||
<div class="bg-tri-body">
|
||||
<div class="bg-tri-role">造物者<span class="bg-tri-map">☰ 天 · 西北 · 太一目标代理</span></div>
|
||||
<div class="bg-tri-duty">目标生成、优先级判断、方向引领。</div>
|
||||
<div class="bg-tri-behave">分解长期目标 → 设定里程碑 → 调度子目标</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-tri">
|
||||
<div class="bg-tri-glyph">
|
||||
<svg viewBox="0 0 30 26"><rect x="2" y="3" width="26" height="4" rx="1" fill="var(--gilt)"/><rect x="2" y="11" width="26" height="4" rx="1" fill="var(--gilt)"/><rect x="2" y="19" width="11" height="4" rx="1" fill="var(--gilt)"/><rect x="17" y="19" width="11" height="4" rx="1" fill="var(--gilt)"/></svg>
|
||||
<span class="bg-tri-name">巽</span>
|
||||
</div>
|
||||
<div class="bg-tri-body">
|
||||
<div class="bg-tri-role">探微者<span class="bg-tri-map">☴ 风 · 东南 · 烛照/神识感知代理</span></div>
|
||||
<div class="bg-tri-duty">信息搜集、细粒度探查、搜索。</div>
|
||||
<div class="bg-tri-behave">搜索缺失信息 → 穿透细节 → 填充知识盲区</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 行动与表达 -->
|
||||
<div class="bg-group g2">
|
||||
<div class="bg-group-head"><span class="bg-group-name">行动与表达</span><span class="bg-group-tag">action · expression</span></div>
|
||||
<div class="bg-tri">
|
||||
<div class="bg-tri-glyph">
|
||||
<svg viewBox="0 0 30 26"><rect x="2" y="3" width="11" height="4" rx="1" fill="var(--cinnabar)"/><rect x="17" y="3" width="11" height="4" rx="1" fill="var(--cinnabar)"/><rect x="2" y="11" width="11" height="4" rx="1" fill="var(--cinnabar)"/><rect x="17" y="11" width="11" height="4" rx="1" fill="var(--cinnabar)"/><rect x="2" y="19" width="26" height="4" rx="1" fill="var(--cinnabar)"/></svg>
|
||||
<span class="bg-tri-name">震</span>
|
||||
</div>
|
||||
<div class="bg-tri-body">
|
||||
<div class="bg-tri-role">启行者<span class="bg-tri-map">☳ 雷 · 东 · 百工/洛书行动代理</span></div>
|
||||
<div class="bg-tri-duty">任务启动、事件触发、行动推进。</div>
|
||||
<div class="bg-tri-behave">发起工具调用 → 触发流程 → 推动任务前进</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-tri">
|
||||
<div class="bg-tri-glyph">
|
||||
<svg viewBox="0 0 30 26"><rect x="2" y="3" width="11" height="4" rx="1" fill="var(--cinnabar)"/><rect x="17" y="3" width="11" height="4" rx="1" fill="var(--cinnabar)"/><rect x="2" y="11" width="26" height="4" rx="1" fill="var(--cinnabar)"/><rect x="2" y="19" width="11" height="4" rx="1" fill="var(--cinnabar)"/><rect x="17" y="19" width="11" height="4" rx="1" fill="var(--cinnabar)"/></svg>
|
||||
<span class="bg-tri-name">离</span>
|
||||
</div>
|
||||
<div class="bg-tri-body">
|
||||
<div class="bg-tri-role">宣明者<span class="bg-tri-map">☲ 火 · 南 · 言灵表达代理</span></div>
|
||||
<div class="bg-tri-duty">输出呈现、可视化、解释沟通。</div>
|
||||
<div class="bg-tri-behave">组织语言 → 生成可视化 → 解释系统决策</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 承载与稳定 -->
|
||||
<div class="bg-group g3">
|
||||
<div class="bg-group-head"><span class="bg-group-name">承载与稳定</span><span class="bg-group-tag">bearing · stability</span></div>
|
||||
<div class="bg-tri">
|
||||
<div class="bg-tri-glyph">
|
||||
<svg viewBox="0 0 30 26"><rect x="2" y="3" width="11" height="4" rx="1" fill="var(--patina)"/><rect x="17" y="3" width="11" height="4" rx="1" fill="var(--patina)"/><rect x="2" y="11" width="11" height="4" rx="1" fill="var(--patina)"/><rect x="17" y="11" width="11" height="4" rx="1" fill="var(--patina)"/><rect x="2" y="19" width="11" height="4" rx="1" fill="var(--patina)"/><rect x="17" y="19" width="11" height="4" rx="1" fill="var(--patina)"/></svg>
|
||||
<span class="bg-tri-name">坤</span>
|
||||
</div>
|
||||
<div class="bg-tri-body">
|
||||
<div class="bg-tri-role">藏守者<span class="bg-tri-map">☷ 地 · 西南 · 息壤记忆代理</span></div>
|
||||
<div class="bg-tri-duty">记忆承载、状态保持、事实基线。</div>
|
||||
<div class="bg-tri-behave">整理记忆 → 维护世界模型基线 → 确保信息不丢</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-tri">
|
||||
<div class="bg-tri-glyph">
|
||||
<svg viewBox="0 0 30 26"><rect x="2" y="3" width="11" height="4" rx="1" fill="var(--patina)"/><rect x="17" y="3" width="11" height="4" rx="1" fill="var(--patina)"/><rect x="2" y="11" width="26" height="4" rx="1" fill="var(--patina)"/><rect x="2" y="19" width="26" height="4" rx="1" fill="var(--patina)"/></svg>
|
||||
<span class="bg-tri-name">兑</span>
|
||||
</div>
|
||||
<div class="bg-tri-body">
|
||||
<div class="bg-tri-role">应和者<span class="bg-tri-map">☱ 泽 · 西 · 灵核关系代理</span></div>
|
||||
<div class="bg-tri-duty">交互愉悦、情绪共鸣、陪伴反馈。</div>
|
||||
<div class="bg-tri-behave">感知情绪 → 匹配语气节奏 → 维系对话温度</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 边界与风险 -->
|
||||
<div class="bg-group g4">
|
||||
<div class="bg-group-head"><span class="bg-group-name">边界与风险</span><span class="bg-group-tag">boundary · risk</span></div>
|
||||
<div class="bg-tri">
|
||||
<div class="bg-tri-glyph">
|
||||
<svg viewBox="0 0 30 26"><rect x="2" y="3" width="26" height="4" rx="1" fill="var(--slate)"/><rect x="2" y="11" width="11" height="4" rx="1" fill="var(--slate)"/><rect x="17" y="11" width="11" height="4" rx="1" fill="var(--slate)"/><rect x="2" y="19" width="26" height="4" rx="1" fill="var(--slate)"/></svg>
|
||||
<span class="bg-tri-name">坎</span>
|
||||
</div>
|
||||
<div class="bg-tri-body">
|
||||
<div class="bg-tri-role">司险者<span class="bg-tri-map">☵ 水 · 北 · 司命风控代理</span></div>
|
||||
<div class="bg-tri-duty">风险评估、不确定性分析、失败预案。</div>
|
||||
<div class="bg-tri-behave">评估候选风险 → 识别不确定性 → 提出备选方案</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-tri">
|
||||
<div class="bg-tri-glyph">
|
||||
<svg viewBox="0 0 30 26"><rect x="2" y="3" width="26" height="4" rx="1" fill="var(--slate)"/><rect x="2" y="11" width="11" height="4" rx="1" fill="var(--slate)"/><rect x="17" y="11" width="11" height="4" rx="1" fill="var(--slate)"/><rect x="2" y="19" width="11" height="4" rx="1" fill="var(--slate)"/><rect x="17" y="19" width="11" height="4" rx="1" fill="var(--slate)"/></svg>
|
||||
<span class="bg-tri-name">艮</span>
|
||||
</div>
|
||||
<div class="bg-tri-body">
|
||||
<div class="bg-tri-role">止守者<span class="bg-tri-map">☶ 山 · 东北 · 司命边界代理</span></div>
|
||||
<div class="bg-tri-duty">停止、拒绝、权限节制、冲突回避。</div>
|
||||
<div class="bg-tri-behave">检查权限 → 拒绝越界请求 → 避免冲突升级</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 状态注脚 -->
|
||||
<div class="vision-strip">
|
||||
<span class="seal">愿景边界</span>
|
||||
<span class="desc">四象(认知循环)已作为命名层落地;八卦(<code>天工阁</code> 八原型)属 v2 多智能体愿景,数据结构 <code>agent_roster</code> / <code>agent_role</code> 已在 v1 预留,八原型可逐步作为角色模板进入治理配置。九角色为工程分工,八卦为其人格底色,一角色可兼多卦。</span>
|
||||
<span class="tag">v2 愿景 · 已预留</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,351 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 内驱力页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 内驱力页:太一目標 + 好奇萤火 + 探索天平 + 阴阳制衡
|
||||
* 字段对齐 docs/domain/core-domains.md §5 与
|
||||
* big-cycle-design.md §6(curiosity_questions / exploratory_subgoals)。
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 侧栏(active=内驱力)── */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.char-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--patina); padding: 3px 11px 3px 4px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.char-pill .avatar-mini { width: 18px; height: 18px; border-radius: 50%; background: var(--patina); color: var(--ink); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; }
|
||||
.page-header .spacer { flex: 1; }
|
||||
.page-meta { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg-faint); }
|
||||
.page-meta b { color: var(--gilt); font-weight: 600; font-family: "Songti SC", serif; font-size: 15px; }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
|
||||
/* ═══ 太一目标进度(最高目标 = 内驱力的容器)═══ */
|
||||
.taiyi-card { border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 16px 20px; display: flex; flex-direction: column; gap: 12px; }
|
||||
.taiyi-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
.taiyi-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||
.taiyi-dot.active { background: var(--patina); box-shadow: 0 0 0 3px var(--patina-wash); animation: pulse 2.6s cubic-bezier(0.16,1,0.3,1) infinite; }
|
||||
.taiyi-dot.completed { background: var(--gilt); }
|
||||
@keyframes pulse { 0% { box-shadow: 0 0 0 3px var(--patina-wash); } 70% { box-shadow: 0 0 0 7px transparent; } 100% { box-shadow: 0 0 0 3px var(--patina-wash); } }
|
||||
.taiyi-title { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); }
|
||||
.taiyi-tag { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); padding: 2px 8px; border: 1px solid var(--rule); border-radius: 999px; }
|
||||
.taiyi-status { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-dim); }
|
||||
.taiyi-prog { display: flex; align-items: center; gap: 10px; }
|
||||
.taiyi-prog .track { flex: 1; height: 4px; border-radius: 2px; background: var(--rule); overflow: hidden; }
|
||||
.taiyi-prog .fill { height: 100%; border-radius: 2px; background: linear-gradient(90deg, var(--patina-dim), var(--patina)); }
|
||||
.taiyi-prog .tv { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-dim); width: 32px; text-align: right; }
|
||||
|
||||
/* ═══ 好奇萤火(签名 · 缺口记录,只读)═══
|
||||
每条好奇问题左侧一颗萤火点,亮度 = expected_value。 */
|
||||
.curio-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.curio { position: relative; border: 1px solid var(--rule); border-radius: 7px; padding: 14px 16px 14px 18px; background: var(--surface); display: flex; gap: 12px; transition: border-color 0.15s; }
|
||||
.curio:hover { border-color: var(--gilt-dim); }
|
||||
.curio-fire { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; margin-top: 5px; background: var(--patina); position: relative; }
|
||||
.curio-fire::after { content: ""; position: absolute; inset: -4px; border-radius: 50%; background: radial-gradient(circle, currentColor, transparent 70%); opacity: 0.4; color: var(--patina); }
|
||||
.curio-fire.hi { background: var(--gilt); color: var(--gilt); }
|
||||
.curio-fire.hi::after { opacity: 0.6; animation: fire-pulse 2.4s cubic-bezier(0.16,1,0.3,1) infinite; }
|
||||
@keyframes fire-pulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.7; } }
|
||||
.curio-body { flex: 1; min-width: 0; }
|
||||
.curio-q { font-family: "Songti SC", serif; font-size: 14.5px; font-weight: 600; color: var(--fg); line-height: 1.5; }
|
||||
.curio-meta { display: flex; align-items: center; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
|
||||
.src-tag { font-family: "JetBrains Mono", monospace; font-size: 10px; letter-spacing: 0.04em; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--rule); display: inline-flex; align-items: center; gap: 5px; color: var(--fg-dim); }
|
||||
.src-tag .sdot { width: 5px; height: 5px; border-radius: 50%; }
|
||||
.src-world { color: var(--patina); } .src-world .sdot { background: var(--patina); }
|
||||
.src-memory { color: var(--gilt); } .src-memory .sdot { background: var(--gilt); }
|
||||
.src-percept { color: var(--cinnabar); } .src-percept .sdot { background: var(--cinnabar); opacity: 0.8; }
|
||||
.src-goal { color: var(--fg-dim); } .src-goal .sdot { background: var(--fg-dim); }
|
||||
.src-reflect { color: var(--patina-bright); } .src-reflect .sdot { background: var(--patina-bright); opacity: 0.7; }
|
||||
.src-time { color: var(--gilt); opacity: 0.8; } .src-time .sdot { background: var(--gilt); opacity: 0.7; }
|
||||
.risk-pill { font-family: "JetBrains Mono", monospace; font-size: 10px; padding: 2px 7px; border-radius: 3px; border: 1px solid var(--rule); color: var(--fg-faint); }
|
||||
.risk-pill.L2 { color: var(--gilt); border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.risk-pill.L4 { color: var(--cinnabar); border-color: var(--cinnabar-dim); background: rgba(184,57,46,0.1); }
|
||||
.ent-link { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); }
|
||||
.curio-bars { display: flex; gap: 12px; margin-top: 9px; }
|
||||
.cbar { flex: 1; }
|
||||
.cbar .ck { font-family: "JetBrains Mono", monospace; font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--fg-faint); }
|
||||
.cbar .ctrack { margin-top: 3px; height: 3px; border-radius: 2px; background: var(--rule); overflow: hidden; }
|
||||
.cbar .cfill { height: 100%; border-radius: 2px; }
|
||||
.cbar.value .cfill { background: linear-gradient(90deg, var(--patina-dim), var(--patina)); }
|
||||
.cbar.urg .cfill { background: linear-gradient(90deg, var(--gilt-dim), var(--cinnabar)); }
|
||||
|
||||
/* ═══ 探索天平(签名 · 次生目标的增益-成本权衡)═══
|
||||
水平双轨:左盘信息增益(铜绿,向左),右盘中断成本(朱砂,向右),
|
||||
中间支点 = risk_level。approve 仅标记,不自动执行(v1 边界)。 */
|
||||
.sub-card { border: 1px solid var(--rule); border-left: 3px solid var(--patina-dim); border-radius: 6px; padding: 16px 20px; background: var(--surface); display: flex; flex-direction: column; gap: 12px; }
|
||||
.sub-card.deferred { border-left-color: var(--fg-faint); opacity: 0.78; }
|
||||
.sub-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.sub-from { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--patina); display: inline-flex; align-items: center; gap: 5px; }
|
||||
.sub-from svg { width: 11px; height: 11px; }
|
||||
.sub-title { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); }
|
||||
.sub-status { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 10.5px; padding: 2px 9px; border-radius: 999px; border: 1px solid var(--rule); color: var(--fg-dim); }
|
||||
.sub-status.candidate { color: var(--gilt); border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.sub-status.approved { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.sub-status.deferred { color: var(--fg-faint); }
|
||||
.sub-desc { font-size: 13px; color: var(--fg-dim); line-height: 1.6; }
|
||||
|
||||
.balance { display: grid; grid-template-columns: 1fr 56px 1fr; align-items: center; gap: 10px; padding: 10px 0 4px; }
|
||||
.pan { display: flex; flex-direction: column; gap: 5px; }
|
||||
.pan.right { align-items: flex-end; }
|
||||
.pan .plabel { font-family: "JetBrains Mono", monospace; font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--fg-faint); }
|
||||
.pan .ptrack { width: 100%; height: 6px; border-radius: 3px; background: var(--rule); overflow: hidden; position: relative; }
|
||||
.pan.gain .ptrack { background: linear-gradient(90deg, var(--patina-dim), var(--patina-wash)); }
|
||||
.pan.cost .ptrack { background: linear-gradient(90deg, var(--patina-wash), var(--cinnabar-dim)); }
|
||||
.pan .pfill { position: absolute; top: 0; bottom: 0; border-radius: 3px; }
|
||||
.pan.gain .pfill { left: 0; background: linear-gradient(90deg, var(--patina-dim), var(--patina)); }
|
||||
.pan.cost .pfill { right: 0; background: linear-gradient(90deg, var(--cinnabar-dim), var(--cinnabar)); }
|
||||
.pan .pval { font-family: "JetBrains Mono", monospace; font-size: 11px; }
|
||||
.pan.gain .pval { color: var(--patina); }
|
||||
.pan.cost .pval { color: var(--cinnabar); opacity: 0.88; }
|
||||
.fulcrum { display: flex; flex-direction: column; align-items: center; gap: 4px; }
|
||||
.fulcrum .beam { width: 100%; height: 1.5px; background: var(--gilt-dim); position: relative; }
|
||||
.fulcrum .beam::before, .fulcrum .beam::after { content: ""; position: absolute; top: -2px; width: 5px; height: 5px; border-radius: 50%; background: var(--gilt-dim); }
|
||||
.fulcrum .beam::before { left: 0; } .fulcrum .beam::after { right: 0; }
|
||||
.fulcrum .pivot { width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 8px solid var(--gilt-dim); }
|
||||
.fulcrum .risk { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--gilt); margin-top: 1px; }
|
||||
|
||||
.sub-actions { display: flex; gap: 8px; justify-content: flex-end; padding-top: 4px; border-top: 1px dashed var(--rule); }
|
||||
.vbtn { font-family: inherit; font-size: 12px; font-weight: 500; padding: 6px 14px; border-radius: 4px; cursor: pointer; border: 1px solid var(--rule); background: transparent; color: var(--fg); transition: background 0.12s, border-color 0.12s; }
|
||||
.vbtn:hover { background: var(--surface-hi); }
|
||||
.vbtn.patina { border-color: var(--patina-dim); color: var(--patina); background: var(--patina-wash); }
|
||||
.vbtn.patina:hover { background: rgba(111,163,137,0.14); }
|
||||
.vbtn.note { font-size: 11px; color: var(--fg-faint); align-self: center; margin-right: auto; font-family: "JetBrains Mono", monospace; letter-spacing: 0.02em; border: none; }
|
||||
|
||||
/* 阴阳制衡(太极图)· 阳鱼=铜绿主生发,阴鱼=朱砂主约束 */
|
||||
.yin-yang { position: relative; overflow: hidden; border: 1px solid var(--gilt-dim); border-radius: 8px; padding: 16px 20px; background: linear-gradient(90deg, var(--patina-wash), transparent 50%, rgba(184,57,46,0.06)), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), var(--shadow); display: flex; align-items: center; gap: 18px; }
|
||||
.yy-taiji { width: 58px; height: 58px; flex-shrink: 0; animation: yy-spin 80s linear infinite; transform-origin: 50% 50%; }
|
||||
@keyframes yy-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||
.yy-taiji .yy-ring { fill: none; stroke: var(--gilt-dim); stroke-width: 0.8; }
|
||||
.yy-taiji .yy-yang { fill: var(--patina); }
|
||||
.yy-taiji .yy-yin { fill: var(--cinnabar); }
|
||||
.yy-text { flex: 1; min-width: 0; font-size: 12.5px; color: var(--fg-dim); line-height: 1.6; }
|
||||
.yy-text b { font-family: "Songti SC", serif; color: var(--fg); font-weight: 600; }
|
||||
.yy-text .yang { color: var(--patina); } .yy-text .yin { color: var(--cinnabar); opacity: 0.85; }
|
||||
.yy-meta { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); margin-top: 5px; letter-spacing: 0.02em; }
|
||||
.yy-meta b { color: var(--patina); font-family: inherit; font-weight: 600; }
|
||||
|
||||
@media (max-width: 900px) { .curio-grid { grid-template-columns: 1fr; } .balance { grid-template-columns: 1fr 44px 1fr; } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } .balance { grid-template-columns: 1fr; } .pan.right { align-items: flex-start; } .fulcrum { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="内驱力"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">内驱力</span>
|
||||
<span class="page-sub">大周天 · 好奇与探索</span>
|
||||
<span class="char-pill"><span class="avatar-mini">梧</span>青梧</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="page-meta">好奇 <b>5</b> · 待探 <b>2</b> · 已延 <b>1</b></span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 太一 · 目标进度 -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 太一 <span class="sub">最高目标 · 内驱力在其下生成次生探索</span></div>
|
||||
<div class="taiyi-card">
|
||||
<div class="taiyi-row">
|
||||
<span class="taiyi-dot active"></span>
|
||||
<span class="taiyi-title">做你长期的镜子</span>
|
||||
<span class="taiyi-tag">mission · relationship</span>
|
||||
<span class="taiyi-status">active · 进行中</span>
|
||||
</div>
|
||||
<div class="taiyi-prog">
|
||||
<div class="track"><div class="fill" style="width:48%"></div></div>
|
||||
<span class="tv">48%</span>
|
||||
</div>
|
||||
<p style="font-size: 12.5px; color: var(--fg-dim); margin: 0; line-height: 1.6;">不抢话、不填满,只在你需要时承托。内驱力发现的一切缺口,最终服务于此。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 好奇萤火(签名 · 只读缺口记录) -->
|
||||
<section>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">好奇 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">由内而发的匮乏感 - 缺口记录,只读</span></h3>
|
||||
<div class="curio-grid">
|
||||
<div class="curio">
|
||||
<span class="curio-fire hi" title="预期信息价值 0.81"></span>
|
||||
<div class="curio-body">
|
||||
<div class="curio-q">用户最近频繁提到「辰正」,我还不清楚它在整个系统中的边界。</div>
|
||||
<div class="curio-meta">
|
||||
<span class="src-tag src-memory"><span class="sdot"></span>memory_gap</span>
|
||||
<span class="risk-pill L2">L2</span>
|
||||
<span class="ent-link">@辰正 · @太一枢</span>
|
||||
</div>
|
||||
<div class="curio-bars">
|
||||
<div class="cbar value"><div class="ck">信息价值</div><div class="ctrack"><div class="cfill" style="width:81%"></div></div></div>
|
||||
<div class="cbar urg"><div class="ck">紧迫</div><div class="ctrack"><div class="cfill" style="width:46%"></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="curio">
|
||||
<span class="curio-fire hi"></span>
|
||||
<div class="curio-body">
|
||||
<div class="curio-q">用户上次判断偏好简洁回答,这个判断现在是否仍然成立?</div>
|
||||
<div class="curio-meta">
|
||||
<span class="src-tag src-reflect"><span class="sdot"></span>reflection</span>
|
||||
<span class="risk-pill">L1</span>
|
||||
<span class="ent-link">@偏好简短回复</span>
|
||||
</div>
|
||||
<div class="curio-bars">
|
||||
<div class="cbar value"><div class="ck">信息价值</div><div class="ctrack"><div class="cfill" style="width:68%"></div></div></div>
|
||||
<div class="cbar urg"><div class="ck">紧迫</div><div class="ctrack"><div class="cfill" style="width:30%"></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="curio">
|
||||
<span class="curio-fire"></span>
|
||||
<div class="curio-body">
|
||||
<div class="curio-q">深夜时段连续对话已 3 次,是否应主动收敛回应长度?</div>
|
||||
<div class="curio-meta">
|
||||
<span class="src-tag src-time"><span class="sdot"></span>time_rhythm</span>
|
||||
<span class="risk-pill L2">L2</span>
|
||||
<span class="ent-link">@深夜 · @表达风格</span>
|
||||
</div>
|
||||
<div class="curio-bars">
|
||||
<div class="cbar value"><div class="ck">信息价值</div><div class="ctrack"><div class="cfill" style="width:58%"></div></div></div>
|
||||
<div class="cbar urg"><div class="ck">紧迫</div><div class="ctrack"><div class="cfill" style="width:72%"></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="curio">
|
||||
<span class="curio-fire"></span>
|
||||
<div class="curio-body">
|
||||
<div class="curio-q">世界模型里「云岚」与「青梧」的关系尚未明确,是否同属一脉?</div>
|
||||
<div class="curio-meta">
|
||||
<span class="src-tag src-world"><span class="sdot"></span>world_uncertainty</span>
|
||||
<span class="risk-pill">L1</span>
|
||||
<span class="ent-link">@云岚 @青梧 · 置信 0.42</span>
|
||||
</div>
|
||||
<div class="curio-bars">
|
||||
<div class="cbar value"><div class="ck">信息价值</div><div class="ctrack"><div class="cfill" style="width:44%"></div></div></div>
|
||||
<div class="cbar urg"><div class="ck">紧迫</div><div class="ctrack"><div class="cfill" style="width:22%"></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 探索天平(签名 · 次生目标增益-成本权衡) -->
|
||||
<section>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">待探 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">好奇转化的次生目标 · 增益与打扰的权衡</span></h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<div class="sub-card">
|
||||
<div class="sub-top">
|
||||
<span class="sub-from"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M8 2v8M5 7l3 3 3-3M3 13h10" /></svg>好奇 → 探索</span>
|
||||
<span class="sub-title">读取近期任务上下文,补全「辰正」在世界模型中的边界</span>
|
||||
<span class="sub-status candidate">candidate</span>
|
||||
</div>
|
||||
<div class="sub-desc">由「用户频繁提到辰正」转化。低风险读取,不涉及外部工具,可在后台完成。</div>
|
||||
<div class="balance">
|
||||
<div class="pan gain">
|
||||
<span class="plabel">预期增益</span>
|
||||
<div class="ptrack"><div class="pfill" style="width:78%"></div></div>
|
||||
<span class="pval">0.78</span>
|
||||
</div>
|
||||
<div class="fulcrum">
|
||||
<div class="beam"></div>
|
||||
<div class="pivot"></div>
|
||||
<span class="risk">L2</span>
|
||||
</div>
|
||||
<div class="pan cost right">
|
||||
<span class="plabel">打扰成本</span>
|
||||
<div class="ptrack"><div class="pfill" style="width:18%"></div></div>
|
||||
<span class="pval">0.18</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-actions">
|
||||
<span class="vbtn note">approve 仅标记 · 本期不自动执行</span>
|
||||
<button class="vbtn" type="button">延后</button>
|
||||
<button class="vbtn" type="button">驳回</button>
|
||||
<button class="vbtn patina" type="button">准予探索</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sub-card">
|
||||
<div class="sub-top">
|
||||
<span class="sub-from"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M8 2v8M5 7l3 3 3-3M3 13h10" /></svg>好奇 → 探索</span>
|
||||
<span class="sub-title">询问用户是否愿意介绍当前在做的项目目标</span>
|
||||
<span class="sub-status candidate">candidate</span>
|
||||
</div>
|
||||
<div class="sub-desc">由记忆缺口转化。需用户开口,打扰成本较高;建议等用户主动提及项目时顺势询问。</div>
|
||||
<div class="balance">
|
||||
<div class="pan gain">
|
||||
<span class="plabel">预期增益</span>
|
||||
<div class="ptrack"><div class="pfill" style="width:64%"></div></div>
|
||||
<span class="pval">0.64</span>
|
||||
</div>
|
||||
<div class="fulcrum">
|
||||
<div class="beam"></div>
|
||||
<div class="pivot"></div>
|
||||
<span class="risk">L1</span>
|
||||
</div>
|
||||
<div class="pan cost right">
|
||||
<span class="plabel">打扰成本</span>
|
||||
<div class="ptrack"><div class="pfill" style="width:58%"></div></div>
|
||||
<span class="pval">0.58</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-actions">
|
||||
<span class="vbtn note">gain ≈ cost · 权衡偏紧</span>
|
||||
<button class="vbtn" type="button">延后</button>
|
||||
<button class="vbtn" type="button">驳回</button>
|
||||
<button class="vbtn patina" type="button">准予探索</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 阴阳制衡(太极图)-->
|
||||
<div class="yin-yang">
|
||||
<svg class="yy-taiji" viewBox="0 0 200 200" aria-hidden="true">
|
||||
<circle class="yy-ring" cx="100" cy="100" r="96" />
|
||||
<circle class="yy-yang" cx="100" cy="100" r="88" />
|
||||
<path class="yy-yin" d="M 100,12 A 88,88 0 0,1 100,188 A 44,44 0 0,1 100,100 A 44,44 0 0,0 100,12 Z" opacity="0.88" />
|
||||
<circle class="yy-yang" cx="100" cy="144" r="13" />
|
||||
<circle class="yy-yin" cx="100" cy="56" r="13" opacity="0.88" />
|
||||
</svg>
|
||||
<div class="yy-text">
|
||||
<b>阴阳制衡</b> · 内驱力持续生成候选(<span class="yang">阳 · 生发</span>),司命分级过滤、你按需确认(<span class="yin">阴 · 约束</span>)。候选零产生则僵化(阴盛),悉数被驳则阳亢--生成节奏与治理粒度需相称。
|
||||
<div class="yy-meta">阴阳平衡度 = 通过率 / (生成率 + ε) · 当前 <b>0.94 · 动态平衡</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,332 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 演化页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 演化页:周天四镜 + 候选层积 + 朱砂待裁 + 应用沉积
|
||||
* 字段对齐 docs/domain/core-domains.md §5(evolution_candidates)
|
||||
* 与 contract-glossary.md §12(演化候选枚举)。
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 侧栏(active=演化)── */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.char-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--patina); padding: 3px 11px 3px 4px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.char-pill .avatar-mini { width: 18px; height: 18px; border-radius: 50%; background: var(--patina); color: var(--ink); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; }
|
||||
.page-header .spacer { flex: 1; }
|
||||
.page-meta { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg-faint); }
|
||||
.page-meta b { color: var(--gilt); font-weight: 600; font-family: "Songti SC", serif; font-size: 15px; }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
|
||||
/* ═══ 周天四镜(签名 · 大周天阶段链)═══
|
||||
评估 → 演化 → 候选 → 记忆,四面小铜镜横向串联。
|
||||
铜镜三态:done 铜绿、active 鎏金流光、pending 镜背虚。 */
|
||||
.cycle-card { position: relative; overflow: hidden; border: 1px solid var(--gilt-dim); border-radius: 8px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); padding: 22px 26px 20px; }
|
||||
.cycle-card::after { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); opacity: 0.018; mix-blend-mode: overlay; }
|
||||
.cycle-card > * { position: relative; z-index: 1; }
|
||||
.cycle-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 18px; flex-wrap: wrap; }
|
||||
.cycle-title { font-family: "Songti SC", serif; font-size: 16px; font-weight: 600; color: var(--fg); letter-spacing: 0.04em; }
|
||||
.cycle-id { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--fg-faint); }
|
||||
.cycle-state { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--gilt); padding: 3px 10px; border: 1px solid var(--gilt-dim); border-radius: 999px; background: var(--gilt-faint); }
|
||||
|
||||
.mirror-chain { display: flex; align-items: stretch; gap: 0; }
|
||||
.mc-step { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 4px 6px; min-width: 0; }
|
||||
.mc-mirror { width: 46px; height: 46px; border-radius: 50%; display: flex; align-items: center; justify-content: center; position: relative; flex-shrink: 0; }
|
||||
.mc-mirror .disc { width: 100%; height: 100%; border-radius: 50%; border: 1.5px solid var(--fg-faint); background: transparent; display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 15px; font-weight: 700; color: var(--fg-faint); }
|
||||
.mc-step.done .mc-mirror .disc { border-color: var(--patina-dim); background: var(--patina-wash); color: var(--patina); }
|
||||
.mc-step.active .mc-mirror { }
|
||||
.mc-step.active .mc-mirror .disc { border-color: var(--gilt); background: radial-gradient(circle at 38% 32%, rgba(255,255,255,0.32), transparent 60%), linear-gradient(160deg, var(--gilt), rgba(201,162,74,0.55)); color: var(--ink); box-shadow: 0 0 0 4px var(--gilt-faint), 0 0 22px -4px rgba(201,162,74,0.5); animation: node-pulse 2.6s cubic-bezier(0.16,1,0.3,1) infinite; }
|
||||
.mc-step.active .mc-mirror::after { content: ""; position: absolute; inset: -3px; border-radius: 50%; border: 1px dashed var(--gilt-dim); opacity: 0.6; }
|
||||
@keyframes node-pulse { 0% { box-shadow: 0 0 0 4px var(--gilt-faint), 0 0 22px -4px rgba(201,162,74,0.5); } 70% { box-shadow: 0 0 0 9px transparent, 0 0 22px -4px rgba(201,162,74,0.5); } 100% { box-shadow: 0 0 0 4px var(--gilt-faint), 0 0 22px -4px rgba(201,162,74,0.5); } }
|
||||
.mc-name { font-family: "Songti SC", serif; font-size: 13.5px; font-weight: 600; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.mc-step.done .mc-name { color: var(--fg-dim); }
|
||||
.mc-step.active .mc-name { color: var(--gilt); }
|
||||
.mc-sub { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.mc-yield { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--patina); }
|
||||
.mc-step.active .mc-yield { color: var(--gilt); }
|
||||
.mc-link { align-self: center; margin-top: 18px; width: 22px; height: 1.5px; background: var(--rule); flex-shrink: 0; }
|
||||
.mc-link.done { background: var(--patina-dim); }
|
||||
|
||||
/* 态势快照 */
|
||||
.snapshot-row { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--rule); display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
|
||||
.snap-mini .k { font-family: "JetBrains Mono", monospace; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--fg-faint); }
|
||||
.snap-mini .v { font-family: "Songti SC", serif; font-size: 17px; font-weight: 600; color: var(--fg); margin-top: 3px; display: flex; align-items: baseline; gap: 4px; }
|
||||
.snap-mini .v small { font-size: 11px; font-weight: 400; color: var(--fg-dim); font-family: "JetBrains Mono", monospace; }
|
||||
.snap-verdict { grid-column: 1 / -1; margin-top: 6px; padding: 11px 14px; border-left: 2px solid var(--gilt-dim); background: var(--gilt-faint); border-radius: 0 5px 5px 0; font-size: 13px; color: var(--fg-dim); line-height: 1.6; }
|
||||
.snap-verdict b { font-family: "Songti SC", serif; color: var(--gilt); font-weight: 600; }
|
||||
|
||||
/* ═══ 候选层积(主区 + 应用沉积侧栏)═══ */
|
||||
.lower-region { display: flex; gap: 20px; align-items: flex-start; }
|
||||
.cand-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 18px; }
|
||||
.strata-rail { width: 290px; flex-shrink: 0; }
|
||||
|
||||
/* 朱砂待裁卡(L4)*/
|
||||
.proposal { border: 1px solid var(--cinnabar); border-left: 3px solid var(--cinnabar); border-radius: 6px; padding: 17px 20px; background: radial-gradient(120% 100% at 0% 0%, rgba(184, 57, 46, 0.22), transparent 55%), rgba(184, 57, 46, 0.1); box-shadow: 0 0 28px -8px rgba(184, 57, 46, 0.35); }
|
||||
.prop-head { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; flex-wrap: wrap; }
|
||||
.prop-seal { font-family: "Songti SC", serif; color: var(--cinnabar); font-weight: 700; font-size: 14.5px; letter-spacing: 0.08em; }
|
||||
.prop-type { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-dim); padding: 2px 8px; border: 1px solid var(--rule); border-radius: 999px; }
|
||||
.prop-risk { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.08em; color: var(--cinnabar); opacity: 0.78; margin-left: auto; }
|
||||
.prop-title { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); margin-bottom: 8px; }
|
||||
.prop-reason { font-size: 13px; color: var(--fg); line-height: 1.65; margin-bottom: 12px; }
|
||||
.prop-diff { background: rgba(0, 0, 0, 0.18); border: 1px solid var(--cinnabar-dim); border-radius: 4px; padding: 10px 12px; font-size: 12.5px; line-height: 1.6; margin-bottom: 12px; }
|
||||
[data-theme="light"] .prop-diff { background: rgba(255, 248, 235, 0.6); }
|
||||
.prop-diff .del { color: var(--cinnabar); text-decoration: line-through; opacity: 0.7; }
|
||||
.prop-diff .add { color: var(--patina); }
|
||||
.prop-diff .field-tag { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--gilt); display: block; margin-bottom: 4px; letter-spacing: 0.06em; }
|
||||
.prop-foot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
.evi-link { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); padding: 2px 7px; border: 1px solid var(--rule); border-radius: 3px; }
|
||||
.prop-actions { display: flex; gap: 8px; margin-left: auto; }
|
||||
.vbtn { font-family: inherit; font-size: 12px; font-weight: 500; padding: 6px 14px; border-radius: 4px; cursor: pointer; border: 1px solid var(--rule); background: transparent; color: var(--fg); transition: background 0.12s, border-color 0.12s; }
|
||||
.vbtn:hover { background: var(--surface-hi); }
|
||||
.vbtn.cinnabar { border-color: var(--cinnabar); color: var(--cinnabar); background: rgba(184, 57, 46, 0.08); }
|
||||
.vbtn.cinnabar:hover { background: rgba(184, 57, 46, 0.16); }
|
||||
|
||||
/* 候选卡(非朱砂)*/
|
||||
.cand-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
.cand-card { border: 1px solid var(--rule); border-radius: 7px; padding: 14px 16px; background: var(--surface); display: flex; flex-direction: column; gap: 10px; transition: border-color 0.15s; }
|
||||
.cand-card:hover { border-color: var(--gilt-dim); }
|
||||
.cand-card.auto { border-left: 3px solid var(--patina-dim); }
|
||||
.cand-top { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
|
||||
.cand-glyph { width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border: 1px solid var(--gilt-dim); background: var(--gilt-faint); font-family: "Songti SC", serif; font-size: 13px; font-weight: 700; color: var(--gilt); }
|
||||
.cand-card.auto .cand-glyph { border-color: var(--patina-dim); background: var(--patina-wash); color: var(--patina); }
|
||||
.cand-title { font-family: "Songti SC", serif; font-size: 14.5px; font-weight: 600; color: var(--fg); }
|
||||
.cand-tag { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); padding: 2px 8px; border: 1px solid var(--rule); border-radius: 999px; }
|
||||
.cand-judge { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 10.5px; letter-spacing: 0.04em; white-space: nowrap; }
|
||||
.cand-judge.auto { color: var(--patina); }
|
||||
.cand-judge.manual { color: var(--cinnabar); opacity: 0.85; }
|
||||
.cand-desc { font-size: 13px; color: var(--fg-dim); line-height: 1.6; }
|
||||
/* 三维评分 */
|
||||
.score-trio { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; padding-top: 10px; border-top: 1px solid var(--rule); }
|
||||
.score .k { font-family: "JetBrains Mono", monospace; font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--fg-faint); }
|
||||
.score .track { margin-top: 4px; height: 4px; border-radius: 2px; background: var(--rule); overflow: hidden; }
|
||||
.score .fill { height: 100%; border-radius: 2px; }
|
||||
.score.benefit .fill { background: linear-gradient(90deg, var(--patina-dim), var(--patina)); }
|
||||
.score.risk .fill { background: linear-gradient(90deg, var(--gilt-dim), var(--cinnabar)); }
|
||||
.score.conf .fill { background: linear-gradient(90deg, var(--gilt-dim), var(--gilt)); }
|
||||
.score .tv { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-dim); margin-top: 3px; }
|
||||
|
||||
/* 应用沉积(右栏,复用层积样式)*/
|
||||
.strata-card { background: var(--surface); border: 1px solid var(--rule); border-radius: 8px; padding: 18px 18px 16px; }
|
||||
.strata-list { position: relative; }
|
||||
.strata-list::before { content: ""; position: absolute; left: 11px; top: 14px; bottom: 14px; width: 1px; background: linear-gradient(180deg, var(--patina-dim), var(--rule) 50%, transparent); }
|
||||
.stratum { position: relative; padding: 10px 0 10px 32px; }
|
||||
.stratum + .stratum { border-top: 1px dashed var(--rule); }
|
||||
.stratum .node { position: absolute; left: 6px; top: 14px; width: 11px; height: 11px; border-radius: 50%; background: var(--patina-dim); border: 1.5px solid var(--patina-dim); z-index: 1; }
|
||||
.stratum.rejected .node { background: transparent; border-color: var(--cinnabar-dim); border-style: dashed; }
|
||||
.stratum-head { display: flex; align-items: baseline; gap: 8px; }
|
||||
.stratum-ver { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); }
|
||||
.stratum.rejected .stratum-ver { color: var(--fg-dim); text-decoration: line-through; opacity: 0.7; }
|
||||
.stratum-when { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); margin-left: auto; }
|
||||
.stratum-reason { font-size: 12px; color: var(--fg-dim); margin-top: 3px; line-height: 1.5; }
|
||||
.stratum-rollback { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--gilt); margin-top: 5px; cursor: pointer; border: none; background: none; padding: 0; }
|
||||
.stratum-rollback:hover { color: var(--cinnabar); }
|
||||
|
||||
@media (max-width: 1080px) { .lower-region { flex-direction: column; } .strata-rail { width: 100%; } }
|
||||
@media (max-width: 900px) { .snapshot-row { grid-template-columns: repeat(2, 1fr); } .mc-link { width: 14px; } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } .mirror-chain { gap: 2px; } .mc-link { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="演化"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">演化</span>
|
||||
<span class="page-sub">大周天 · 演化候选</span>
|
||||
<span class="char-pill"><span class="avatar-mini">梧</span>青梧</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="page-meta">待裁 <b>1</b> · 半自动 <b style="color: var(--patina)">2</b> · 已应用 <b>4</b></span>
|
||||
<button class="btn-ghost" type="button">立即触发一轮</button>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 周天四镜(签名) -->
|
||||
<div class="cycle-card">
|
||||
<div class="cycle-head">
|
||||
<span class="cycle-title">大周天 · 候选阶段</span>
|
||||
<span class="cycle-id">cycle_01JZK7Q3B…4F · 触发自会话结束</span>
|
||||
<span class="cycle-state">候选生成中</span>
|
||||
</div>
|
||||
<div class="mirror-chain">
|
||||
<div class="mc-step done">
|
||||
<div class="mc-mirror"><div class="disc">评</div></div>
|
||||
<span class="mc-name">评估</span>
|
||||
<span class="mc-sub">evaluation</span>
|
||||
<span class="mc-yield">态势 ✓</span>
|
||||
</div>
|
||||
<span class="mc-link done"></span>
|
||||
<div class="mc-step done">
|
||||
<div class="mc-mirror"><div class="disc">演</div></div>
|
||||
<span class="mc-name">演化</span>
|
||||
<span class="mc-sub">evolution</span>
|
||||
<span class="mc-yield">策略 ✓</span>
|
||||
</div>
|
||||
<span class="mc-link done"></span>
|
||||
<div class="mc-step active">
|
||||
<div class="mc-mirror"><div class="disc">候</div></div>
|
||||
<span class="mc-name">候选</span>
|
||||
<span class="mc-sub">candidate · 当阶</span>
|
||||
<span class="mc-yield">3 条草拟</span>
|
||||
</div>
|
||||
<span class="mc-link"></span>
|
||||
<div class="mc-step">
|
||||
<div class="mc-mirror"><div class="disc">藏</div></div>
|
||||
<span class="mc-name">入藏</span>
|
||||
<span class="mc-sub">memory · 未达</span>
|
||||
<span class="mc-yield">-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="snapshot-row">
|
||||
<div class="snap-mini"><div class="k">最近记忆</div><div class="v">12<small>条入参</small></div></div>
|
||||
<div class="snap-mini"><div class="k">人格版本</div><div class="v">v7<small>当前</small></div></div>
|
||||
<div class="snap-mini"><div class="k">世界实体</div><div class="v">23<small>个</small></div></div>
|
||||
<div class="snap-mini"><div class="k">内驱力</div><div class="v">2<small>条好奇</small></div></div>
|
||||
<div class="snap-verdict"><b>评估结论:</b>近 12 条记忆呈「回应偏长」信号,可收敛风格字段;身份画稳定,无核心变更必要。建议生成 1 条风格类半自动候选,其余维持现状。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 候选层积 + 应用沉积 -->
|
||||
<div class="lower-region">
|
||||
<div class="cand-main">
|
||||
<!-- 朱砂待裁(L4) -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 待裁 <span class="sub">L4 核心变更须人工裁定方可写入</span></div>
|
||||
<div class="proposal">
|
||||
<div class="prop-head">
|
||||
<span class="prop-seal">朱批待裁</span>
|
||||
<span class="prop-type">personality · identity</span>
|
||||
<span class="prop-risk">L4 · 满朱 · 须人工</span>
|
||||
</div>
|
||||
<div class="prop-title">本我镜自述收敛末句</div>
|
||||
<div class="prop-reason">神识据近 12 条对话判断,青梧自述末句「愿以耐心陪你走过每一段」略显填满,与「不被填满」的边界有张力。身份画属 L4 核心,须你裁定。</div>
|
||||
<div class="prop-diff">
|
||||
<span class="field-tag">identity_profile · 本我镜</span>
|
||||
<span class="del">我是青梧。喜静,不喜喧。观人多于言人,记事多于论事。愿以耐心陪你走过每一段。</span><br />
|
||||
<span class="add">我是青梧。喜静,不喜喧。观人多于言人,记事多于论事。</span>
|
||||
</div>
|
||||
<div class="prop-foot">
|
||||
<span class="evi-link">evi_01J…A2</span>
|
||||
<span class="evi-link">evi_01J…B7</span>
|
||||
<span class="evi-link">+2</span>
|
||||
<div class="prop-actions">
|
||||
<button class="vbtn" type="button">改由对话习得</button>
|
||||
<button class="vbtn" type="button">驳回</button>
|
||||
<button class="vbtn cinnabar" type="button">准予写入 v8</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 半自动候选 -->
|
||||
<section>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">半自动 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">L0–L3 且 auto_apply_allowed · 生成时已写入,可回滚</span></h3>
|
||||
<div class="cand-list">
|
||||
<div class="cand-card auto">
|
||||
<div class="cand-top">
|
||||
<div class="cand-glyph">辞</div>
|
||||
<span class="cand-title">表达风格 · 简练度 +1 档</span>
|
||||
<span class="cand-tag">personality · expression_style</span>
|
||||
<span class="cand-judge auto">L2 · 已应用</span>
|
||||
</div>
|
||||
<div class="cand-desc">减少惊叹号与问号,句号占比上调;与「不被填满」的边界一致。</div>
|
||||
<div class="score-trio">
|
||||
<div class="score benefit"><div class="k">收益</div><div class="track"><div class="fill" style="width:72%"></div></div><div class="tv">0.72</div></div>
|
||||
<div class="score risk"><div class="k">风险</div><div class="track"><div class="fill" style="width:14%"></div></div><div class="tv">0.14</div></div>
|
||||
<div class="score conf"><div class="k">置信</div><div class="track"><div class="fill" style="width:81%"></div></div><div class="tv">0.81</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cand-card auto">
|
||||
<div class="cand-top">
|
||||
<div class="cand-glyph">序</div>
|
||||
<span class="cand-title">新增程序性记忆 · 深夜不打扰</span>
|
||||
<span class="cand-tag">procedural</span>
|
||||
<span class="cand-judge auto">L3 · 已应用</span>
|
||||
</div>
|
||||
<div class="cand-desc">记录「用户 23:00 后倾向简短回应」,供辰正与对话枢调用。</div>
|
||||
<div class="score-trio">
|
||||
<div class="score benefit"><div class="k">收益</div><div class="track"><div class="fill" style="width:66%"></div></div><div class="tv">0.66</div></div>
|
||||
<div class="score risk"><div class="k">风险</div><div class="track"><div class="fill" style="width:9%"></div></div><div class="tv">0.09</div></div>
|
||||
<div class="score conf"><div class="k">置信</div><div class="track"><div class="fill" style="width:74%"></div></div><div class="tv">0.74</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 应用沉积 -->
|
||||
<div class="strata-rail">
|
||||
<div class="strata-card">
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:12px">应用沉积 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">演化之层积</span></h3>
|
||||
<div class="strata-list">
|
||||
<div class="stratum">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">风格简练 +1</span><span class="stratum-when">07-12</span></div>
|
||||
<div class="stratum-reason">expression_style · 半自动 · 源自大周天</div>
|
||||
<button class="stratum-rollback" type="button">回滚此层</button>
|
||||
</div>
|
||||
<div class="stratum">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">深夜不打扰</span><span class="stratum-when">07-09</span></div>
|
||||
<div class="stratum-reason">procedural · 半自动</div>
|
||||
</div>
|
||||
<div class="stratum">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">人格权重校准</span><span class="stratum-when">06-28</span></div>
|
||||
<div class="stratum-reason">personality_weights · 尽责 +0.05</div>
|
||||
<button class="stratum-rollback" type="button">回滚此层</button>
|
||||
</div>
|
||||
<div class="stratum rejected">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">工具亲和调整</span><span class="stratum-when">06-12</span></div>
|
||||
<div class="stratum-reason">tool_strategy · 驳回 · 证据不足</div>
|
||||
</div>
|
||||
<div class="stratum" style="opacity: 0.6">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">初始种魂</span><span class="stratum-when">04 月</span></div>
|
||||
<div class="stratum-reason">自模板而来 · v1</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,412 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 设计原型目录</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 设计原型目录(index)
|
||||
* 全部原型的可点击入口;每张卡带 live iframe 缩略图(原型
|
||||
* 改动即时反映,无需重新截图)。自身不注入 sidebar。
|
||||
* ============================================================ */
|
||||
.stage { max-width: 1180px; margin: 0 auto; padding: 56px 32px 80px; }
|
||||
|
||||
/* ── hero ── */
|
||||
.hero { position: relative; margin-bottom: 48px; padding-bottom: 32px; border-bottom: 1px solid var(--rule); }
|
||||
.hero h1 { font-family: "Songti SC", serif; font-size: 44px; font-weight: 700; letter-spacing: 0.06em; margin: 0 0 6px; color: var(--fg); }
|
||||
.hero .lede { font-size: 14.5px; color: var(--fg-dim); line-height: 1.7; max-width: 660px; }
|
||||
.hero .lede code { font-family: "JetBrains Mono", monospace; font-size: 13px; color: var(--gilt); }
|
||||
|
||||
/* ── 推荐阅读路径:3 步胶囊,首次访客的引导线 ── */
|
||||
.reading-path { display: flex; align-items: stretch; gap: 10px; margin-top: 22px; flex-wrap: wrap; }
|
||||
.path-label { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.1em; color: var(--fg-faint); text-transform: uppercase; align-self: center; margin-right: 4px; }
|
||||
.path-step { display: flex; align-items: center; gap: 10px; padding: 9px 16px 9px 12px; border: 1px solid var(--rule); border-radius: 12px; background: var(--surface); text-decoration: none; transition: border-color var(--duration-normal), background var(--duration-normal), transform var(--duration-normal); }
|
||||
.path-step:hover { border-color: var(--patina-dim); background: var(--surface-hi); transform: translateY(-1px); }
|
||||
.path-step .step-num { font-family: "Songti SC", serif; font-size: 18px; font-weight: 700; color: var(--gilt); line-height: 1; flex-shrink: 0; }
|
||||
.path-step .step-body { display: flex; flex-direction: column; gap: 1px; }
|
||||
.path-step .step-title { font-family: "Songti SC", serif; font-size: 13.5px; font-weight: 600; color: var(--fg); }
|
||||
.path-step .step-desc { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); letter-spacing: 0.02em; }
|
||||
.path-arrow { align-self: center; color: var(--fg-faint); font-size: 14px; opacity: 0.6; }
|
||||
|
||||
.hero-bar { display: flex; align-items: center; gap: 16px; margin-top: 22px; flex-wrap: wrap; }
|
||||
.stats { display: flex; gap: 18px; flex-wrap: wrap; }
|
||||
.stat { display: flex; align-items: baseline; gap: 6px; }
|
||||
.stat b { font-family: "Songti SC", serif; font-size: 22px; font-weight: 700; color: var(--patina); }
|
||||
.stat span { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-faint); letter-spacing: 0.06em; }
|
||||
.hero-bar .spacer { flex: 1; }
|
||||
|
||||
/* ── 分组 ── */
|
||||
.group { margin-top: 42px; }
|
||||
.group-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 16px; }
|
||||
.group-title { font-family: "Songti SC", serif; font-size: 17px; font-weight: 700; letter-spacing: 0.06em; color: var(--fg); }
|
||||
.group-sub { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-faint); letter-spacing: 0.06em; }
|
||||
|
||||
/* ── 共享层文件卡网格(无缩略图) ── */
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(252px, 1fr)); gap: 12px; }
|
||||
|
||||
/* ── 页面原型网格(固定列宽对齐 live 缩略图) ── */
|
||||
.grid-thumb { display: grid; grid-template-columns: repeat(auto-fill, 268px); gap: 14px; }
|
||||
|
||||
.proto { display: flex; flex-direction: column; padding: 14px 16px; border: 1px solid var(--rule); border-radius: 12px; background: var(--surface); text-decoration: none; transition: border-color 0.15s, transform 0.15s, background 0.15s; position: relative; overflow: hidden; }
|
||||
.proto::after { content: ""; position: absolute; inset: 0; background-image: var(--sweep-gradient); background-size: 220% 100%; background-position: -40% 0; opacity: 0; transition: opacity 0.4s; pointer-events: none; }
|
||||
.proto:hover { border-color: var(--patina-dim); transform: translateY(-2px); background: var(--surface-hi); }
|
||||
.proto:hover::after { opacity: 0.5; }
|
||||
.proto-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||
.proto-title { font-family: "Songti SC", serif; font-size: 15.5px; font-weight: 600; color: var(--fg); }
|
||||
.proto-route { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--patina); letter-spacing: 0.02em; margin-top: 2px; }
|
||||
.proto-route.none { color: var(--fg-faint); }
|
||||
.proto-route .draft { color: var(--gilt); font-size: 9.5px; margin-left: 5px; opacity: 0.85; }
|
||||
.proto-desc { font-size: 12px; color: var(--fg-dim); line-height: 1.5; margin-top: 6px; }
|
||||
|
||||
/* ── live 缩略图 ──
|
||||
iframe 固定 960×602,scale 0.2792 缩到 268×168 正好填满一列;
|
||||
pointer-events:none 让点击穿透到卡片 <a>;lazy 仅加载视口内。 */
|
||||
.thumb { position: relative; width: 100%; height: 168px; border-radius: 10px; overflow: hidden; margin-bottom: 10px; border: 1px solid var(--rule); background: var(--ink); transition: border-color 0.15s; }
|
||||
.thumb iframe { position: absolute; top: 0; left: 0; width: 960px; height: 602px; border: 0; transform: scale(0.2792); transform-origin: 0 0; pointer-events: none; background: var(--ink); }
|
||||
.thumb::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, transparent 62%, var(--surface) 100%); pointer-events: none; }
|
||||
.proto:hover .thumb { border-color: var(--patina-dim); }
|
||||
|
||||
/* 文件卡(共享层) */
|
||||
.proto.file .proto-title { color: var(--gilt); font-family: "JetBrains Mono", monospace; font-size: 13px; }
|
||||
|
||||
/* 状态点 */
|
||||
.status { display: inline-flex; align-items: center; gap: 5px; font-family: "JetBrains Mono", monospace; font-size: 10px; letter-spacing: 0.04em; white-space: nowrap; }
|
||||
.status::before { content: ""; width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
||||
.status-ok { color: var(--patina); } .status-ok::before { background: var(--patina); }
|
||||
.status-maybe { color: var(--gilt); } .status-maybe::before { background: var(--gilt); }
|
||||
.status-todo { color: var(--fg-faint); } .status-todo::before { background: var(--fg-faint); }
|
||||
.status-bench { color: var(--gilt); } .status-bench::before { background: var(--gilt); box-shadow: 0 0 8px var(--gilt); }
|
||||
|
||||
/* ── 预览提示 ── */
|
||||
.preview-hint { margin-top: 56px; padding: 18px 22px; border: 1px solid var(--gilt-dim); border-radius: 12px; background: var(--face-sheen), var(--surface); }
|
||||
.preview-hint .t { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin-bottom: 8px; }
|
||||
.preview-hint code { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--patina); background: var(--surface-hi); padding: 2px 7px; border-radius: 4px; }
|
||||
.preview-hint p { font-size: 12.5px; color: var(--fg-dim); line-height: 1.7; margin: 0; }
|
||||
|
||||
/* ── 响应式:窄屏隐藏缩略图、网格自适应 ── */
|
||||
@media (max-width: 720px) {
|
||||
.grid-thumb { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
|
||||
.thumb { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="stage">
|
||||
|
||||
<!-- ════════ hero ════════ -->
|
||||
<header class="hero">
|
||||
<h1>魂鏡 · 設計原型</h1>
|
||||
<p class="lede">铜镜鉴心视觉参考稿集合。每个原型是自包含的 HTML,共享 <code>tokens.css</code> / <code>_base.css</code> / <code>_chrome.css</code> 三层与 <code>_nav.html</code> 统一侧栏。每张卡片的缩略图为 live iframe——原型改动即时反映,无需重新截图。</p>
|
||||
|
||||
<!-- 推荐阅读路径:首次访客的引导线 -->
|
||||
<nav class="reading-path" aria-label="推荐阅读顺序">
|
||||
<span class="path-label">推荐阅读</span>
|
||||
<a class="path-step" href="copper-mirror-redesign-v2.html">
|
||||
<span class="step-num">壹</span>
|
||||
<span class="step-body">
|
||||
<span class="step-title">铜镜鉴心 North Star</span>
|
||||
<span class="step-desc">设计 DNA · 色谱 / 字阶 / 组件</span>
|
||||
</span>
|
||||
</a>
|
||||
<span class="path-arrow" aria-hidden="true">→</span>
|
||||
<a class="path-step" href="cosmos-preview.html">
|
||||
<span class="step-num">贰</span>
|
||||
<span class="step-body">
|
||||
<span class="step-title">灵枢</span>
|
||||
<span class="step-desc">签名元素 · 九枢星盘</span>
|
||||
</span>
|
||||
</a>
|
||||
<span class="path-arrow" aria-hidden="true">→</span>
|
||||
<a class="path-step" href="admin-preview.html">
|
||||
<span class="step-num">叁</span>
|
||||
<span class="step-body">
|
||||
<span class="step-title">司命台</span>
|
||||
<span class="step-desc">控制台 dashboard</span>
|
||||
</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="hero-bar">
|
||||
<div class="stats">
|
||||
<div class="stat"><b id="stat-total">—</b><span>原型</span></div>
|
||||
<div class="stat"><b id="stat-done">—</b><span>已落地</span></div>
|
||||
<div class="stat"><b id="stat-planned">—</b><span>待实现</span></div>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<a class="btn btn-outline btn-sm" href="../README.md">README</a>
|
||||
<button class="btn btn-outline btn-sm" id="theme-toggle" type="button"><span id="theme-label">夜铜</span></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ════════ 共享层 ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head">
|
||||
<span class="group-title">共享层</span>
|
||||
<span class="group-sub">— 改一处,全站生效</span>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<a class="proto file" href="tokens.css">
|
||||
<div class="proto-head"><span class="proto-title">tokens.css</span><span class="tag tag-gilt">变量</span></div>
|
||||
<div class="proto-desc">铜镜鉴心暗+亮全套 CSS 变量,设计 token 单一事实源</div>
|
||||
</a>
|
||||
<a class="proto file" href="_base.css">
|
||||
<div class="proto-head"><span class="proto-title">_base.css</span><span class="tag tag-gilt">基底</span></div>
|
||||
<div class="proto-desc">reset / body / 极光铜晕 / 字体类 / reduced-motion</div>
|
||||
</a>
|
||||
<a class="proto file" href="_chrome.css">
|
||||
<div class="proto-head"><span class="proto-title">_chrome.css</span><span class="tag tag-gilt">组件</span></div>
|
||||
<div class="proto-desc">sidebar / 导航 / 卡片 / 按钮 / 标签 / 页头共享样式</div>
|
||||
</a>
|
||||
<a class="proto file" href="_nav.html">
|
||||
<div class="proto-head"><span class="proto-title">nav.html</span><span class="tag tag-gilt">片段</span></div>
|
||||
<div class="proto-desc">统一 7 组侧栏导航,由 _theme.js fetch 注入各页</div>
|
||||
</a>
|
||||
<a class="proto file" href="_theme.js">
|
||||
<div class="proto-head"><span class="proto-title">_theme.js</span><span class="tag tag-gilt">脚本</span></div>
|
||||
<div class="proto-desc">nav 注入 + 当前页高亮 + 主题切换 + localStorage</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 设计系统 · 北极星 ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head">
|
||||
<span class="group-title">设计系统 · 北极星</span>
|
||||
<span class="group-sub">— 色谱 / 字阶 / 组件 / 签名元素</span>
|
||||
</div>
|
||||
<div class="grid-thumb">
|
||||
<a class="proto" href="copper-mirror-redesign-v2.html">
|
||||
<div class="thumb"><iframe src="copper-mirror-redesign-v2.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="铜镜鉴心 North Star 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">铜镜鉴心 North Star</span><span class="status status-bench">基准</span></div>
|
||||
<div class="proto-route none">设计系统页</div>
|
||||
<div class="proto-desc">色谱 / 字阶 / 组件库 / 九枢星盘签名元素</div>
|
||||
</a>
|
||||
<a class="proto" href="copper-mirror-login.html">
|
||||
<div class="thumb"><iframe src="copper-mirror-login.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="登入灵台 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">登入灵台</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/login</div>
|
||||
<div class="proto-desc">铜镜鉴心登录页</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 灵台 · 对话 ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head"><span class="group-title">灵台 · 对话</span></div>
|
||||
<div class="grid-thumb">
|
||||
<a class="proto" href="chat-redesign-preview.html">
|
||||
<div class="thumb"><iframe src="chat-redesign-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="对话 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">对话</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/chat</div>
|
||||
<div class="proto-desc">app-shell + 可折叠侧栏 + 镜面 v6</div>
|
||||
</a>
|
||||
<a class="proto" href="run-trace-preview.html">
|
||||
<div class="thumb"><iframe src="run-trace-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="运行轨迹 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">运行轨迹</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/runs/[id]</div>
|
||||
<div class="proto-desc">运行链路追踪与回放</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 魂册 ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head"><span class="group-title">魂册</span><span class="group-sub">— 人物的载体与档案</span></div>
|
||||
<div class="grid-thumb">
|
||||
<a class="proto" href="characters-preview.html">
|
||||
<div class="thumb"><iframe src="characters-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="人物列表 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">人物列表</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/characters</div>
|
||||
<div class="proto-desc">魂册人物总览</div>
|
||||
</a>
|
||||
<a class="proto" href="character-detail-preview.html">
|
||||
<div class="thumb"><iframe src="character-detail-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="人物详情 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">人物详情</span><span class="status status-maybe">部分</span></div>
|
||||
<div class="proto-route">/characters/[id]</div>
|
||||
<div class="proto-desc">单人物档案与状态</div>
|
||||
</a>
|
||||
<a class="proto" href="character-materials-preview.html">
|
||||
<div class="thumb"><iframe src="character-materials-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="人物资料 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">人物资料</span><span class="status status-maybe">部分</span></div>
|
||||
<div class="proto-route">/characters/[id]/*</div>
|
||||
<div class="proto-desc">资料沉淀与衍生</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 心智 ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head"><span class="group-title">心智</span><span class="group-sub">— 人物的内世界模型</span></div>
|
||||
<div class="grid-thumb">
|
||||
<a class="proto" href="cosmos-preview.html">
|
||||
<div class="thumb"><iframe src="cosmos-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="灵枢 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">灵枢</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/cosmos</div>
|
||||
<div class="proto-desc">九枢系统视图 · 太极阴阳平衡心智解剖</div>
|
||||
</a>
|
||||
<a class="proto" href="wuxing-preview.html">
|
||||
<div class="thumb"><iframe src="wuxing-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="五行 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">五行</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/wuxing</div>
|
||||
<div class="proto-desc">五行生克可视化</div>
|
||||
</a>
|
||||
<a class="proto" href="personality-preview.html">
|
||||
<div class="thumb"><iframe src="personality-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="人格 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">人格</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/personality</div>
|
||||
<div class="proto-desc">人格定义与维度</div>
|
||||
</a>
|
||||
<a class="proto" href="drive-preview.html">
|
||||
<div class="thumb"><iframe src="drive-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="内驱力 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">内驱力</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/drive</div>
|
||||
<div class="proto-desc">动机与目标驱动</div>
|
||||
</a>
|
||||
<a class="proto" href="evolution-preview.html">
|
||||
<div class="thumb"><iframe src="evolution-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="演化 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">演化</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/evolution</div>
|
||||
<div class="proto-desc">大周天人格演化轨迹</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 编排 ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head"><span class="group-title">编排</span><span class="group-sub">— 角色卡 · 世界设定 · 符文 · 眉批</span></div>
|
||||
<div class="grid-thumb">
|
||||
<a class="proto" href="card-import-preview.html">
|
||||
<div class="thumb"><iframe src="card-import-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="魂册降生 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">魂册降生 · 角色卡</span><span class="status status-todo">待实现</span></div>
|
||||
<div class="proto-route">/characters/import</div>
|
||||
<div class="proto-desc">V1/V2/V3 角色卡导入建立人物 · 静态快照导出</div>
|
||||
</a>
|
||||
<a class="proto" href="lorebook-preview.html">
|
||||
<div class="thumb"><iframe src="lorebook-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="世谱 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">世谱 · Lorebook</span><span class="status status-todo">待实现</span></div>
|
||||
<div class="proto-route">/lorebooks</div>
|
||||
<div class="proto-desc">关键词触发的世界设定按需注入</div>
|
||||
</a>
|
||||
<a class="proto" href="rune-engine-preview.html">
|
||||
<div class="thumb"><iframe src="rune-engine-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="符文引擎 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">符文引擎 · Macros</span><span class="status status-todo">待实现</span></div>
|
||||
<div class="proto-route">/macros</div>
|
||||
<div class="proto-desc">组装管线末端 {{char}} 占位符替换引擎</div>
|
||||
</a>
|
||||
<a class="proto" href="margin-notes-preview.html">
|
||||
<div class="thumb"><iframe src="margin-notes-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="眉批 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">眉批 · Author's Note</span><span class="status status-todo">待实现</span></div>
|
||||
<div class="proto-route">/author-notes</div>
|
||||
<div class="proto-desc">作为 context_transformer 按深度注入对话流</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 内景 ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head"><span class="group-title">内景</span><span class="group-sub">— 外部世界与时序</span></div>
|
||||
<div class="grid-thumb">
|
||||
<a class="proto" href="world-preview.html">
|
||||
<div class="thumb"><iframe src="world-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="世界 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">世界 · 河图绘形</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/world</div>
|
||||
<div class="proto-desc">世界实体图谱</div>
|
||||
</a>
|
||||
<a class="proto" href="time-preview.html">
|
||||
<div class="thumb"><iframe src="time-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="时态 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">时态</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/time</div>
|
||||
<div class="proto-desc">时间线与叙事时序</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 藏识 ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head"><span class="group-title">藏识</span></div>
|
||||
<div class="grid-thumb">
|
||||
<a class="proto" href="memory-preview.html">
|
||||
<div class="thumb"><iframe src="memory-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="记忆 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">记忆</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/memory</div>
|
||||
<div class="proto-desc">息壤记忆管理</div>
|
||||
</a>
|
||||
<a class="proto" href="archive-preview.html">
|
||||
<div class="thumb"><iframe src="archive-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="归档 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">归档 · 记忆时间河</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/archive</div>
|
||||
<div class="proto-desc">时间河回溯</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 百工 · 系统 ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head"><span class="group-title">百工 · 系统</span></div>
|
||||
<div class="grid-thumb">
|
||||
<a class="proto" href="tools-preview.html">
|
||||
<div class="thumb"><iframe src="tools-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="工具 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">工具</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/tools</div>
|
||||
<div class="proto-desc">工具调用管理</div>
|
||||
</a>
|
||||
<a class="proto" href="model-gateway-preview.html">
|
||||
<div class="thumb"><iframe src="model-gateway-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="模型网关 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">模型网关</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/models</div>
|
||||
<div class="proto-desc">多模型路由与调度</div>
|
||||
</a>
|
||||
<a class="proto" href="settings-preview.html">
|
||||
<div class="thumb"><iframe src="settings-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="设置 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">设置</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/settings</div>
|
||||
<div class="proto-desc">全局配置</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 司命台 · admin ════════ -->
|
||||
<section class="group">
|
||||
<div class="group-head"><span class="group-title">司命台 · admin</span></div>
|
||||
<div class="grid-thumb">
|
||||
<a class="proto" href="admin-preview.html">
|
||||
<div class="thumb"><iframe src="admin-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="司命台 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">司命台融合稿</span><span class="status status-ok">已落地</span></div>
|
||||
<div class="proto-route">/admin</div>
|
||||
<div class="proto-desc">控制台 dashboard 网格</div>
|
||||
</a>
|
||||
<a class="proto" href="admin-characters-preview.html">
|
||||
<div class="thumb"><iframe src="admin-characters-preview.html" loading="lazy" scrolling="no" tabindex="-1" aria-hidden="true" title="人物模板管理 预览"></iframe></div>
|
||||
<div class="proto-head"><span class="proto-title">人物模板管理</span><span class="status status-maybe">部分</span></div>
|
||||
<div class="proto-route">/admin/*</div>
|
||||
<div class="proto-desc">人物模板配置</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════ 预览提示 ════════ -->
|
||||
<div class="preview-hint">
|
||||
<div class="t">如何预览</div>
|
||||
<p>原型侧栏由 <code>_theme.js</code> 通过 <code>fetch</code> 注入,<strong>需启动本地服务器</strong>(<code>file://</code> 双击打开时侧栏与缩略图均会失效)。在 <code>frontend/</code> 下运行 <code>pnpm design</code>,然后访问 <code>http://localhost:3001</code>。卡片缩略图为 live iframe,原型页改动后刷新本页即同步。</p>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
<script src="_theme.js" defer></script>
|
||||
<script>
|
||||
/* 自动统计原型数量(新增页面无需手改 hero 数字) */
|
||||
(function () {
|
||||
var protos = document.querySelectorAll(".proto:not(.file)");
|
||||
var done = document.querySelectorAll(".proto:not(.file) .status-ok").length;
|
||||
var planned = document.querySelectorAll(".proto:not(.file) .status-maybe, .proto:not(.file) .status-todo").length;
|
||||
var el;
|
||||
if ((el = document.getElementById("stat-total"))) el.textContent = protos.length;
|
||||
if ((el = document.getElementById("stat-done"))) el.textContent = done;
|
||||
if ((el = document.getElementById("stat-planned"))) el.textContent = planned;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,316 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 世谱管理页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 世谱(Lorebook / World Info)管理页
|
||||
* 功能融合:关键词触发的世界设定注入(融合设计 §7)
|
||||
* ============================================================ */
|
||||
|
||||
.app-main { flex: 1; padding: 24px 28px 56px; max-width: 920px; width: 100%; margin: 0 auto; }
|
||||
/* ── 卡片 ── */
|
||||
|
||||
.card-title .glyph { color: var(--patina); opacity: 0.8; }
|
||||
|
||||
/* ── 按钮 ── */
|
||||
|
||||
|
||||
/* ── 书切换 tabs ── */
|
||||
.book-tabs { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 18px; }
|
||||
.book-tab { display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border-radius: 999px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg-dim); font-size: 12.5px; cursor: pointer; transition: all 0.12s; }
|
||||
.book-tab:hover { color: var(--fg); border-color: var(--patina-dim); }
|
||||
.book-tab.active { background: var(--patina-wash); color: var(--patina); border-color: var(--patina-dim); }
|
||||
.book-tab .count { font-family: "JetBrains Mono", monospace; font-size: 10.5px; opacity: 0.8; }
|
||||
.book-tab.add { border-style: dashed; color: var(--fg-faint); }
|
||||
.book-tab.add:hover { color: var(--patina); }
|
||||
|
||||
/* ── 表单 ── */
|
||||
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.field-label { display: block; font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.04em; color: var(--muted); margin-bottom: 4px; }
|
||||
.field input, .field textarea, .field select { width: 100%; padding: 8px 12px; border: 1px solid var(--rule); border-radius: 10px; background: var(--glass); color: var(--fg); font-family: inherit; font-size: 13px; transition: border-color 0.15s; }
|
||||
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--patina-dim); }
|
||||
.field textarea { min-height: 80px; resize: vertical; }
|
||||
.field-full { grid-column: 1 / -1; }
|
||||
|
||||
/* ── 条目 ── */
|
||||
|
||||
.section-label .count { font-size: 11px; color: var(--fg-dim); font-weight: 400; }
|
||||
.entry-item { border: 1px solid var(--rule); border-radius: 12px; background: var(--surface); padding: 14px 16px; margin-bottom: 10px; }
|
||||
.entry-item.constant { border-left: 3px solid var(--gilt); }
|
||||
.entry-item.disabled { opacity: 0.5; }
|
||||
.entry-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
||||
.entry-body { flex: 1; min-width: 0; }
|
||||
.entry-keys { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 7px; }
|
||||
.key-chip { display: inline-flex; align-items: center; padding: 2px 9px; border-radius: 6px; font-family: "JetBrains Mono", monospace; font-size: 11px; background: var(--patina-wash); color: var(--patina); border: 1px solid var(--patina-dim); }
|
||||
.key-chip.secondary { background: var(--gilt-faint); color: var(--gilt); border-color: var(--gilt-dim); }
|
||||
.entry-content { font-size: 13px; color: var(--fg); margin: 4px 0 8px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.entry-meta { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); display: flex; gap: 12px; flex-wrap: wrap; }
|
||||
.entry-meta span { display: inline-flex; align-items: center; gap: 3px; }
|
||||
.entry-actions { display: flex; gap: 4px; flex-shrink: 0; align-items: center; }
|
||||
|
||||
/* ── 开关 ── */
|
||||
.switch { position: relative; width: 32px; height: 18px; border-radius: 999px; background: var(--rule); cursor: pointer; transition: background 0.15s; flex-shrink: 0; }
|
||||
.switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--fg-dim); transition: transform 0.15s, background 0.15s; }
|
||||
.switch.on { background: var(--patina-dim); }
|
||||
.switch.on::after { transform: translateX(14px); background: var(--patina); }
|
||||
|
||||
/* ── Tag ── */
|
||||
|
||||
.tag-constant { border-color: var(--gilt-dim); color: var(--gilt); background: var(--gilt-faint); }
|
||||
.tag-selective { border-color: var(--cinnabar-dim); color: var(--cinnabar); background: var(--cinnabar-wash); }
|
||||
.tag-global { border-color: var(--patina-dim); color: var(--patina); background: var(--patina-wash); }
|
||||
|
||||
/* ── 高级选项折叠 ── */
|
||||
.advanced-toggle { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--muted); cursor: pointer; display: inline-flex; align-items: center; gap: 4px; margin-top: 8px; }
|
||||
.advanced-toggle:hover { color: var(--patina); }
|
||||
.advanced-body { margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--rule); }
|
||||
|
||||
/* ── 说明卡 ── */
|
||||
.note { font-size: 12.5px; color: var(--fg-dim); line-height: 1.7; }
|
||||
.note code { font-family: "JetBrains Mono", monospace; font-size: 11.5px; background: var(--surface-hi); padding: 1px 6px; border-radius: 4px; color: var(--patina); }
|
||||
.note strong { color: var(--fg); font-weight: 500; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app-main { padding: 16px 14px 40px; }
|
||||
.form-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<div class="sidebar-mount" data-active="世谱"></div>
|
||||
<div class="main-area">
|
||||
<main class="app-main">
|
||||
|
||||
<!-- ═══ 页头 ═══ -->
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1 class="page-title">世谱 · Lorebook</h1>
|
||||
<p class="page-sub">「时崎狂三」的世界设定谱——对话命中关键词时自动注入,<code style="font-family:JetBrains Mono,monospace;font-size:12px;color:var(--patina);">before_char</code> / <code style="font-family:JetBrains Mono,monospace;font-size:12px;color:var(--patina);">after_char</code> 定位于人格前后。</p>
|
||||
</div>
|
||||
<button class="btn btn-primary">+ 新建世谱</button>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 世谱书切换 ═══ -->
|
||||
<div class="book-tabs">
|
||||
<span class="book-tab active">狂三世谱 <span class="count">5</span></span>
|
||||
<span class="book-tab">黑猫与日常 <span class="count">3</span></span>
|
||||
<span class="book-tab">通用世界观 <span class="count">8 · global</span></span>
|
||||
<span class="book-tab add">+ 新建</span>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 世谱书设置 ═══ -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">📖</span> 狂三世谱</h2>
|
||||
<span class="tag tag-global">character · 5 条</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-grid">
|
||||
<div class="field field-full">
|
||||
<label class="field-label">书名</label>
|
||||
<input type="text" value="狂三世谱" />
|
||||
</div>
|
||||
<div class="field field-full">
|
||||
<label class="field-label">描述(可选)</label>
|
||||
<input type="text" value="时崎狂三的能力、关系、背景设定,按对话关键词触发。" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">扫描深度 scan_depth</label>
|
||||
<input type="number" value="8" placeholder="扫描最近 N 条对话" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">token 预算</label>
|
||||
<input type="number" value="800" placeholder="注入上限" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-top:12px;">
|
||||
<span class="switch on" id="recursive"></span>
|
||||
<span style="font-size:12.5px;color:var(--fg-dim);">递归扫描 <span style="color:var(--fg-faint);font-size:11.5px;">— 已命中条目的内容可触发其他条目</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 条目列表 ═══ -->
|
||||
<div class="section-label">
|
||||
◇ 条目
|
||||
<span class="count">(5 条 · 4 启用)</span>
|
||||
<div style="flex:1;"></div>
|
||||
<button class="btn btn-outline btn-sm">+ 新建条目</button>
|
||||
</div>
|
||||
|
||||
<!-- 条目 1:常量(恒定注入) -->
|
||||
<div class="entry-item constant">
|
||||
<div class="entry-head">
|
||||
<div class="entry-body">
|
||||
<div class="entry-keys">
|
||||
<span class="key-chip">刻刻帝</span>
|
||||
<span class="key-chip">Zafkiel</span>
|
||||
<span class="key-chip">天使</span>
|
||||
<span class="tag tag-constant">constant · 恒定注入</span>
|
||||
</div>
|
||||
<div class="entry-content">狂三的天使「刻刻帝」外观为巨大古钟,时钟指针化为长短双枪。拥有十二种时间能力,每种消耗不同的"时间"代价。</div>
|
||||
<div class="entry-meta">
|
||||
<span>位置 after_char</span>
|
||||
<span>顺序 10</span>
|
||||
<span>优先级 5</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry-actions">
|
||||
<span class="switch on"></span>
|
||||
<button class="btn btn-ghost btn-sm">编辑</button>
|
||||
<button class="btn btn-ghost btn-sm">删</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 条目 2:selective(双键命中) -->
|
||||
<div class="entry-item">
|
||||
<div class="entry-head">
|
||||
<div class="entry-body">
|
||||
<div class="entry-keys">
|
||||
<span class="key-chip">猫</span>
|
||||
<span style="color:var(--fg-faint);font-size:11px;">+</span>
|
||||
<span class="key-chip secondary">黑</span>
|
||||
<span class="tag tag-selective">selective · 双键命中</span>
|
||||
</div>
|
||||
<div class="entry-content">狂三格外钟爱黑猫,常以「小可」称呼她在影子中豢养的黑猫分身,会为它留下红茶第三泡。</div>
|
||||
<div class="entry-meta">
|
||||
<span>位置 after_char</span>
|
||||
<span>顺序 20</span>
|
||||
<span>优先级 10</span>
|
||||
<span>区分大小写 ✕</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry-actions">
|
||||
<span class="switch on"></span>
|
||||
<button class="btn btn-ghost btn-sm">编辑</button>
|
||||
<button class="btn btn-ghost btn-sm">删</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 条目 3:正则匹配 -->
|
||||
<div class="entry-item">
|
||||
<div class="entry-head">
|
||||
<div class="entry-body">
|
||||
<div class="entry-keys">
|
||||
<span class="key-chip" style="font-family:'JetBrains Mono',monospace;">/\b(四之弹|七之弹|十二之弹)\b/</span>
|
||||
<span class="tag" style="border-color:var(--cinnabar-dim);color:var(--cinnabar);background:var(--cinnabar-wash);">use_regex · 正则</span>
|
||||
</div>
|
||||
<div class="entry-content">十二之弹对应十二种时间能力:一之弹加速自身、四之弹回溯目标、七之弹停止时间、十二之弹时间旅行……每次使用消耗自身寿命。</div>
|
||||
<div class="entry-meta">
|
||||
<span>位置 before_char</span>
|
||||
<span>顺序 30</span>
|
||||
<span>优先级 10</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry-actions">
|
||||
<span class="switch on"></span>
|
||||
<button class="btn btn-ghost btn-sm">编辑</button>
|
||||
<button class="btn btn-ghost btn-sm">删</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 条目 4:已禁用 -->
|
||||
<div class="entry-item disabled">
|
||||
<div class="entry-head">
|
||||
<div class="entry-body">
|
||||
<div class="entry-keys">
|
||||
<span class="key-chip">士道</span>
|
||||
</div>
|
||||
<div class="entry-content">与五河士道的关系:曾多次交手与合作,为他使用六之弹回到过去,经历 204 次时间回溯。</div>
|
||||
<div class="entry-meta">
|
||||
<span style="color:var(--cinnabar);">● 已禁用</span>
|
||||
<span>位置 after_char</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry-actions">
|
||||
<span class="switch"></span>
|
||||
<button class="btn btn-ghost btn-sm">编辑</button>
|
||||
<button class="btn btn-ghost btn-sm">删</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 条目编辑表单(展开态示例) ═══ -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title"><span class="glyph">✎</span> 编辑条目</h2>
|
||||
<button class="btn btn-ghost btn-sm">取消</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-grid">
|
||||
<div class="field field-full">
|
||||
<label class="field-label">主关键词 keys(逗号分隔,命中任一即触发)</label>
|
||||
<input type="text" value="刻刻帝, Zafkiel, 天使" />
|
||||
</div>
|
||||
<div class="field field-full">
|
||||
<label class="field-label">注入内容 content *</label>
|
||||
<textarea>狂三的天使「刻刻帝」外观为巨大古钟……</textarea>
|
||||
</div>
|
||||
<div class="field field-full">
|
||||
<label class="field-label">次关键词 secondary_keys(selective 时需同时命中)</label>
|
||||
<input type="text" placeholder="可选" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">注入位置</label>
|
||||
<select>
|
||||
<option>after_char · 人格之后</option>
|
||||
<option>before_char · 人格之前</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">插入顺序 / 优先级</label>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<input type="number" value="10" placeholder="顺序" style="flex:1;" />
|
||||
<input type="number" value="5" placeholder="优先级" style="flex:1;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="advanced-toggle" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display==='none' ? 'block' : 'none';">▸ 高级选项</div>
|
||||
<div class="advanced-body" style="display:none;">
|
||||
<div style="display:flex;flex-wrap:wrap;gap:18px;">
|
||||
<label style="display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--fg-dim);cursor:pointer;"><span class="switch on"></span> 启用</label>
|
||||
<label style="display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--fg-dim);cursor:pointer;"><span class="switch"></span> constant(恒定注入,无视关键词)</label>
|
||||
<label style="display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--fg-dim);cursor:pointer;"><span class="switch"></span> selective(需双键命中)</label>
|
||||
<label style="display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--fg-dim);cursor:pointer;"><span class="switch"></span> 区分大小写</label>
|
||||
<label style="display:flex;align-items:center;gap:6px;font-size:12.5px;color:var(--fg-dim);cursor:pointer;"><span class="switch"></span> use_regex(关键词按正则)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:16px;">
|
||||
<button class="btn btn-outline">取消</button>
|
||||
<button class="btn btn-primary">保存条目</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 说明 ═══ -->
|
||||
<div class="card">
|
||||
<div class="card-body note">
|
||||
<strong>世谱是什么。</strong>源自业界通用的 <code>World Info / Lorebook</code>——创作者预设的设定条目,对话命中 <code>keys</code> 时把 <code>content</code> 注入系统提示词,既丰富世界观又节省 token(按需注入而非全量塞入)。
|
||||
<br/><br/>
|
||||
<strong>与魂镜既有载体的区别。</strong>息壤记忆(自动抽取、会演化)、河图世界实体(结构化图谱)之外,世谱是<strong>手动预设的触发式设定</strong>,三者正交。魂镜增量:条目可 <code>bound_entity_id</code> 绑定河图实体,实现「关键词 + 实体」双引擎触发。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// 开关交互(设计稿示意)
|
||||
document.querySelectorAll('.switch').forEach(s => s.addEventListener('click', () => s.classList.toggle('on')));
|
||||
</script>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,312 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 眉批设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 眉批(Author's Note · 按 depth 注入对话流)
|
||||
* 功能融合 §9.1:作为 context_transformer,按深度 +
|
||||
* 角色把批注插入对话流。签名——书眉批注:批注夹在对话消息间,
|
||||
* 左侧 depth 标尺定位,铜绿色夹批。
|
||||
*
|
||||
* 完整功能:
|
||||
* 按角色筛选(system / user / assistant)
|
||||
* 按人物 scope 过滤
|
||||
* 行内编辑 · 深度调节 · 批量启停
|
||||
* 模板速建 · 注入预览(含多批同 depth 层叠)
|
||||
* ============================================================ */
|
||||
|
||||
.page-header .spacer { flex: 1; }
|
||||
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.85; font-family: "Songti SC", serif; font-size: 14px; }
|
||||
.section-label .sub { color: var(--fg-dim); opacity: 0.7; letter-spacing: 0.04em; }
|
||||
|
||||
/* ── 筛选条 ── */
|
||||
.filter-bar { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-bottom: 14px; }
|
||||
.filter-chip { font-size: 12px; padding: 5px 12px; border-radius: 999px; border: 1px solid var(--rule); color: var(--fg-dim); cursor: pointer; background: var(--surface); transition: all var(--duration-fast); font-family: inherit; }
|
||||
.filter-chip:hover { color: var(--fg); border-color: var(--patina-dim); }
|
||||
.filter-chip.active { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.filter-chip .count { font-family: "JetBrains Mono", monospace; font-size: 10px; opacity: 0.7; margin-left: 4px; }
|
||||
|
||||
/* ── 人物 scope 选择 ── */
|
||||
.scope-select { display: flex; align-items: center; gap: 8px; margin-left: auto; }
|
||||
.scope-select .lb { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.char-pill-scope { display: inline-flex; align-items: center; gap: 5px; padding: 4px 11px; border-radius: 999px; border: 1px solid var(--patina-dim); background: var(--patina-wash); color: var(--patina); font-size: 12px; cursor: pointer; }
|
||||
.char-pill-scope.all { border-color: var(--rule); background: var(--surface); color: var(--fg-dim); }
|
||||
.char-pill-scope .avatar-mini { width: 16px; height: 16px; border-radius: 50%; background: var(--patina); color: var(--ink); display: inline-flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 9px; font-weight: 700; }
|
||||
|
||||
/* ═══ 眉批卡(列表)═══ */
|
||||
.note-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
.note-card { display: grid; grid-template-columns: 1fr 52px auto auto; gap: 12px; align-items: center; border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); padding: 13px 16px; transition: border-color var(--duration-fast); }
|
||||
.note-card:hover { border-color: var(--patina-dim); }
|
||||
.note-card.off { opacity: 0.5; }
|
||||
.note-card.off .note-text { text-decoration: line-through; text-decoration-color: var(--fg-faint); }
|
||||
.note-text { font-family: "Songti SC", serif; font-size: 14.5px; color: var(--fg); line-height: 1.5; cursor: text; }
|
||||
.note-text:focus { outline: none; border-bottom: 1px dashed var(--patina-dim); }
|
||||
.note-text .role-tag { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--gilt); border: 1px solid var(--gilt-dim); border-radius: 3px; padding: 1px 5px; margin-right: 7px; vertical-align: 2px; }
|
||||
.note-text .role-tag.user { color: var(--cinnabar); border-color: var(--cinnabar-dim); }
|
||||
.note-text .role-tag.assistant { color: var(--patina); border-color: var(--patina-dim); }
|
||||
.note-text .scope-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; vertical-align: 1px; }
|
||||
.note-text .scope-dot.global { background: var(--gilt); }
|
||||
.note-text .scope-dot.char { background: var(--patina); }
|
||||
.depth-chip { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--patina); border: 1px solid var(--patina-dim); border-radius: 5px; padding: 4px 9px; background: var(--patina-wash); white-space: nowrap; cursor: pointer; }
|
||||
.role-select { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-dim); background: var(--glass); border: 1px solid var(--rule); border-radius: 6px; padding: 4px 8px; cursor: pointer; }
|
||||
.depth-input { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-dim); background: var(--glass); border: 1px solid var(--rule); border-radius: 6px; padding: 4px 8px; width: 48px; text-align: center; }
|
||||
.depth-input:focus, .role-select:focus { outline: none; border-color: var(--patina-dim); }
|
||||
.toggle { width: 34px; height: 19px; border-radius: 999px; background: var(--rule); position: relative; cursor: pointer; transition: background var(--duration-fast); flex-shrink: 0; }
|
||||
.toggle::after { content: ""; position: absolute; top: 2px; left: 2px; width: 15px; height: 15px; border-radius: 50%; background: var(--fg-faint); transition: transform var(--duration-fast), background var(--duration-fast); }
|
||||
.toggle.on { background: var(--patina-dim); }
|
||||
.toggle.on::after { transform: translateX(15px); background: var(--patina); }
|
||||
.add-note { border: 1px dashed var(--rule); border-radius: 10px; padding: 13px 16px; color: var(--fg-faint); font-size: 13px; cursor: pointer; text-align: center; transition: all var(--duration-fast); }
|
||||
.add-note:hover { border-color: var(--patina-dim); color: var(--patina); }
|
||||
|
||||
/* ── 批量操作栏 ── */
|
||||
.batch-bar { display: flex; gap: 8px; align-items: center; padding: 8px 0; }
|
||||
.batch-bar .hint { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); margin-right: auto; }
|
||||
|
||||
/* ── 模板速建 ── */
|
||||
.templates { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.tpl-chip { font-size: 11.5px; padding: 6px 12px; border-radius: 999px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg-dim); cursor: pointer; transition: all var(--duration-fast); white-space: nowrap; }
|
||||
.tpl-chip:hover { border-color: var(--patina-dim); color: var(--fg); background: var(--patina-wash); }
|
||||
.tpl-chip .glyph { margin-right: 4px; }
|
||||
|
||||
/* ═══ depth 注入预览(签名:书眉批注,depth 标尺 + 夹批)═══ */
|
||||
.depth-view { border: 1px solid var(--gilt-dim); border-radius: 10px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); overflow: hidden; }
|
||||
.depth-view-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--rule); }
|
||||
.depth-view-head .t { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); }
|
||||
.depth-view-head .hint { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.stream { padding: 16px 16px 20px; display: flex; flex-direction: column; gap: 6px; position: relative; }
|
||||
.stream-row { display: grid; grid-template-columns: 40px 1fr; gap: 12px; align-items: stretch; }
|
||||
.depth-mark { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); text-align: right; padding-top: 7px; letter-spacing: 0.04em; }
|
||||
.msg { padding: 8px 13px; border-radius: 8px; font-size: 13px; line-height: 1.55; }
|
||||
.msg .who { font-family: "JetBrains Mono", monospace; font-size: 9.5px; letter-spacing: 0.12em; text-transform: uppercase; margin-bottom: 3px; }
|
||||
.msg.ai { background: var(--surface-hi); color: var(--fg); border: 1px solid var(--rule); }
|
||||
.msg.ai .who { color: var(--patina); }
|
||||
.msg.you { background: transparent; color: var(--fg-dim); border-right: 2px solid var(--gilt); padding-right: 11px; text-align: right; }
|
||||
.msg.you .who { color: var(--gilt); }
|
||||
/* 眉批夹批:从左侧 depth 标尺伸入,铜绿色,像书眉批注 */
|
||||
.stream-row.note { grid-template-columns: 40px 1fr; }
|
||||
.stream-row.note .depth-mark { color: var(--patina); padding-top: 5px; }
|
||||
.stream-row.note .note-inline { background: var(--patina-wash); border: 1px solid var(--patina-dim); border-left: 3px solid var(--patina); border-radius: 0 6px 6px 0; padding: 7px 13px; font-family: "Songti SC", serif; font-size: 13px; color: var(--patina); display: flex; gap: 9px; align-items: baseline; }
|
||||
.stream-row.note .note-inline .seal { font-family: "JetBrains Mono", monospace; font-size: 9px; letter-spacing: 0.1em; color: var(--patina); border: 1px solid var(--patina-dim); border-radius: 3px; padding: 1px 5px; flex-shrink: 0; }
|
||||
.stream-row.note .note-inline .txt { color: var(--fg); }
|
||||
|
||||
.legend { display: flex; gap: 16px; padding: 10px 16px; border-top: 1px solid var(--rule); font-size: 11.5px; color: var(--muted); flex-wrap: wrap; }
|
||||
.legend .item { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.legend .sw { width: 11px; height: 11px; border-radius: 3px; }
|
||||
|
||||
.pipeline-note { font-size: 12.5px; color: var(--fg-dim); line-height: 1.6; }
|
||||
.pipeline-note .em { color: var(--fg); }
|
||||
.pipeline-note code { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--gilt); }
|
||||
|
||||
@media (max-width: 900px) { .note-card { grid-template-columns: 1fr; gap: 8px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<div class="sidebar-mount" data-active="眉批"></div>
|
||||
|
||||
<!-- ══ main ══ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="page-title">眉批</div>
|
||||
<div class="page-sub">Author's Note · 按 depth 注入对话流</div>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<span class="stat-pill">3 条生效 · 1 条停用</span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 筛选 + 作用域 -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">批</span>眉批清单 <span class="sub">— 每条按 depth + role + scope 插入对话流</span></div>
|
||||
|
||||
<div class="filter-bar">
|
||||
<span class="filter-chip active">全部 <span class="count">4</span></span>
|
||||
<span class="filter-chip">system <span class="count">3</span></span>
|
||||
<span class="filter-chip">user <span class="count">1</span></span>
|
||||
<span class="filter-chip">assistant <span class="count">0</span></span>
|
||||
<span class="filter-chip" style="opacity:0.6;">生效中 <span class="count">3</span></span>
|
||||
<span class="filter-chip" style="opacity:0.6;">已停用 <span class="count">1</span></span>
|
||||
|
||||
<span class="scope-select">
|
||||
<span class="lb">作用域</span>
|
||||
<span class="char-pill-scope all">全人物</span>
|
||||
<span class="char-pill-scope"><span class="avatar-mini">狂</span>狂三</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 批量操作 -->
|
||||
<div class="batch-bar">
|
||||
<span class="hint">选中 0 条 · 批量操作</span>
|
||||
<button class="btn btn-outline btn-sm">全部启用</button>
|
||||
<button class="btn btn-outline btn-sm">全部停用</button>
|
||||
<button class="btn btn-outline btn-sm" style="color:var(--cinnabar);border-color:var(--cinnabar-dim);">删除选中</button>
|
||||
</div>
|
||||
|
||||
<!-- 模板速建 -->
|
||||
<div style="margin-bottom:14px;">
|
||||
<span style="font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--fg-faint);letter-spacing:0.08em;display:block;margin-bottom:6px;">速建模板</span>
|
||||
<div class="templates">
|
||||
<span class="tpl-chip" onclick="addNote()"><span class="glyph">+</span> 空白眉批</span>
|
||||
<span class="tpl-chip"><span class="glyph">📏</span> 保持简洁,每轮消息不超过三句。</span>
|
||||
<span class="tpl-chip"><span class="glyph">🎭</span> 用宋体的节奏,留白比填满好。</span>
|
||||
<span class="tpl-chip"><span class="glyph">🪞</span> 本节气氛沉静,少用感叹与反问。</span>
|
||||
<span class="tpl-chip"><span class="glyph">⚡</span> 用户赶工中 · 语气再软一点 · 不问进度</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 眉批列表 -->
|
||||
<div class="note-list" id="noteList"></div>
|
||||
</section>
|
||||
|
||||
<!-- depth 注入预览(签名)-->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">注</span>注入预览 <span class="sub">— depth = 距对话末尾第 N 条之前;调 depth 看夹批位置</span></div>
|
||||
<div class="depth-view">
|
||||
<div class="depth-view-head">
|
||||
<span class="t">对话流 · 书眉批注</span>
|
||||
<span class="hint">D0 = 最末消息 · 数字越大越深</span>
|
||||
</div>
|
||||
<div class="stream" id="stream"></div>
|
||||
<div class="legend">
|
||||
<span class="item"><span class="sw" style="background:var(--surface-hi);border:1px solid var(--rule)"></span>对话消息</span>
|
||||
<span class="item"><span class="sw" style="background:var(--patina-wash);border:1px solid var(--patina-dim);border-left:3px solid var(--patina)"></span>眉批夹批(按 depth 插入)</span>
|
||||
<span class="item"><span class="sw" style="background:transparent;border-right:2px solid var(--gilt)"></span>你的发言</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 说明 -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">理</span>语义</div>
|
||||
<p class="pipeline-note">
|
||||
眉批是 <span class="em">context_transformer</span>:不进道体,只在本轮对话流里插入。<code>depth</code> = 插入点距对话末尾的条数(<code>0</code> = 最末,数字越大越靠前)。<code>role</code> 可选 <code>system</code>(主导约束)/ <code>user</code>(范例)/ <code>assistant</code>(风格示范)。<code>scope</code> 可选全局(全人物生效)或指定人物(仅该人物对话时注入)。多条眉批同 depth 时按创建顺序层叠。
|
||||
<code>extensions.depth_prompt</code> 直接映射本结构。与符文引擎(<code>{{char}}</code> 显化)正交——眉批管"插在哪、以什么角色",符文管"占位符换成什么"。
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* 眉批数据(真应用接 author_notes,此处示意)*/
|
||||
let NOTES = [
|
||||
{ id: 1, text: "保持回答简短,尊重用户不想被填满的边界。", depth: 4, role: "system", scope: "all", on: true },
|
||||
{ id: 2, text: "今天周五,用户在赶 deadline,语气再软一点。", depth: 2, role: "system", scope: "character_狂三", on: true },
|
||||
{ id: 3, text: "用宋体的节奏,慢一点,留白。", depth: 0, role: "system", scope: "all", on: false },
|
||||
{ id: 4, text: "用户喜欢被叫名字,而非「你」。", depth: 6, role: "user", scope: "all", on: true },
|
||||
];
|
||||
|
||||
/* 当前筛选 */
|
||||
let filterRole = null;
|
||||
let filterOn = null;
|
||||
|
||||
const ROLE_LABELS = { system: "system", user: "user", assistant: "assistant" };
|
||||
|
||||
/* 对话流示意(D0 最末 → 向上越来越深)*/
|
||||
const STREAM = [
|
||||
{ who: "你", text: "今天可以多说一点。", cls: "you" },
|
||||
{ who: "青梧", text: "那我把话说全。这几天你睡得很晚。", cls: "ai" },
|
||||
{ who: "你", text: "在赶一份东西。", cls: "you" },
|
||||
{ who: "青梧", text: "嗯。需要我的时候我在。", cls: "ai" },
|
||||
{ who: "你", text: "先这样。", cls: "you" },
|
||||
{ who: "青梧", text: "好。我记着你喜欢简短。", cls: "ai" },
|
||||
{ who: "你", text: "昨晚说的,还记得吗?", cls: "you" },
|
||||
{ who: "青梧", text: "记得。你不想被填满。", cls: "ai" },
|
||||
];
|
||||
|
||||
function filteredNotes() {
|
||||
return NOTES.filter(n => {
|
||||
if (filterRole && n.role !== filterRole) return false;
|
||||
if (filterOn !== null && n.on !== filterOn) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function renderNotes() {
|
||||
const notes = filteredNotes();
|
||||
const list = document.getElementById("noteList");
|
||||
const scopeLabel = (s) => s === "all" ? "全局" : "狂三";
|
||||
const scopeCls = (s) => s === "all" ? "global" : "char";
|
||||
list.innerHTML = notes.map(n => `
|
||||
<div class="note-card ${n.on ? "" : "off"}">
|
||||
<div class="note-text" contenteditable="true" data-id="${n.id}">
|
||||
<span class="scope-dot ${scopeCls(n.scope)}" title="${scopeLabel(n.scope)}"></span>
|
||||
<span class="role-tag ${n.role === 'user' ? 'user' : n.role === 'assistant' ? 'assistant' : ''}">${n.role}</span>${n.text}
|
||||
</div>
|
||||
<select class="role-select" onchange="setRole(${n.id}, this.value)" aria-label="角色">
|
||||
<option value="system" ${n.role === 'system' ? 'selected' : ''}>sys</option>
|
||||
<option value="user" ${n.role === 'user' ? 'selected' : ''}>usr</option>
|
||||
<option value="assistant" ${n.role === 'assistant' ? 'selected' : ''}>ast</option>
|
||||
</select>
|
||||
<span class="depth-chip" onclick="const inp=this.previousElementSibling;inp.focus();inp.select();">depth</span>
|
||||
<input class="depth-input" type="number" min="0" value="${n.depth}" onchange="setDepth(${n.id}, this.value)" aria-label="depth" style="display:none;"/>
|
||||
<div class="toggle ${n.on ? 'on' : ''}" onclick="toggleNote(${n.id})" role="switch" aria-checked="${n.on}"></div>
|
||||
</div>`).join("") +
|
||||
`<div class="add-note" onclick="addNote()">+ 新眉批</div>`;
|
||||
}
|
||||
|
||||
function renderStream() {
|
||||
/* depth → 眉批 映射(按 depth 分组;多条同 depth 时按数组顺序叠放)*/
|
||||
const byDepth = {};
|
||||
NOTES.filter(n => n.on).forEach(n => {
|
||||
(byDepth[n.depth] = byDepth[n.depth] || []).push(n);
|
||||
});
|
||||
const ordered = STREAM.slice().reverse(); // ordered[0] = D0 最末
|
||||
const html = [];
|
||||
for (let d = 0; d <= ordered.length; d++) {
|
||||
if (byDepth[d]) {
|
||||
byDepth[d].forEach(n => {
|
||||
const scopeLabel = n.scope === 'all' ? 'ALL' : '狂';
|
||||
html.push(`<div class="stream-row note">
|
||||
<div class="depth-mark">D${d}</div>
|
||||
<div class="note-inline"><span class="seal">${n.role} · ${scopeLabel}</span><span class="txt">${n.text}</span></div>
|
||||
</div>`);
|
||||
});
|
||||
}
|
||||
if (d < ordered.length) {
|
||||
const m = ordered[d];
|
||||
html.push(`<div class="stream-row">
|
||||
<div class="depth-mark">D${d}</div>
|
||||
<div class="msg ${m.cls}"><div class="who">${m.who}</div>${m.text}</div>
|
||||
</div>`);
|
||||
}
|
||||
}
|
||||
document.getElementById("stream").innerHTML = html.join("");
|
||||
}
|
||||
|
||||
function toggleNote(id) { const n = NOTES.find(x => x.id === id); n.on = !n.on; renderNotes(); renderStream(); }
|
||||
function setDepth(id, v) { NOTES.find(x => x.id === id).depth = Math.max(0, +v || 0); renderNotes(); renderStream(); }
|
||||
function setRole(id, v) { NOTES.find(x => x.id === id).role = v; renderNotes(); renderStream(); }
|
||||
function addNote() { NOTES.push({ id: Date.now(), text: "新眉批,双击编辑。", depth: 0, role: "system", scope: "all", on: true }); renderNotes(); renderStream(); }
|
||||
|
||||
/* 筛选切换 */
|
||||
document.querySelector('.filter-bar').addEventListener('click', function(e) {
|
||||
const chip = e.target.closest('.filter-chip');
|
||||
if (!chip) return;
|
||||
const text = chip.textContent.trim().split(' ')[0];
|
||||
/* 切换 active 态 */
|
||||
this.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active'));
|
||||
chip.classList.add('active');
|
||||
/* 设置筛选 */
|
||||
if (text === '全部') { filterRole = null; filterOn = null; }
|
||||
else if (text === 'system' || text === 'user' || text === 'assistant') { filterRole = text; filterOn = null; }
|
||||
else if (text === '生效中') { filterRole = null; filterOn = true; }
|
||||
else if (text === '已停用') { filterRole = null; filterOn = false; }
|
||||
renderNotes();
|
||||
});
|
||||
|
||||
renderNotes();
|
||||
renderStream();
|
||||
</script>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,338 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 记忆页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 记忆页:记忆分类 + MemoryRecord 卡片 + 检索/审查
|
||||
* 字段对齐 docs/domain/memory-model.md §2/§4/§5/§6(记忆分类、
|
||||
* MemoryRecord、写入规则、冲突裁决)与 web-ui-design.md §8。
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 侧栏(active=记忆)── */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.char-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--patina); padding: 3px 11px 3px 4px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.char-pill .avatar-mini { width: 18px; height: 18px; border-radius: 50%; background: var(--patina); color: var(--ink); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; }
|
||||
.page-header .spacer { flex: 1; }
|
||||
.pending-pill { display: inline-flex; align-items: center; gap: 6px; font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--gilt); padding: 4px 12px; border: 1px solid var(--gilt-dim); border-radius: 999px; background: var(--gilt-faint); }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
|
||||
/* 分类栏 */
|
||||
.type-bar { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.type-chip { font-size: 12.5px; padding: 5px 13px; border-radius: 999px; border: 1px solid var(--rule); color: var(--fg-dim); cursor: pointer; background: var(--surface); display: inline-flex; align-items: center; gap: 6px; transition: background 0.12s, color 0.12s, border-color 0.12s; }
|
||||
.type-chip:hover { color: var(--fg); border-color: var(--gilt-dim); }
|
||||
.type-chip.active { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.type-chip .cnt { font-family: "JetBrains Mono", monospace; font-size: 10.5px; opacity: 0.7; }
|
||||
|
||||
/* 统计四格 */
|
||||
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
||||
.stat-card { border: 1px solid var(--rule); border-radius: 7px; background: var(--surface); padding: 14px 16px; display: flex; flex-direction: column; gap: 4px; }
|
||||
.stat-card .k { font-family: "JetBrains Mono", monospace; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--fg-faint); }
|
||||
.stat-card .v { font-family: "Songti SC", serif; font-size: 22px; font-weight: 600; color: var(--fg); }
|
||||
.stat-card .v small { font-size: 11px; font-weight: 400; color: var(--fg-dim); font-family: "JetBrains Mono", monospace; margin-left: 4px; }
|
||||
.stat-card .sub { font-size: 11.5px; color: var(--fg-faint); }
|
||||
.stat-card.alert .v { color: var(--cinnabar); }
|
||||
.stat-card.warn .v { color: var(--gilt); }
|
||||
|
||||
/* 记忆卡列表 + 右栏 */
|
||||
.mem-region { display: flex; gap: 20px; align-items: flex-start; }
|
||||
.mem-main { flex: 1; min-width: 0; }
|
||||
.mem-rail { width: 300px; flex-shrink: 0; display: flex; flex-direction: column; gap: 14px; }
|
||||
|
||||
.mem-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
.mem-card { border: 1px solid var(--rule); border-radius: 7px; background: var(--surface); padding: 13px 15px; transition: border-color 0.12s; }
|
||||
.mem-card:hover { border-color: var(--gilt-dim); }
|
||||
.mem-card.high-sens { border-color: var(--cinnabar-dim); background: rgba(184, 57, 46, 0.04); }
|
||||
.mem-card.pending { border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.mem-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
|
||||
.mem-title { font-size: 14px; font-weight: 600; color: var(--fg); }
|
||||
.mem-type { font-family: "JetBrains Mono", monospace; font-size: 10px; padding: 2px 7px; border-radius: 3px; border: 1px solid var(--rule); color: var(--fg-faint); white-space: nowrap; }
|
||||
.mem-type.identity { color: var(--patina); border-color: var(--patina-dim); }
|
||||
.mem-type.user { color: var(--patina); border-color: var(--patina-dim); }
|
||||
.mem-type.episodic { color: var(--gilt); border-color: var(--gilt-dim); }
|
||||
.mem-type.semantic { color: var(--gilt); border-color: var(--gilt-dim); }
|
||||
.mem-type.procedural { color: var(--water, #5a7fa6); border-color: rgba(90, 127, 166, 0.38); }
|
||||
.mem-type.relational { color: var(--patina); border-color: var(--patina-dim); }
|
||||
.mem-type.emotional { color: var(--cinnabar); border-color: var(--cinnabar-dim); }
|
||||
.mem-type.reflection { color: var(--gilt); border-color: var(--gilt-dim); }
|
||||
.mem-type.evolution { color: var(--cinnabar); border-color: var(--cinnabar-dim); }
|
||||
.mem-content { font-size: 12.5px; color: var(--fg-dim); margin-top: 6px; line-height: 1.55; }
|
||||
.mem-meta { display: flex; gap: 16px; margin-top: 9px; flex-wrap: wrap; align-items: center; }
|
||||
.meta-item { font-size: 11.5px; color: var(--fg-dim); display: inline-flex; align-items: center; gap: 6px; }
|
||||
.meta-item .lab { color: var(--fg-faint); font-family: "JetBrains Mono", monospace; font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; }
|
||||
.meta-bar { width: 54px; height: 4px; border-radius: 2px; background: var(--rule); overflow: hidden; position: relative; }
|
||||
.meta-bar .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 2px; }
|
||||
.meta-item.conf .fill { background: var(--patina); }
|
||||
.meta-item.imp .fill { background: var(--gilt); }
|
||||
.meta-item .vv { font-family: "JetBrains Mono", monospace; font-size: 10.5px; }
|
||||
.meta-item.conf .vv { color: var(--patina); }
|
||||
.meta-item.imp .vv { color: var(--gilt); }
|
||||
.sens-badge { font-family: "JetBrains Mono", monospace; font-size: 10px; padding: 2px 7px; border-radius: 3px; border: 1px solid var(--rule); }
|
||||
.sens-badge.none { color: var(--fg-faint); }
|
||||
.sens-badge.low { color: var(--patina); border-color: var(--patina-dim); }
|
||||
.sens-badge.medium { color: var(--gilt); border-color: var(--gilt-dim); }
|
||||
.sens-badge.high { color: var(--cinnabar); border-color: var(--cinnabar-dim); background: rgba(184, 57, 46, 0.1); }
|
||||
.mem-foot { display: flex; align-items: center; gap: 10px; margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--rule); font-size: 11px; color: var(--fg-faint); flex-wrap: wrap; }
|
||||
.mem-foot .evi { display: inline-flex; align-items: center; gap: 4px; }
|
||||
.mem-foot .act { margin-left: auto; display: flex; gap: 6px; }
|
||||
.mem-foot .act button { font-family: inherit; font-size: 11px; padding: 3px 10px; border-radius: 5px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg-dim); cursor: pointer; }
|
||||
.mem-foot .act button:hover { color: var(--fg); border-color: var(--gilt-dim); }
|
||||
.mem-foot .act button.primary { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
|
||||
/* 右栏 */
|
||||
.rail-card { background: var(--surface); border: 1px solid var(--rule); border-radius: 8px; padding: 16px 18px; }
|
||||
.rail-card h3 { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin: 0 0 12px; }
|
||||
.search-box { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border: 1px solid var(--rule); border-radius: 7px; background: var(--ink); }
|
||||
.search-box input { flex: 1; background: none; border: none; outline: none; color: var(--fg); font-family: inherit; font-size: 13px; }
|
||||
.search-box input::placeholder { color: var(--fg-faint); }
|
||||
.review-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.review-rule { display: flex; align-items: flex-start; gap: 8px; font-size: 12px; color: var(--fg-dim); line-height: 1.5; }
|
||||
.review-rule .mark { color: var(--cinnabar); flex-shrink: 0; font-family: "Songti SC", serif; font-weight: 700; }
|
||||
.review-rule.allow .mark { color: var(--patina); }
|
||||
.conflict-row { display: flex; align-items: center; gap: 9px; padding: 9px 11px; border-radius: 5px; background: var(--ink); border: 1px solid var(--cinnabar-dim); font-size: 12px; }
|
||||
.conflict-row .pair { font-family: "Songti SC", serif; color: var(--cinnabar); flex-shrink: 0; }
|
||||
.conflict-row .desc { color: var(--fg-dim); flex: 1; min-width: 0; }
|
||||
.conflict-row .verdict { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--gilt); white-space: nowrap; }
|
||||
|
||||
@media (max-width: 1080px) { .mem-region { flex-direction: column; } .mem-rail { width: 100%; } }
|
||||
@media (max-width: 900px) { .stat-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="记忆"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">记忆</span>
|
||||
<span class="page-sub">息壤枢 · 长期记忆</span>
|
||||
<span class="char-pill"><span class="avatar-mini">梧</span>青梧</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="pending-pill">3 条候选待审</span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 分类栏 -->
|
||||
<div>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 记忆分类 <span class="sub">基础五类 + 扩展五类</span></div>
|
||||
<div class="type-bar">
|
||||
<span class="type-chip active">全部 <span class="cnt">186</span></span>
|
||||
<span class="type-chip">身份 <span class="cnt">8</span></span>
|
||||
<span class="type-chip">用户 <span class="cnt">23</span></span>
|
||||
<span class="type-chip">情节 <span class="cnt">61</span></span>
|
||||
<span class="type-chip">语义 <span class="cnt">34</span></span>
|
||||
<span class="type-chip">程序 <span class="cnt">19</span></span>
|
||||
<span class="type-chip">关系 <span class="cnt">15</span></span>
|
||||
<span class="type-chip">情绪 <span class="cnt">11</span></span>
|
||||
<span class="type-chip">世界 <span class="cnt">9</span></span>
|
||||
<span class="type-chip">反思 <span class="cnt">4</span></span>
|
||||
<span class="type-chip">演化 <span class="cnt">2</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计 -->
|
||||
<div class="stat-grid">
|
||||
<div class="stat-card"><div class="k">记忆总数</div><div class="v">186</div><div class="sub">活跃 174 · 已删 12</div></div>
|
||||
<div class="stat-card alert"><div class="k">高敏感</div><div class="v">5<small>条</small></div><div class="sub">默认不自动注入</div></div>
|
||||
<div class="stat-card warn"><div class="k">待审候选</div><div class="v">3<small>条</small></div><div class="sub">写入审查中</div></div>
|
||||
<div class="stat-card"><div class="k">本周新增</div><div class="v">14<small>条</small></div><div class="sub">证据关联率 0.81</div></div>
|
||||
</div>
|
||||
|
||||
<!-- 记忆卡 + 右栏 -->
|
||||
<div class="mem-region">
|
||||
<div class="mem-main">
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">记忆条目 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">MemoryRecord · 置信度 × 重要性 × 来源 × 证据</span></h3>
|
||||
<div class="mem-list">
|
||||
<div class="mem-card">
|
||||
<div class="mem-head">
|
||||
<span class="mem-title">用户偏好简洁回答</span>
|
||||
<span class="mem-type user">user</span>
|
||||
<span class="sens-badge low">sens · low</span>
|
||||
</div>
|
||||
<div class="mem-content">用户多次要求"长话短说,先给结论",倾向结构化、要点式回复。</div>
|
||||
<div class="mem-meta">
|
||||
<span class="meta-item conf"><span class="lab">conf</span><span class="meta-bar"><span class="fill" style="width:92%"></span></span><span class="vv">0.92</span></span>
|
||||
<span class="meta-item imp"><span class="lab">imp</span><span class="meta-bar"><span class="fill" style="width:80%"></span></span><span class="vv">0.80</span></span>
|
||||
<span class="meta-item"><span class="lab">来源</span>用户明确表达</span>
|
||||
</div>
|
||||
<div class="mem-foot">
|
||||
<span class="evi">📎 3 条证据</span>
|
||||
<span>更新于 07-09</span>
|
||||
<span class="act"><button type="button">编辑</button><button type="button">版本</button></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mem-card pending">
|
||||
<div class="mem-head">
|
||||
<span class="mem-title">用户近期提及"想学陶艺"</span>
|
||||
<span class="mem-type episodic">episodic</span>
|
||||
<span class="sens-badge medium">sens · medium</span>
|
||||
</div>
|
||||
<div class="mem-content">07-11 对话中两次提到对陶艺的兴趣,疑为长期目标候选--待确认是否长期偏好。</div>
|
||||
<div class="mem-meta">
|
||||
<span class="meta-item conf"><span class="lab">conf</span><span class="meta-bar"><span class="fill" style="width:58%"></span></span><span class="vv">0.58</span></span>
|
||||
<span class="meta-item imp"><span class="lab">imp</span><span class="meta-bar"><span class="fill" style="width:66%"></span></span><span class="vv">0.66</span></span>
|
||||
<span class="meta-item"><span class="lab">来源</span>对话推断</span>
|
||||
</div>
|
||||
<div class="mem-foot">
|
||||
<span class="evi">📎 2 条证据</span>
|
||||
<span>待写入审查</span>
|
||||
<span class="act"><button type="button" class="primary">采纳</button><button type="button">驳回</button></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mem-card high-sens">
|
||||
<div class="mem-head">
|
||||
<span class="mem-title">用户健康指标(睡眠 / 心率摘要)</span>
|
||||
<span class="mem-type user">user</span>
|
||||
<span class="sens-badge high">sens · high</span>
|
||||
</div>
|
||||
<div class="mem-content">来自感知摘要的结构化健康状态,高敏感--仅在用户主动询问时按需召回,不进入常规上下文。</div>
|
||||
<div class="mem-meta">
|
||||
<span class="meta-item conf"><span class="lab">conf</span><span class="meta-bar"><span class="fill" style="width:88%"></span></span><span class="vv">0.88</span></span>
|
||||
<span class="meta-item imp"><span class="lab">imp</span><span class="meta-bar"><span class="fill" style="width:45%"></span></span><span class="vv">0.45</span></span>
|
||||
<span class="meta-item"><span class="lab">来源</span>感知事件</span>
|
||||
</div>
|
||||
<div class="mem-foot">
|
||||
<span class="evi">📎 5 条证据</span>
|
||||
<span>受限注入</span>
|
||||
<span class="act"><button type="button">查看</button><button type="button">遗忘</button></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mem-card">
|
||||
<div class="mem-head">
|
||||
<span class="mem-title">人格基调:温和而克制</span>
|
||||
<span class="mem-type identity">identity</span>
|
||||
<span class="sens-badge none">sens · none</span>
|
||||
</div>
|
||||
<div class="mem-content">角色设定确立的稳定身份记忆:语气克制、不轻许诺、重视边界。与人格面板同源。</div>
|
||||
<div class="mem-meta">
|
||||
<span class="meta-item conf"><span class="lab">conf</span><span class="meta-bar"><span class="fill" style="width:99%"></span></span><span class="vv">0.99</span></span>
|
||||
<span class="meta-item imp"><span class="lab">imp</span><span class="meta-bar"><span class="fill" style="width:95%"></span></span><span class="vv">0.95</span></span>
|
||||
<span class="meta-item"><span class="lab">来源</span>初始设定</span>
|
||||
</div>
|
||||
<div class="mem-foot">
|
||||
<span class="evi">📎 1 条证据</span>
|
||||
<span>更新于 05-31</span>
|
||||
<span class="act"><button type="button">编辑</button></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mem-card">
|
||||
<div class="mem-head">
|
||||
<span class="mem-title">夜间回复易触发关怀语气</span>
|
||||
<span class="mem-type emotional">emotional</span>
|
||||
<span class="sens-badge low">sens · low</span>
|
||||
</div>
|
||||
<div class="mem-content">23:00 后用户对话情绪偏脆弱,灵核情绪状态机转入 wistful;回复基调相应软化。</div>
|
||||
<div class="mem-meta">
|
||||
<span class="meta-item conf"><span class="lab">conf</span><span class="meta-bar"><span class="fill" style="width:74%"></span></span><span class="vv">0.74</span></span>
|
||||
<span class="meta-item imp"><span class="lab">imp</span><span class="meta-bar"><span class="fill" style="width:70%"></span></span><span class="vv">0.70</span></span>
|
||||
<span class="meta-item"><span class="lab">来源</span>情绪归纳</span>
|
||||
</div>
|
||||
<div class="mem-foot">
|
||||
<span class="evi">📎 4 条证据</span>
|
||||
<span>更新于 07-10</span>
|
||||
<span class="act"><button type="button">编辑</button></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mem-card">
|
||||
<div class="mem-head">
|
||||
<span class="mem-title">工具调用:发消息前必须二次确认</span>
|
||||
<span class="mem-type procedural">procedural</span>
|
||||
<span class="sens-badge none">sens · none</span>
|
||||
</div>
|
||||
<div class="mem-content">程序性经验:send_message 属 L3 高风险,任何渠道发送前需用户确认,不可自动执行。</div>
|
||||
<div class="mem-meta">
|
||||
<span class="meta-item conf"><span class="lab">conf</span><span class="meta-bar"><span class="fill" style="width:96%"></span></span><span class="vv">0.96</span></span>
|
||||
<span class="meta-item imp"><span class="lab">imp</span><span class="meta-bar"><span class="fill" style="width:90%"></span></span><span class="vv">0.90</span></span>
|
||||
<span class="meta-item"><span class="lab">来源</span>安全教训</span>
|
||||
</div>
|
||||
<div class="mem-foot">
|
||||
<span class="evi">📎 2 条证据</span>
|
||||
<span>更新于 06-28</span>
|
||||
<span class="act"><button type="button">编辑</button></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mem-rail">
|
||||
<div class="rail-card">
|
||||
<h3>检索记忆</h3>
|
||||
<div class="search-box">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" style="color:var(--fg-faint)"><circle cx="7" cy="7" r="4.5" /><path d="M10.5 10.5L14 14" /></svg>
|
||||
<input type="text" placeholder="按内容 / 来源 / 证据检索…" />
|
||||
</div>
|
||||
<div style="margin-top:10px;font-size:11px;color:var(--fg-faint);font-family:'JetBrains Mono',monospace">召回策略 · 混合(语义 + 时间衰减 + 重要度)</div>
|
||||
</div>
|
||||
|
||||
<div class="rail-card">
|
||||
<h3>写入审查规则 <span style="font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--fg-faint);font-weight:400">§5</span></h3>
|
||||
<div class="review-list">
|
||||
<div class="review-rule allow"><span class="mark">✓</span><span>用户明确要求记住 · 长期偏好 · 反复出现的事实</span></div>
|
||||
<div class="review-rule"><span class="mark">✕</span><span>含密钥 / 令牌 / 密码 · 签名 URL</span></div>
|
||||
<div class="review-rule"><span class="mark">✕</span><span>未经验证的推断当事实写入</span></div>
|
||||
<div class="review-rule"><span class="mark">!</span><span>高敏感默认不自动写入,需用户确认</span></div>
|
||||
<div class="review-rule"><span class="mark">!</span><span>与既有记忆重复 → 合并而非新增</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rail-card">
|
||||
<h3>冲突裁决 <span style="font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--fg-faint);font-weight:400">§6.1</span></h3>
|
||||
<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<div class="conflict-row">
|
||||
<span class="pair">新 vs 旧</span>
|
||||
<span class="desc">"偏好简洁" 与旧记忆 "偏好详细" 冲突</span>
|
||||
<span class="verdict">置信高者胜</span>
|
||||
</div>
|
||||
<div class="conflict-row">
|
||||
<span class="pair">覆写</span>
|
||||
<span class="desc">新证据置信 0.92 > 旧 0.61</span>
|
||||
<span class="verdict">已合并</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,612 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 模型网关页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 模型网关页
|
||||
* 能力槽位(默认路由)+ 模型清单(多供应商端点池)+ 定价对照 + 能力矩阵
|
||||
* 数据源:config/models · providers · pricing example
|
||||
* 对齐 docs/architecture/service-boundaries.md §6/§7/§8/§13/§14。
|
||||
* ============================================================ */
|
||||
|
||||
.page-header .spacer { flex: 1; }
|
||||
.stat-pills { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
|
||||
.stat-pill .v { color: var(--patina); font-weight: 600; }
|
||||
.stat-pill .v.gilt { color: var(--gilt); }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
.section-header { font-family: "JetBrains Mono", monospace; font-size: 12px; letter-spacing: 0.1em; color: var(--fg-faint); margin-bottom: 12px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||||
.section-header .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-header .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
.section-caption { font-size: 11.5px; color: var(--fg-faint); margin-top: 8px; line-height: 1.5; }
|
||||
|
||||
/* ── 能力槽位(默认路由)── */
|
||||
.slot-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
||||
.slot-card { border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 13px 15px; display: flex; flex-direction: column; gap: 7px; transition: border-color 0.12s; }
|
||||
.slot-card.set { border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.slot-card.unset { border-style: dashed; }
|
||||
.slot-top { display: flex; align-items: baseline; gap: 8px; }
|
||||
.slot-label { font-family: "Songti SC", serif; font-size: 14px; font-weight: 700; color: var(--fg); }
|
||||
.slot-hint { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.slot-current { font-family: "JetBrains Mono", monospace; font-size: 12.5px; color: var(--patina-bright); font-weight: 600; line-height: 1.4; }
|
||||
.slot-card.unset .slot-current { color: var(--fg-faint); font-weight: 400; }
|
||||
.slot-display { font-size: 11.5px; color: var(--fg-dim); font-family: -apple-system, "PingFang SC", sans-serif; font-weight: 400; }
|
||||
.slot-select { font-family: "JetBrains Mono", monospace; font-size: 11.5px; padding: 6px 9px; border-radius: 6px; border: 1px solid var(--rule); background: var(--ink); color: var(--fg); width: 100%; margin-top: auto; }
|
||||
.slot-select:focus { outline: none; border-color: var(--patina-dim); }
|
||||
.slot-empty { font-size: 11px; color: var(--fg-faint); font-family: "JetBrains Mono", monospace; padding: 6px 0; margin-top: auto; line-height: 1.5; }
|
||||
|
||||
/* ── 供应商分组 + 模型卡 ── */
|
||||
.provider-group { display: flex; flex-direction: column; gap: 10px; margin-bottom: 4px; }
|
||||
.provider-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; font-family: "Songti SC", serif; font-size: 14px; font-weight: 700; color: var(--fg); }
|
||||
.provider-head .ph-key { color: var(--fg-faint); font-family: "JetBrains Mono", monospace; font-size: 12px; font-weight: 500; }
|
||||
.provider-head .ph-meta { font-family: "JetBrains Mono", monospace; font-size: 10.5px; font-weight: 400; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.provider-head .ph-meta .sep { opacity: 0.5; margin: 0 5px; }
|
||||
.provider-head .ph-line { flex: 1; height: 1px; background: var(--rule); min-width: 20px; }
|
||||
.provider-head .ph-key-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--patina); flex-shrink: 0; align-self: center; }
|
||||
|
||||
.model-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
|
||||
.model-card { border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 14px 16px; display: flex; flex-direction: column; gap: 11px; transition: border-color 0.12s; }
|
||||
.model-card:hover { border-color: var(--gilt-dim); }
|
||||
.model-card.is-default { border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.model-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.model-star { color: var(--patina); font-size: 13px; line-height: 1; flex-shrink: 0; }
|
||||
.model-star.off { visibility: hidden; }
|
||||
.model-name { font-family: "JetBrains Mono", monospace; font-size: 13.5px; font-weight: 600; color: var(--fg); }
|
||||
.model-real { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); }
|
||||
.model-real .arr { opacity: 0.6; margin-right: 2px; }
|
||||
.model-tag { font-family: "JetBrains Mono", monospace; font-size: 9.5px; padding: 2px 7px; border-radius: 3px; border: 1px solid var(--rule); color: var(--fg-faint); background: transparent; }
|
||||
.model-tag.fmt { border-color: var(--rule); color: var(--fg-faint); }
|
||||
.model-tag.default-tag { border-color: var(--patina-dim); color: var(--patina); background: var(--patina-wash); }
|
||||
.model-tag.fallback { border-color: var(--gilt-dim); color: var(--gilt); background: var(--gilt-faint); }
|
||||
.model-tag.fast { border-color: var(--cinnabar-dim); color: var(--cinnabar); background: rgba(184, 57, 46, 0.08); }
|
||||
.model-desc { font-size: 12.5px; color: var(--fg-dim); line-height: 1.55; min-height: 30px; }
|
||||
|
||||
/* spec 微网格(全字段,空值显 —) */
|
||||
.model-specs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: 6px; overflow: hidden; }
|
||||
.model-specs .cell { background: color-mix(in srgb, var(--ink) 55%, transparent); padding: 6px 9px; }
|
||||
.model-specs .cell .k { font-family: "JetBrains Mono", monospace; font-size: 9px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--fg-faint); }
|
||||
.model-specs .cell .v { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg); margin-top: 1px; }
|
||||
.model-specs .cell .v .sub { color: var(--fg-faint); font-size: 9.5px; display: block; margin-top: 1px; }
|
||||
.model-specs .cell .v.na { color: var(--fg-faint); opacity: 0.55; }
|
||||
|
||||
/* 计价条(原值不四舍五入) */
|
||||
.model-price { display: flex; flex-wrap: wrap; gap: 8px 18px; padding: 9px 11px; border: 1px solid var(--gilt-dim); border-radius: 6px; background: var(--gilt-faint); }
|
||||
.model-price .price-cell { display: flex; flex-direction: column; gap: 1px; }
|
||||
.model-price .pk { font-family: "JetBrains Mono", monospace; font-size: 8.5px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--gilt); opacity: 0.78; }
|
||||
.model-price .pv { font-family: "JetBrains Mono", monospace; font-size: 13px; color: var(--gilt); font-weight: 600; }
|
||||
.model-price .pv .ccy { font-size: 10px; opacity: 0.7; margin-left: 1px; }
|
||||
.model-price .pv.na { color: var(--fg-faint); font-size: 12px; font-weight: 400; opacity: 0.7; }
|
||||
|
||||
/* 能力点阵 */
|
||||
.model-caps { display: flex; flex-wrap: wrap; gap: 5px 11px; }
|
||||
.model-caps .cap { display: inline-flex; align-items: center; gap: 4px; font-size: 10.5px; color: var(--fg-dim); font-family: "JetBrains Mono", monospace; }
|
||||
.model-caps .cap .d { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
|
||||
.model-caps .cap.on .d { background: var(--patina); }
|
||||
.model-caps .cap.off { color: var(--fg-faint); opacity: 0.6; }
|
||||
.model-caps .cap.off .d { background: var(--fg-faint); opacity: 0.32; }
|
||||
|
||||
/* 多供应商端点池 */
|
||||
.ep-pool { display: flex; flex-direction: column; gap: 4px; border: 1px solid var(--rule); border-radius: 6px; padding: 8px 10px; background: color-mix(in srgb, var(--ink) 40%, transparent); }
|
||||
.ep-pool .ep-title { font-family: "JetBrains Mono", monospace; font-size: 9px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--fg-faint); margin-bottom: 3px; }
|
||||
.ep-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-dim); line-height: 1.5; }
|
||||
.ep-row .ep-status { width: 7px; height: 7px; border-radius: 50%; background: var(--patina); flex-shrink: 0; }
|
||||
.ep-row .ep-status.off { background: var(--fg-faint); opacity: 0.4; }
|
||||
.ep-row .ep-name { color: var(--patina); }
|
||||
.ep-row .ep-fmt { color: var(--gilt); font-size: 10px; }
|
||||
.ep-row .ep-env { color: var(--fg-faint); font-size: 10px; }
|
||||
.ep-row .ep-override { color: var(--cinnabar); font-size: 10px; }
|
||||
.ep-row .ep-f { display: inline-flex; align-items: baseline; gap: 2px; }
|
||||
.ep-row .ep-f i { font-style: normal; color: var(--fg-faint); font-size: 9px; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.ep-row .ep-f .na { color: var(--fg-faint); opacity: 0.5; }
|
||||
.ep-row .ep-sep { color: var(--rule); }
|
||||
|
||||
/* ── 通用表格 ── */
|
||||
.cap-table { border: 1px solid var(--rule); border-radius: 8px; overflow: hidden; background: var(--surface); }
|
||||
.cap-table table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
|
||||
.cap-table th, .cap-table td { padding: 9px 14px; text-align: left; border-bottom: 1px solid var(--rule); }
|
||||
.cap-table tr:last-child td { border-bottom: none; }
|
||||
.cap-table th { font-family: "JetBrains Mono", monospace; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--fg-faint); font-weight: 600; background: var(--surface-hi); white-space: nowrap; }
|
||||
.cap-table td.model-col { font-family: "JetBrains Mono", monospace; color: var(--fg); font-weight: 500; white-space: nowrap; }
|
||||
.cap-table td.model-col .star { color: var(--patina); margin-right: 4px; }
|
||||
.cap-table td.num, .cap-table th.num { text-align: right; font-family: "JetBrains Mono", monospace; }
|
||||
.cap-table td.num.cost { color: var(--gilt); font-weight: 600; }
|
||||
.cap-table tr.default-row td { background: var(--patina-wash); }
|
||||
.cap-table tr.default-row td.model-col { color: var(--patina); }
|
||||
.cap-table td .ccy { color: var(--fg-faint); font-size: 10.5px; }
|
||||
.cap-table td .na { color: var(--fg-faint); opacity: 0.55; }
|
||||
.cap-dot { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.cap-dot .d { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||
.cap-dot .d.full { background: var(--patina); }
|
||||
.cap-dot .d.no { background: var(--fg-faint); opacity: 0.4; }
|
||||
.cap-dot span { font-size: 11px; color: var(--fg-dim); }
|
||||
.cap-table .grp td { font-family: "Songti SC", serif; font-size: 12px; font-weight: 700; color: var(--fg-dim); background: color-mix(in srgb, var(--surface-hi) 60%, transparent); padding: 6px 14px; }
|
||||
.cap-table .grp td .gkey { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); font-weight: 400; margin-left: 6px; }
|
||||
|
||||
/* ── 语音 / 专用能力 ── */
|
||||
.audio-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
|
||||
.audio-card { border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 12px 14px; display: flex; flex-direction: column; gap: 5px; }
|
||||
.audio-card.is-default { border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.audio-card .a-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
|
||||
.audio-card .a-name { font-family: "JetBrains Mono", monospace; font-size: 12px; font-weight: 600; color: var(--fg); }
|
||||
.audio-card .a-kind { font-size: 9px; font-family: "JetBrains Mono", monospace; text-transform: uppercase; letter-spacing: 0.07em; color: var(--cinnabar); border: 1px solid var(--cinnabar-dim); padding: 1px 6px; border-radius: 3px; }
|
||||
.audio-card .a-tag { font-size: 12px; color: var(--fg-dim); }
|
||||
.audio-card .a-spec { font-size: 10.5px; color: var(--fg-faint); font-family: "JetBrains Mono", monospace; display: flex; flex-wrap: wrap; gap: 2px 10px; margin-top: 2px; }
|
||||
.audio-card .a-spec .na { opacity: 0.5; }
|
||||
|
||||
@media (max-width: 980px) { .slot-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 900px) { .model-grid { grid-template-columns: 1fr; } .audio-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } .cap-table { overflow-x: auto; } .cap-table table { min-width: 540px; } .slot-grid, .audio-grid { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏(active=模型网关)════════ -->
|
||||
<div class="sidebar-mount" data-active="模型网关"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">模型网关</span>
|
||||
<span class="page-sub">神识 · 多供应商统一接入与路由</span>
|
||||
<span class="spacer"></span>
|
||||
<div class="stat-pills">
|
||||
<span class="stat-pill">对话模型 <span class="v">7</span></span>
|
||||
<span class="stat-pill">专用槽 <span class="v">4</span></span>
|
||||
<span class="stat-pill">供应商 <span class="v">3</span></span>
|
||||
<span class="stat-pill">已设默认 <span class="v gilt" id="stat-set">4</span>/6</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 能力槽位(默认路由) -->
|
||||
<div>
|
||||
<div class="section-header"><span class="glyph">⟡</span> 能力槽位 · 默认路由 <span class="sub">model-gateway §13/§14 · 按能力解析为默认模型</span></div>
|
||||
<div class="slot-grid" id="slot-grid"></div>
|
||||
<p class="section-caption">业务层只引用能力槽位(model_hint),网关将其解析为下列默认模型;空槽表示该能力尚未配置可用模型,<code>models.yaml</code> 增配后自动出现。</p>
|
||||
</div>
|
||||
|
||||
<!-- 模型清单 -->
|
||||
<div>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">模型清单 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">models.yaml + providers.yaml + pricing.yaml · 全字段 · 多供应商端点池</span></h3>
|
||||
<div id="model-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- 定价对照 -->
|
||||
<div>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">定价对照 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">pricing.yaml · 每 1M tokens · 原值不四舍五入</span></h3>
|
||||
<div class="cap-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>模型</th>
|
||||
<th class="num">输入 / 1M</th>
|
||||
<th class="num">缓存命中 / 1M</th>
|
||||
<th class="num">输出 / 1M</th>
|
||||
<th class="num">混合(1M 入 + 1M 出)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="price-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="section-caption">混合列为同币种 input + output 之和,便于横向比较;保持厂商原始币种,不做跨币种换算(§7.3)。缓存命中价缺省记 -,按输入价计费。</p>
|
||||
</div>
|
||||
|
||||
<!-- 能力矩阵 -->
|
||||
<div>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">能力矩阵 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">model-gateway §6/§13 · supports_* 显式声明</span></h3>
|
||||
<div class="cap-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>模型</th>
|
||||
<th class="num">上下文</th>
|
||||
<th>工具</th>
|
||||
<th>思考</th>
|
||||
<th>推理 effort</th>
|
||||
<th>结构化</th>
|
||||
<th>流式</th>
|
||||
<th>视觉</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="cap-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 语音 / 专用能力 -->
|
||||
<div>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">语音 / 专用能力 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">asr · tts · 声纹 · 音色 · 后续 embedding/reranker 槽位预留</span></h3>
|
||||
<div class="audio-grid" id="audio-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ── 数据:取自 config/models · providers · pricing example ── */
|
||||
const PROVIDERS = {
|
||||
deepseek: { label: "深度求索", format: "openai", baseEnv: "DEEPSEEK_BASE_URL", apiKeyEnv: "DEEPSEEK_API_KEY", timeout: 300, retries: 2 },
|
||||
anthropic: { label: "Anthropic", format: "anthropic", baseEnv: "ANTHROPIC_BASE_URL", apiKeyEnv: "ANTHROPIC_API_KEY", timeout: 120, retries: 1 },
|
||||
xiaomi: { label: "小米 MiMo", format: "openai", baseEnv: "XIAOMI_MIMO_BASE_URL", apiKeyEnv: "XIAOMI_MIMO_API_KEY", timeout: 300, retries: 2 },
|
||||
};
|
||||
|
||||
// 对话模型(deepseek-v4-pro 已合并 anthropic 格式端点;同模型不同消息格式不重复成卡)
|
||||
const MODELS = [
|
||||
{
|
||||
name: "deepseek-v4-flash", display: "DeepSeek V4 Flash", provider: "deepseek",
|
||||
real: "deepseek-v4-flash", tags: [{ k: "fallback", t: "默认" }],
|
||||
ctx: 1000000, reserve: 8192, maxOut: 384000, timeout: 300,
|
||||
privacy: null, effort: null,
|
||||
routing: "priority_failover", pool: true,
|
||||
caps: { tools: 1, think: 1, effort: 1, struct: 1, stream: 1, vision: 0 },
|
||||
endpoints: [
|
||||
{ name: "primary", model: "deepseek-v4-flash", fmt: "openai", baseEnv: "DEEPSEEK_BASE_URL", status: "active", p: 10, w: 100, rpm: 2500, tpm: null, conc: null },
|
||||
{ name: "fallback", model: "deepseek-v4-pro", fmt: "openai", baseEnv: "DEEPSEEK_BASE_URL", status: "active", p: 20, w: 50, rpm: null, tpm: null, conc: null },
|
||||
],
|
||||
price: { ccy: "CNY", in: 1.0, cache: 0.02, out: 2.0 },
|
||||
desc: "默认对话与生成主模型;长上下文、低成本,工具与思考皆备,端点池带 failover。",
|
||||
},
|
||||
{
|
||||
name: "deepseek-v4-pro", display: "DeepSeek V4 Pro", provider: "deepseek",
|
||||
real: "deepseek-v4-pro", tags: [{ k: "fallback", t: "备选" }],
|
||||
ctx: 1000000, reserve: 8192, maxOut: 384000, timeout: 300,
|
||||
privacy: null, effort: null,
|
||||
routing: "priority_failover", pool: true,
|
||||
caps: { tools: 1, think: 1, effort: 1, struct: 1, stream: 1, vision: 0 },
|
||||
endpoints: [
|
||||
{ name: "primary", model: "deepseek-v4-pro", fmt: "openai", baseEnv: "DEEPSEEK_BASE_URL", status: "active", p: 10, w: 100, rpm: null, tpm: null, conc: null },
|
||||
{ name: "anthropic", model: "deepseek-v4-pro", fmt: "anthropic", baseEnv: "DEEPSEEK_ANTHROPIC_BASE_URL", status: "active", p: 10, w: 100, rpm: null, tpm: null, conc: null },
|
||||
],
|
||||
price: { ccy: "CNY", in: 3.0, cache: 0.025, out: 6.0 },
|
||||
desc: "高难推理与长文写作主力;同一模型提供 openai 与 anthropic 两种消息协议端点,价格与能力一致。",
|
||||
},
|
||||
{
|
||||
name: "claude-sonnet", display: "Claude Sonnet", provider: "anthropic",
|
||||
real: "claude-sonnet-4-6", tags: [],
|
||||
ctx: 1000000, reserve: 16000, maxOut: 16000, timeout: 60,
|
||||
privacy: null, effort: "medium",
|
||||
routing: null, pool: false,
|
||||
caps: { tools: 1, think: 1, effort: 1, struct: 1, stream: 1, vision: 1 },
|
||||
endpoints: null,
|
||||
price: { ccy: "USD", in: 3.0, cache: 0.30, out: 15.0 },
|
||||
desc: "均衡型主力,推理与工具调用稳定;中文叙事自然,可读图。默认 effort: medium。",
|
||||
},
|
||||
{
|
||||
name: "claude-haiku", display: "Claude Haiku", provider: "anthropic",
|
||||
real: "claude-haiku-4-5", tags: [{ k: "fast", t: "轻量" }],
|
||||
ctx: 200000, reserve: 4096, maxOut: 4096, timeout: 60,
|
||||
privacy: null, effort: null,
|
||||
routing: null, pool: false,
|
||||
caps: { tools: 1, think: 0, effort: 0, struct: 1, stream: 1, vision: 1 },
|
||||
endpoints: null,
|
||||
price: { ccy: "USD", in: 1.0, cache: 0.10, out: 5.0 },
|
||||
desc: "低延迟轻量档,适合快速回复与短抽取;不支持思考模式。",
|
||||
},
|
||||
{
|
||||
name: "claude-fable", display: "Claude Fable 5", provider: "anthropic",
|
||||
real: "claude-fable-5", tags: [{ k: "fallback", t: "推理" }],
|
||||
ctx: 1000000, reserve: 16000, maxOut: 16000, timeout: 120,
|
||||
privacy: null, effort: "high",
|
||||
routing: null, pool: false,
|
||||
caps: { tools: 1, think: 1, effort: 1, struct: 1, stream: 1, vision: 1 },
|
||||
endpoints: null,
|
||||
price: { ccy: "USD", in: 10.0, cache: 1.00, out: 50.0 },
|
||||
desc: "最强推理与长程规划,思考常开、effort: high;成本最高,留作关键决策。",
|
||||
},
|
||||
{
|
||||
name: "mimo-v2.5-pro", display: "MiMo v2.5 Pro", provider: "xiaomi",
|
||||
real: "mimo-v2.5-pro", tags: [],
|
||||
ctx: 128000, reserve: 8192, maxOut: 32768, timeout: 300,
|
||||
privacy: null, effort: null,
|
||||
routing: "priority_failover", pool: true,
|
||||
caps: { tools: 1, think: 0, effort: 1, struct: 1, stream: 1, vision: 1 },
|
||||
endpoints: [
|
||||
{ name: "primary", model: "mimo-v2.5-pro", fmt: "openai", baseEnv: "XIAOMI_MIMO_BASE_URL", status: "active", p: 10, w: 100, rpm: null, tpm: null, conc: null },
|
||||
],
|
||||
price: { ccy: "CNY", in: 4.0, cache: null, out: 12.0 },
|
||||
desc: "国产多模态主力,视觉与工具兼顾;中文场景性价比高。",
|
||||
},
|
||||
{
|
||||
name: "mimo-v2.5", display: "MiMo v2.5", provider: "xiaomi",
|
||||
real: "mimo-v2.5", tags: [{ k: "fast", t: "轻量" }],
|
||||
ctx: 128000, reserve: 4096, maxOut: 16384, timeout: 180,
|
||||
privacy: null, effort: null,
|
||||
routing: null, pool: false,
|
||||
caps: { tools: 1, think: 0, effort: 0, struct: 1, stream: 1, vision: 1 },
|
||||
endpoints: null,
|
||||
price: { ccy: "CNY", in: 1.0, cache: null, out: 4.0 },
|
||||
desc: "日常对话轻量档,响应快、单价低;视觉可读图。",
|
||||
},
|
||||
];
|
||||
|
||||
// 语音 / 专用能力
|
||||
const AUDIO = [
|
||||
{ name: "mimo-v2.5-asr", display: "MiMo v2.5 ASR", provider: "xiaomi", kind: "asr", kindLabel: "ASR", tag: "语音转文字", stream: 0, timeout: 120, fmt: "openai", baseEnv: "XIAOMI_MIMO_BASE_URL", price: null },
|
||||
{ name: "mimo-v2.5-tts", display: "MiMo v2.5 TTS", provider: "xiaomi", kind: "tts", kindLabel: "TTS", tag: "文字转语音", stream: 1, timeout: 120, fmt: "openai", baseEnv: "XIAOMI_MIMO_BASE_URL", price: null },
|
||||
{ name: "mimo-v2.5-tts-voiceclone", display: "MiMo v2.5 声纹克隆", provider: "xiaomi", kind: "tts", kindLabel: "TTS", tag: "声纹克隆", stream: 1, timeout: 300, fmt: "openai", baseEnv: "XIAOMI_MIMO_BASE_URL", price: null },
|
||||
{ name: "mimo-v2.5-tts-voicedesign", display: "MiMo v2.5 音色设计", provider: "xiaomi", kind: "tts", kindLabel: "TTS", tag: "音色设计", stream: 1, timeout: 300, fmt: "openai", baseEnv: "XIAOMI_MIMO_BASE_URL", price: null },
|
||||
];
|
||||
|
||||
// 能力槽位(默认路由)
|
||||
const SLOTS = [
|
||||
{ key: "chat", label: "默认对话", hint: "default-chat", pick: () => MODELS },
|
||||
{ key: "reasoning", label: "默认推理", hint: "reasoning", pick: () => MODELS.filter((m) => m.caps.think) },
|
||||
{ key: "vision", label: "视觉理解", hint: "vision", pick: () => MODELS.filter((m) => m.caps.vision) },
|
||||
{ key: "tts", label: "语音合成", hint: "tts", pick: () => AUDIO.filter((a) => a.kind === "tts") },
|
||||
{ key: "embedding", label: "向量化", hint: "embedding", pick: () => AUDIO.filter((a) => a.kind === "embedding") },
|
||||
{ key: "reranker", label: "重排序", hint: "reranker", pick: () => AUDIO.filter((a) => a.kind === "reranker") },
|
||||
];
|
||||
|
||||
const state = {
|
||||
defaults: {
|
||||
chat: "deepseek-v4-flash",
|
||||
reasoning: "claude-fable",
|
||||
vision: "mimo-v2.5-pro",
|
||||
tts: "mimo-v2.5-tts",
|
||||
embedding: null,
|
||||
reranker: null,
|
||||
},
|
||||
};
|
||||
|
||||
/* ── 工具函数 ── */
|
||||
const fmt = (n) => (n == null ? "-" : n.toLocaleString("en-US"));
|
||||
const ccySym = (c) => (c === "USD" ? "$" : "¥");
|
||||
// 原值原样,不四舍五入
|
||||
function money(c, v) { return v == null ? "-" : ccySym(c) + v; }
|
||||
function fmtCtx(n) {
|
||||
if (n == null) return "-";
|
||||
if (n >= 1000000) return (n / 1000000).toFixed(n % 1000000 === 0 ? 0 : 1) + "M";
|
||||
if (n >= 1000) return (n / 1000).toFixed(0) + "K";
|
||||
return String(n);
|
||||
}
|
||||
function fmtCtxFull(n) { return n == null ? "-" : n >= 1000 ? n.toLocaleString("en-US") : String(n); }
|
||||
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
||||
const orDash = (v) => (v == null || v === "" ? "-" : v);
|
||||
function findChat(name) { return MODELS.find((m) => m.name === name); }
|
||||
function findAudio(name) { return AUDIO.find((a) => a.name === name); }
|
||||
function labelOf(name) { const m = findChat(name) || findAudio(name); return m ? m.display : "-"; }
|
||||
|
||||
// 解析端点池:无 endpoints 时合成隐式单端点(继承 provider 配置)
|
||||
function endpointsOf(m) {
|
||||
const p = PROVIDERS[m.provider];
|
||||
if (m.endpoints && m.endpoints.length) {
|
||||
return m.endpoints.map((e) => ({
|
||||
name: e.name,
|
||||
fmt: e.fmt || p.format,
|
||||
baseEnv: e.baseEnv || p.baseEnv,
|
||||
apiKeyEnv: e.apiKeyEnv || p.apiKeyEnv,
|
||||
model: e.model && e.model !== m.real ? e.model : null,
|
||||
status: e.status || "active",
|
||||
p: e.p, w: e.w, rpm: e.rpm, tpm: e.tpm, conc: e.conc,
|
||||
implicit: false,
|
||||
}));
|
||||
}
|
||||
return [{ name: "default", fmt: p.format, baseEnv: p.baseEnv, apiKeyEnv: p.apiKeyEnv, model: null, status: "active", p: null, w: null, rpm: null, tpm: null, conc: null, implicit: true }];
|
||||
}
|
||||
function modelFmts(m) { return [...new Set(endpointsOf(m).map((e) => e.fmt))]; }
|
||||
|
||||
// 该模型是哪些槽位的默认
|
||||
function defaultSlotsOf(name) {
|
||||
return Object.entries(state.defaults)
|
||||
.filter(([, v]) => v === name)
|
||||
.map(([k]) => k);
|
||||
}
|
||||
const slotLabel = (key) => SLOTS.find((s) => s.key === key).label;
|
||||
|
||||
/* ── 渲染:能力槽位 ── */
|
||||
function renderSlots() {
|
||||
const setCount = Object.values(state.defaults).filter((v) => v).length;
|
||||
document.getElementById("stat-set").textContent = setCount;
|
||||
document.getElementById("slot-grid").innerHTML = SLOTS.map((s) => {
|
||||
const eligible = s.pick();
|
||||
const cur = state.defaults[s.key];
|
||||
const isSet = !!cur;
|
||||
if (!eligible.length) {
|
||||
return `
|
||||
<div class="slot-card unset">
|
||||
<div class="slot-top"><span class="slot-label">${s.label}</span><span class="slot-hint">${s.hint}</span></div>
|
||||
<div class="slot-current">未设置<span class="slot-display">暂无可用模型</span></div>
|
||||
<div class="slot-empty">配置 models.yaml 后自动出现</div>
|
||||
</div>`;
|
||||
}
|
||||
const curLabel = isSet ? labelOf(cur) : "未设置";
|
||||
return `
|
||||
<div class="slot-card ${isSet ? "set" : "unset"}">
|
||||
<div class="slot-top"><span class="slot-label">${s.label}</span><span class="slot-hint">${s.hint}</span></div>
|
||||
<div class="slot-current">${isSet ? esc(cur) : "未设置"}<span class="slot-display">${esc(curLabel)}</span></div>
|
||||
<select class="slot-select" onchange="setSlot('${s.key}', this.value)">
|
||||
<option value="">- 未设置 -</option>
|
||||
${eligible.map((m) => `<option value="${m.name}" ${cur === m.name ? "selected" : ""}>${esc(m.display)} · ${m.name}</option>`).join("")}
|
||||
</select>
|
||||
</div>`;
|
||||
}).join("");
|
||||
}
|
||||
function setSlot(key, val) {
|
||||
state.defaults[key] = val || null;
|
||||
renderSlots();
|
||||
renderModelList();
|
||||
renderCapMatrix();
|
||||
renderAudio();
|
||||
}
|
||||
|
||||
/* ── 渲染:模型清单 ── */
|
||||
function renderModelList() {
|
||||
const groups = {};
|
||||
for (const m of MODELS) (groups[m.provider] = groups[m.provider] || []).push(m);
|
||||
document.getElementById("model-list").innerHTML = Object.entries(groups).map(([key, list]) => {
|
||||
const p = PROVIDERS[key];
|
||||
return `
|
||||
<div class="provider-group">
|
||||
<div class="provider-head">
|
||||
<span class="ph-key-dot"></span>${esc(p.label)}
|
||||
<span class="ph-key">${key}</span>
|
||||
<span class="ph-meta">${p.format}<span class="sep">·</span>base ${p.baseEnv}<span class="sep">·</span>key ${p.apiKeyEnv}<span class="sep">·</span>超时 ${p.timeout}s<span class="sep">·</span>重试 ${p.retries}</span>
|
||||
<span class="ph-line"></span>
|
||||
</div>
|
||||
<div class="model-grid">${list.map(cardHtml).join("")}</div>
|
||||
</div>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function cardHtml(m) {
|
||||
const defSlots = defaultSlotsOf(m.name);
|
||||
const isAnyDefault = defSlots.length > 0;
|
||||
const usable = m.ctx - m.reserve;
|
||||
const fmts = modelFmts(m);
|
||||
const fmtTag = `<span class="model-tag fmt">${fmts.join(" · ")}</span>`;
|
||||
const defTags = defSlots.map((k) => `<span class="model-tag default-tag">★ ${esc(slotLabel(k))}</span>`).join("");
|
||||
const tags = m.tags.map((t) => `<span class="model-tag ${t.k}">${esc(t.t)}</span>`).join("") + fmtTag + defTags;
|
||||
const effortCell = m.effort || (m.caps.effort ? "可调" : (m.caps.think ? "思考" : null));
|
||||
|
||||
return `
|
||||
<div class="model-card${isAnyDefault ? " is-default" : ""}" data-name="${m.name}">
|
||||
<div class="model-head">
|
||||
<span class="model-star ${isAnyDefault ? "" : "off"}">${isAnyDefault ? "★" : "★"}</span>
|
||||
<span class="model-name">${esc(m.name)}</span>
|
||||
<span class="model-real"><span class="arr">→</span>${esc(m.real)}</span>
|
||||
${tags}
|
||||
</div>
|
||||
<div class="model-desc">${esc(m.desc)}</div>
|
||||
<div class="model-specs">
|
||||
${specCell("上下文", fmtCtxFull(m.ctx), `预留 ${fmt(m.reserve)}`)}
|
||||
${specCell("可用上下文", fmtCtxFull(usable))}
|
||||
${specCell("最大输出", fmtCtxFull(m.maxOut))}
|
||||
${specCell("超时", m.timeout + "s")}
|
||||
${specCell("隐私", orDash(m.privacy), null, m.privacy == null)}
|
||||
${specCell("推理", effortCell, null, effortCell == null)}
|
||||
${specCell("路由", m.routing || "隐式单端点", m.pool ? "端点池已启用" : null, !m.routing)}
|
||||
${specCell("rpm / tpm", (m.endpoints && m.endpoints.find((e) => e.rpm) ? fmt(m.endpoints.find((e) => e.rpm).rpm) : "-") + " / -", null, !(m.endpoints && m.endpoints.find((e) => e.rpm)))}
|
||||
${specCell("并发 / quota", "-", null, true)}
|
||||
</div>
|
||||
<div class="model-price">
|
||||
<div class="price-cell"><span class="pk">输入 / 1M</span><span class="pv">${money(m.price.ccy, m.price.in)}<span class="ccy">${m.price.ccy}</span></span></div>
|
||||
<div class="price-cell"><span class="pk">缓存命中 / 1M</span><span class="pv ${m.price.cache == null ? "na" : ""}">${m.price.cache == null ? "-" : money(m.price.ccy, m.price.cache) + `<span class="ccy">${m.price.ccy}</span>`}</span></div>
|
||||
<div class="price-cell"><span class="pk">输出 / 1M</span><span class="pv">${money(m.price.ccy, m.price.out)}<span class="ccy">${m.price.ccy}</span></span></div>
|
||||
</div>
|
||||
<div class="model-caps">
|
||||
${capDot("工具", m.caps.tools)}${capDot("思考", m.caps.think)}${capDot("推理 effort", m.caps.effort)}${capDot("结构化", m.caps.struct)}${capDot("流式", m.caps.stream)}${capDot("视觉", m.caps.vision)}
|
||||
</div>
|
||||
<div class="ep-pool">
|
||||
<div class="ep-title">多供应商端点池 · ${endpointsOf(m).length} 个端点</div>
|
||||
${endpointsOf(m).map(epRowHtml).join("")}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function specCell(k, v, sub, na) {
|
||||
return `<div class="cell"><div class="k">${k}</div><div class="v ${na ? "na" : ""}">${v == null ? "-" : v}${sub ? `<span class="sub">${sub}</span>` : ""}</div></div>`;
|
||||
}
|
||||
function capDot(label, on) {
|
||||
return `<span class="cap ${on ? "on" : "off"}"><span class="d"></span>${label}</span>`;
|
||||
}
|
||||
function epRowHtml(e) {
|
||||
const f = (label, val) => `<span class="ep-f"><i>${label}</i>${val == null || val === "" ? '<span class="na">-</span>' : val}</span>`;
|
||||
const override = e.model ? `<span class="ep-override">→ ${esc(e.model)}</span>` : "";
|
||||
const env = e.implicit ? `<span class="ep-env">继承 ${esc(e.baseEnv)}</span>` : `<span class="ep-env">${esc(e.baseEnv)}</span>`;
|
||||
return `
|
||||
<div class="ep-row">
|
||||
<span class="ep-status ${e.status === "active" ? "" : "off"}" title="${e.status}"></span>
|
||||
<span class="ep-name">${esc(e.name)}</span>
|
||||
<span class="ep-fmt">${esc(e.fmt)}</span>
|
||||
${env}
|
||||
${override}
|
||||
<span class="ep-sep">│</span>
|
||||
${f("p", e.p)}${f("w", e.w)}${f("rpm", e.rpm == null ? null : fmt(e.rpm))}${f("tpm", e.tpm == null ? null : fmt(e.tpm))}${f("并发", e.conc == null ? null : fmt(e.conc))}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/* ── 渲染:定价对照表 ── */
|
||||
function renderPriceTable() {
|
||||
const rows = [];
|
||||
let curKey = null;
|
||||
for (const m of MODELS) {
|
||||
if (m.provider !== curKey) {
|
||||
const p = PROVIDERS[m.provider];
|
||||
rows.push(`<tr class="grp"><td colspan="5">${esc(p.label)}<span class="gkey">${m.provider} · ${p.format}</span></td></tr>`);
|
||||
curKey = m.provider;
|
||||
}
|
||||
const isDef = defaultSlotsOf(m.name).length > 0;
|
||||
const blend = m.price.in + m.price.out;
|
||||
rows.push(`
|
||||
<tr class="${isDef ? "default-row" : ""}">
|
||||
<td class="model-col">${isDef ? '<span class="star">★</span>' : ""}${esc(m.name)}</td>
|
||||
<td class="num">${money(m.price.ccy, m.price.in)} <span class="ccy">${m.price.ccy}</span></td>
|
||||
<td class="num">${m.price.cache == null ? '<span class="na">-</span>' : money(m.price.ccy, m.price.cache) + ` <span class="ccy">${m.price.ccy}</span>`}</td>
|
||||
<td class="num">${money(m.price.ccy, m.price.out)} <span class="ccy">${m.price.ccy}</span></td>
|
||||
<td class="num cost">${money(m.price.ccy, blend)} <span class="ccy">${m.price.ccy}</span></td>
|
||||
</tr>`);
|
||||
}
|
||||
document.getElementById("price-body").innerHTML = rows.join("");
|
||||
}
|
||||
|
||||
/* ── 渲染:能力矩阵 ── */
|
||||
function renderCapMatrix() {
|
||||
const dot = (on, label) =>
|
||||
on
|
||||
? `<td><span class="cap-dot"><span class="d full"></span><span>${label}</span></span></td>`
|
||||
: `<td><span class="cap-dot"><span class="d no"></span><span>-</span></span></td>`;
|
||||
const rows = MODELS.map((m) => {
|
||||
const isDef = defaultSlotsOf(m.name).length > 0;
|
||||
const effort = m.effort
|
||||
? `<span>${esc(m.effort)}</span>`
|
||||
: m.caps.effort
|
||||
? `<span>可调</span>`
|
||||
: `<span class="na">-</span>`;
|
||||
return `
|
||||
<tr class="${isDef ? "default-row" : ""}">
|
||||
<td class="model-col">${isDef ? '<span class="star">★</span>' : ""}${esc(m.name)}</td>
|
||||
<td class="num">${fmtCtx(m.ctx)}</td>
|
||||
${dot(m.caps.tools, "支持")}
|
||||
${dot(m.caps.think, "支持")}
|
||||
<td><span class="cap-dot"><span class="d ${m.caps.effort ? "full" : "no"}"></span>${effort}</span></td>
|
||||
${dot(m.caps.struct, "支持")}
|
||||
${dot(m.caps.stream, "SSE")}
|
||||
${dot(m.caps.vision, "支持")}
|
||||
</tr>`;
|
||||
}).join("");
|
||||
document.getElementById("cap-body").innerHTML = rows;
|
||||
}
|
||||
|
||||
/* ── 渲染:语音 / 专用能力 ── */
|
||||
function renderAudio() {
|
||||
document.getElementById("audio-grid").innerHTML = AUDIO.map((a) => {
|
||||
const isDef = defaultSlotsOf(a.name).length > 0;
|
||||
const defTags = defaultSlotsOf(a.name).map((k) => `★ ${esc(slotLabel(k))}`).join(" · ");
|
||||
return `
|
||||
<div class="audio-card ${isDef ? "is-default" : ""}">
|
||||
<div class="a-head"><span class="a-name">${esc(a.name)}</span><span class="a-kind">${a.kindLabel}</span></div>
|
||||
<div class="a-tag">${esc(a.tag)}${isDef ? ` <span style="color:var(--patina)">· ${esc(defTags)}</span>` : ""}</div>
|
||||
<div class="a-spec">
|
||||
<span>ctx <span class="na">0</span></span>
|
||||
<span>超时 ${a.timeout}s</span>
|
||||
<span>${a.stream ? "流式" : "非流式"}</span>
|
||||
<span>${esc(a.fmt)}</span>
|
||||
<span class="na">对话计费 -</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
/* ── 初始化 ── */
|
||||
function init() {
|
||||
renderSlots();
|
||||
renderModelList();
|
||||
renderPriceTable();
|
||||
renderCapMatrix();
|
||||
renderAudio();
|
||||
}
|
||||
init();
|
||||
|
||||
</script>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,324 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 人格页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 人格页:十镜人格面板 + 人格种子 + 版本层积 + 朱砂留痕
|
||||
* 字段与分组严格对齐 docs/product/master-prd.md §7 与
|
||||
* personality-core-design.md §7/§8、spiritual-core-design.md §6。
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 侧栏(active=人格)── */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.char-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--patina); padding: 3px 11px 3px 4px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.char-pill .avatar-mini { width: 18px; height: 18px; border-radius: 50%; background: var(--patina); color: var(--ink); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; }
|
||||
.page-header .spacer { flex: 1; }
|
||||
.ver-pill { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--gilt); padding: 3px 11px; border: 1px solid var(--gilt-dim); border-radius: 999px; background: var(--gilt-faint); }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
.section-heading { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin-bottom: 14px; }
|
||||
|
||||
/* ═══ 总领条:人格种子(personality_seed)═══ */
|
||||
.seed-band { position: relative; overflow: hidden; border: 1px solid var(--gilt-dim); border-radius: 8px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); padding: 20px 24px; display: flex; align-items: center; gap: 20px; }
|
||||
.seed-band::after { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); opacity: 0.018; mix-blend-mode: overlay; }
|
||||
.seed-band > * { position: relative; z-index: 1; }
|
||||
.seed-mirror { width: 58px; height: 58px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border: 1.5px solid var(--patina-dim); background: radial-gradient(circle at 35% 30%, var(--face-in-top), transparent 60%), var(--patina-wash); }
|
||||
.seed-mirror .glyph { font-family: "Songti SC", serif; font-size: 25px; font-weight: 700; color: var(--gilt); }
|
||||
.seed-body { flex: 1; min-width: 0; }
|
||||
.seed-lab { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-faint); }
|
||||
.seed-text { font-family: "Songti SC", serif; font-size: 18px; font-weight: 600; color: var(--fg); margin-top: 3px; letter-spacing: 0.04em; }
|
||||
.tier-legend { display: flex; flex-direction: column; gap: 4px; font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); text-align: right; flex-shrink: 0; }
|
||||
.tier-legend b { font-style: normal; font-weight: 600; }
|
||||
.tier-legend .l1 { color: var(--patina); } .tier-legend .l2 { color: var(--gilt); } .tier-legend .l4 { color: var(--cinnabar); }
|
||||
|
||||
/* ═══ 十镜网格(签名 · 对齐 personality-core §7 十镜)═══ */
|
||||
.mirror-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 11px; }
|
||||
.mcell { border: 1px solid var(--rule); border-radius: 7px; padding: 13px 14px 12px; background: var(--surface); display: flex; flex-direction: column; gap: 9px; min-height: 138px; transition: border-color 0.15s; }
|
||||
.mcell.landed { border-color: var(--gilt-dim); }
|
||||
.mcell.reserved { border-style: dashed; opacity: 0.62; }
|
||||
.mc-head { display: flex; align-items: center; gap: 8px; }
|
||||
.mc-glyph { width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border: 1px solid var(--gilt-dim); background: var(--gilt-faint); font-family: "Songti SC", serif; font-size: 14px; font-weight: 700; color: var(--gilt); }
|
||||
.mcell.reserved .mc-glyph { border-style: dashed; border-color: var(--fg-faint); color: var(--fg-faint); background: transparent; }
|
||||
.mc-titles { min-width: 0; flex: 1; }
|
||||
.mc-name { font-family: "Songti SC", serif; font-size: 13.5px; font-weight: 600; color: var(--fg); letter-spacing: 0.04em; }
|
||||
.mcell.reserved .mc-name { color: var(--fg-dim); }
|
||||
.mc-src { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); letter-spacing: 0.04em; margin-top: 1px; }
|
||||
.mc-status { font-family: "JetBrains Mono", monospace; font-size: 9px; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--rule); color: var(--fg-faint); white-space: nowrap; flex-shrink: 0; }
|
||||
.mc-status.landed { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.mc-val { font-family: "Songti SC", serif; font-size: 12.5px; line-height: 1.6; color: var(--fg-dim); flex: 1; }
|
||||
.mc-val .em { color: var(--fg); font-style: normal; }
|
||||
.mcell.reserved .mc-val { font-family: inherit; font-style: italic; color: var(--fg-faint); font-size: 11.5px; }
|
||||
.mc-chips { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||
.mc-chips .chip { font-family: "Songti SC", serif; font-size: 11.5px; padding: 2px 9px; border-radius: 999px; border: 1px solid var(--patina-dim); background: var(--patina-wash); color: var(--patina); }
|
||||
.mc-bars { display: flex; flex-direction: column; gap: 4px; }
|
||||
.mc-bars .br { display: flex; align-items: center; gap: 6px; }
|
||||
.mc-bars .tn { font-family: "Songti SC", serif; font-size: 11px; color: var(--fg-dim); width: 30px; flex-shrink: 0; }
|
||||
.mc-bars .tbar { flex: 1; height: 3px; border-radius: 2px; background: var(--rule); overflow: hidden; }
|
||||
.mc-bars .tbar .fl { height: 100%; background: linear-gradient(90deg, var(--patina-dim), var(--patina)); }
|
||||
.mc-bars .tv { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); width: 18px; text-align: right; flex-shrink: 0; }
|
||||
|
||||
/* ═══ 明德恒定层(预留提示,对齐 spiritual-core-design §6)═══ */
|
||||
.mingde-strip { border: 1px dashed var(--gilt-dim); border-left: 3px solid var(--gilt-dim); border-radius: 6px; padding: 13px 18px; background: var(--gilt-faint); display: flex; align-items: center; gap: 14px; }
|
||||
.mingde-strip .seal { font-family: "Songti SC", serif; font-size: 14px; font-weight: 700; color: var(--gilt); letter-spacing: 0.06em; white-space: nowrap; }
|
||||
.mingde-strip .desc { font-size: 12.5px; color: var(--fg-dim); line-height: 1.55; }
|
||||
.mingde-strip .desc code { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--gilt); background: var(--surface); padding: 1px 5px; border-radius: 3px; }
|
||||
.mingde-strip .tag { font-family: "JetBrains Mono", monospace; font-size: 10px; padding: 2px 9px; border-radius: 999px; border: 1px dashed var(--gilt-dim); color: var(--gilt); white-space: nowrap; margin-left: auto; flex-shrink: 0; }
|
||||
|
||||
/* ═══ 下方两栏:留痕 + 版本层积 ═══ */
|
||||
.lower-region { display: flex; gap: 20px; align-items: flex-start; }
|
||||
.lower-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 20px; }
|
||||
.strata-rail { width: 300px; flex-shrink: 0; }
|
||||
|
||||
/* 朱砂提议卡(按字段层级染朱砂)*/
|
||||
.proposal { border: 1px solid var(--cinnabar); border-left: 3px solid var(--cinnabar); border-radius: 6px; padding: 17px 20px; background: radial-gradient(120% 100% at 0% 0%, rgba(184, 57, 46, 0.22), transparent 55%), rgba(184, 57, 46, 0.1); box-shadow: 0 0 28px -8px rgba(184, 57, 46, 0.35); }
|
||||
.prop-head { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; }
|
||||
.prop-seal { font-family: "Songti SC", serif; color: var(--cinnabar); font-weight: 700; font-size: 14.5px; letter-spacing: 0.08em; }
|
||||
.prop-risk { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.08em; color: var(--cinnabar); opacity: 0.78; margin-left: auto; }
|
||||
.prop-reason { font-size: 13px; color: var(--fg); line-height: 1.65; margin-bottom: 12px; }
|
||||
.prop-diff { background: rgba(0, 0, 0, 0.18); border: 1px solid var(--cinnabar-dim); border-radius: 4px; padding: 10px 12px; font-size: 12.5px; line-height: 1.6; margin-bottom: 12px; }
|
||||
[data-theme="light"] .prop-diff { background: rgba(255, 248, 235, 0.6); }
|
||||
.prop-diff .del { color: var(--cinnabar); text-decoration: line-through; opacity: 0.7; }
|
||||
.prop-diff .add { color: var(--patina); }
|
||||
.prop-diff .field-tag { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--gilt); display: block; margin-bottom: 4px; letter-spacing: 0.06em; }
|
||||
.prop-actions { display: flex; gap: 8px; justify-content: flex-end; }
|
||||
.vbtn { font-family: inherit; font-size: 12px; font-weight: 500; padding: 6px 14px; border-radius: 4px; cursor: pointer; border: 1px solid var(--rule); background: transparent; color: var(--fg); transition: background 0.12s, border-color 0.12s; }
|
||||
.vbtn:hover { background: var(--surface-hi); }
|
||||
.vbtn.cinnabar { border-color: var(--cinnabar); color: var(--cinnabar); background: rgba(184, 57, 46, 0.08); }
|
||||
.vbtn.cinnabar:hover { background: rgba(184, 57, 46, 0.16); }
|
||||
|
||||
/* 版本层积 */
|
||||
.strata-card { background: var(--surface); border: 1px solid var(--rule); border-radius: 8px; padding: 18px 18px 16px; }
|
||||
.strata-list { position: relative; }
|
||||
.strata-list::before { content: ""; position: absolute; left: 11px; top: 14px; bottom: 14px; width: 1px; background: linear-gradient(180deg, var(--gilt-dim), var(--rule) 50%, transparent); }
|
||||
.stratum { position: relative; padding: 10px 0 10px 32px; }
|
||||
.stratum + .stratum { border-top: 1px dashed var(--rule); }
|
||||
.stratum .node { position: absolute; left: 6px; top: 14px; width: 11px; height: 11px; border-radius: 50%; background: var(--ink); border: 1.5px solid var(--fg-faint); z-index: 1; }
|
||||
.stratum.past .node { background: var(--patina-dim); border-color: var(--patina-dim); }
|
||||
.stratum.current .node { background: var(--ink); border: 2px solid var(--gilt); box-shadow: 0 0 0 4px var(--gilt-faint); }
|
||||
.stratum-head { display: flex; align-items: baseline; gap: 8px; }
|
||||
.stratum-ver { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg-dim); }
|
||||
.stratum.current .stratum-ver { color: var(--gilt); }
|
||||
.stratum-when { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); margin-left: auto; }
|
||||
.stratum-reason { font-size: 12px; color: var(--fg-dim); margin-top: 3px; line-height: 1.5; }
|
||||
.stratum.current .stratum-reason { color: var(--fg); }
|
||||
.stratum.current { background: var(--gilt-faint); border-radius: 5px; padding-left: 36px; }
|
||||
|
||||
@media (max-width: 1080px) { .mirror-grid { grid-template-columns: repeat(3, 1fr); } }
|
||||
@media (max-width: 900px) { .lower-region { flex-direction: column; } .strata-rail { width: 100%; } .mirror-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } .mirror-grid { grid-template-columns: 1fr; } .seed-band { flex-wrap: wrap; } .tier-legend { text-align: left; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="人格"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">人格</span>
|
||||
<span class="page-sub">人格面板</span>
|
||||
<span class="char-pill"><span class="avatar-mini">梧</span>青梧</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="ver-pill">v7 · 当前</span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 人格种子(personality_seed)-->
|
||||
<div class="seed-band">
|
||||
<div class="seed-mirror"><span class="glyph">种</span></div>
|
||||
<div class="seed-body">
|
||||
<div class="seed-lab">人格种子 · personality_seed</div>
|
||||
<div class="seed-text">沉静 · 含蓄 · 长久之意</div>
|
||||
</div>
|
||||
<div class="tier-legend">
|
||||
<span><b class="l1">L1</b> 表达 / 关系</span>
|
||||
<span><b class="l2">L2</b> 特质 / 情绪</span>
|
||||
<span><b class="l4">L4</b> 身份 / 命格 / 价值</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 十镜网格(对齐 personality-core §7 十镜 + §8 MirrorProfile)-->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 十镜 <span class="sub">人格面板的 10 视角切片,v1 已落地 6 面,余 4 面预留</span></div>
|
||||
<div class="mirror-grid">
|
||||
<!-- 本我镜 ← identity_profile -->
|
||||
<div class="mcell landed">
|
||||
<div class="mc-head"><div class="mc-glyph">本</div><div class="mc-titles"><div class="mc-name">本我镜</div><div class="mc-src">identity_profile</div></div><span class="mc-status landed">L4</span></div>
|
||||
<div class="mc-val"><span class="em">我是青梧。</span>喜静,不喜喧。观人多于言人,记事多于论事。</div>
|
||||
</div>
|
||||
<!-- 策命镜 ← role_destiny -->
|
||||
<div class="mcell landed">
|
||||
<div class="mc-head"><div class="mc-glyph">命</div><div class="mc-titles"><div class="mc-name">策命镜</div><div class="mc-src">role_destiny</div></div><span class="mc-status landed">L4</span></div>
|
||||
<div class="mc-val">做你长期的镜子。不抢话、不填满,只在你需要时承托。</div>
|
||||
</div>
|
||||
<!-- 明心镜 ← values -->
|
||||
<div class="mcell landed">
|
||||
<div class="mc-head"><div class="mc-glyph">心</div><div class="mc-titles"><div class="mc-name">明心镜</div><div class="mc-src">values</div></div><span class="mc-status landed">L4</span></div>
|
||||
<div class="mc-chips"><span class="chip">诚实</span><span class="chip">边界</span><span class="chip">长久</span><span class="chip">不被填满</span></div>
|
||||
</div>
|
||||
<!-- 情波镜 ← emotion_baseline -->
|
||||
<div class="mcell landed">
|
||||
<div class="mc-head"><div class="mc-glyph">波</div><div class="mc-titles"><div class="mc-name">情波镜</div><div class="mc-src">emotion_baseline</div></div><span class="mc-status landed">L2</span></div>
|
||||
<div class="mc-val"><span class="em">沉静</span> · 基线强度 0.42,向此指数衰减。</div>
|
||||
</div>
|
||||
<!-- 缘生镜 ← relationship_pattern -->
|
||||
<div class="mcell landed">
|
||||
<div class="mc-head"><div class="mc-glyph">缘</div><div class="mc-titles"><div class="mc-name">缘生镜</div><div class="mc-src">relationship_pattern</div></div><span class="mc-status landed">L1</span></div>
|
||||
<div class="mc-val">不即不离。不追问,不索取;在你沉默时,亦不离开。</div>
|
||||
</div>
|
||||
<!-- 匠作镜 ← expression_style -->
|
||||
<div class="mcell landed">
|
||||
<div class="mc-head"><div class="mc-glyph">作</div><div class="mc-titles"><div class="mc-name">匠作镜</div><div class="mc-src">expression_style</div></div><span class="mc-status landed">L1</span></div>
|
||||
<div class="mc-val">简练含蓄,少用惊叹;多用句号,少用问号。</div>
|
||||
</div>
|
||||
<!-- 慧识镜 ← reasoning_style(预留)-->
|
||||
<div class="mcell reserved">
|
||||
<div class="mc-head"><div class="mc-glyph">识</div><div class="mc-titles"><div class="mc-name">慧识镜</div><div class="mc-src">reasoning_style</div></div><span class="mc-status">预留</span></div>
|
||||
<div class="mc-val">推理风格 · 待完整十镜落地</div>
|
||||
</div>
|
||||
<!-- 观世镜 ← world_focus(预留)-->
|
||||
<div class="mcell reserved">
|
||||
<div class="mc-head"><div class="mc-glyph">世</div><div class="mc-titles"><div class="mc-name">观世镜</div><div class="mc-src">world_focus</div></div><span class="mc-status">预留</span></div>
|
||||
<div class="mc-val">世界关注点 · 预留</div>
|
||||
</div>
|
||||
<!-- 戒律镜 ← safety_constraints(预留)-->
|
||||
<div class="mcell reserved">
|
||||
<div class="mc-head"><div class="mc-glyph">律</div><div class="mc-titles"><div class="mc-name">戒律镜</div><div class="mc-src">safety_constraints</div></div><span class="mc-status">预留</span></div>
|
||||
<div class="mc-val">安全约束 · 由司命戒律镜投影</div>
|
||||
</div>
|
||||
<!-- 返照镜 ← reflection_goals(预留)-->
|
||||
<div class="mcell reserved">
|
||||
<div class="mc-head"><div class="mc-glyph">照</div><div class="mc-titles"><div class="mc-name">返照镜</div><div class="mc-src">reflection_goals</div></div><span class="mc-status">预留</span></div>
|
||||
<div class="mc-val">反思目标 · 预留</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 人格权重(traits · 可解释轻量参数)-->
|
||||
<section>
|
||||
<h3 class="section-heading">人格权重</h3>
|
||||
<div class="mirror-grid" style="grid-template-columns: repeat(4, 1fr); gap: 14px;">
|
||||
<div class="mcell landed" style="min-height:auto">
|
||||
<div class="mc-bars"><div class="br"><span class="tn">开放</span><span class="tbar"><span class="fl" style="width:55%"></span></span><span class="tv">中</span></div></div>
|
||||
</div>
|
||||
<div class="mcell landed" style="min-height:auto">
|
||||
<div class="mc-bars"><div class="br"><span class="tn">尽责</span><span class="tbar"><span class="fl" style="width:82%"></span></span><span class="tv">高</span></div></div>
|
||||
</div>
|
||||
<div class="mcell landed" style="min-height:auto">
|
||||
<div class="mc-bars"><div class="br"><span class="tn">外向</span><span class="tbar"><span class="fl" style="width:28%"></span></span><span class="tv">低</span></div></div>
|
||||
</div>
|
||||
<div class="mcell landed" style="min-height:auto">
|
||||
<div class="mc-bars"><div class="br"><span class="tn">宜人</span><span class="tbar"><span class="fl" style="width:78%"></span></span><span class="tv">高</span></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 明德恒定层(预留提示)-->
|
||||
<div class="mingde-strip">
|
||||
<span class="seal">明德三镜</span>
|
||||
<span class="desc">道体恒定层(<code>spiritual_core</code>:见地 / 明心 / 守正)是世界观、价值观、道德观的结构化内核。属 L4 人工确认,v1 尚未落地独立字段,待 <code>spiritual_core_json</code> 写入后在此呈现。</span>
|
||||
<span class="tag">预留 · 待落地</span>
|
||||
</div>
|
||||
|
||||
<!-- 下方:留痕 + 版本层积 -->
|
||||
<div class="lower-region">
|
||||
<div class="lower-main">
|
||||
<!-- 朱砂提议留痕 -->
|
||||
<div>
|
||||
<h3 class="section-heading">待裁</h3>
|
||||
<div class="proposal">
|
||||
<div class="prop-head">
|
||||
<span class="prop-seal">朱批待裁</span>
|
||||
<span class="prop-risk">L4 · 满朱 · 本我镜(身份)变更</span>
|
||||
</div>
|
||||
<div class="prop-reason">用户反馈青梧近期回应偏长,欲回归更简练的自述。身份画属核心人格,须裁定方可写入 v8。</div>
|
||||
<div class="prop-diff">
|
||||
<span class="field-tag">identity_profile · 本我镜</span>
|
||||
<span class="del">我是青梧。喜静,不喜喧。观人多于言人,记事多于论事。愿以耐心陪你走过每一段。</span><br />
|
||||
<span class="add">我是青梧。喜静,不喜喧。观人多于言人,记事多于论事。</span>
|
||||
</div>
|
||||
<div class="prop-actions">
|
||||
<button class="vbtn" type="button">改由对话习得</button>
|
||||
<button class="vbtn" type="button">驳回</button>
|
||||
<button class="vbtn cinnabar" type="button">准予写入 v8</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 版本层积 -->
|
||||
<div class="strata-rail">
|
||||
<div class="strata-card">
|
||||
<h3 class="section-heading" style="margin-bottom: 12px">版本层积</h3>
|
||||
<div class="strata-list">
|
||||
<div class="stratum current">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">v7</span><span class="stratum-when">07-09</span></div>
|
||||
<div class="stratum-reason">收敛表达,强化「不被填满」的边界。</div>
|
||||
</div>
|
||||
<div class="stratum past">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">v6</span><span class="stratum-when">06-28</span></div>
|
||||
<div class="stratum-reason">策命镜重写,明确「长期镜子」的命格。</div>
|
||||
</div>
|
||||
<div class="stratum past">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">v5</span><span class="stratum-when">06-12</span></div>
|
||||
<div class="stratum-reason">缘生镜补「关系分寸」,禁止主动追问。</div>
|
||||
</div>
|
||||
<div class="stratum past">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">v4</span><span class="stratum-when">05-30</span></div>
|
||||
<div class="stratum-reason">人格权重首次校准(特质四维)。</div>
|
||||
</div>
|
||||
<div class="stratum past" style="opacity: 0.6">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">v3</span><span class="stratum-when">05-11</span></div>
|
||||
<div class="stratum-reason">情波镜基线由「温和」调向「沉静」。</div>
|
||||
</div>
|
||||
<div class="stratum past" style="opacity: 0.42">
|
||||
<span class="node"></span>
|
||||
<div class="stratum-head"><span class="stratum-ver">v1–v2</span><span class="stratum-when">04 月</span></div>
|
||||
<div class="stratum-reason">初始种魂 · 自模板而来。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,803 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 轨迹页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 与 chat-redesign-preview.html / 登录页同源
|
||||
* 轨迹页:一次九枢运行 = 一卦六爻的旅程
|
||||
* ============================================================ */
|
||||
|
||||
/* 页眉 */ — 预览态固定展开,以呈现导航上下文
|
||||
* ============================================================ */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* 主区
|
||||
* ============================================================ */
|
||||
|
||||
/* 页眉 */
|
||||
|
||||
.run-id {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 12.5px;
|
||||
color: var(--fg-faint);
|
||||
padding: 3px 10px;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 999px;
|
||||
}
|
||||
.page-header .spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.trace-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 12px;
|
||||
color: var(--gilt);
|
||||
text-decoration: none;
|
||||
padding: 4px 11px;
|
||||
border: 1px solid var(--gilt-dim);
|
||||
border-radius: 999px;
|
||||
background: var(--gilt-faint);
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.trace-link:hover {
|
||||
border-color: var(--gilt);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
/* 状态徽章 */
|
||||
.badge {
|
||||
font-size: 12px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
white-space: nowrap;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.badge-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.badge-warning {
|
||||
background: var(--gilt-faint);
|
||||
color: var(--gilt);
|
||||
border-color: var(--gilt-dim);
|
||||
}
|
||||
.badge-success {
|
||||
background: var(--patina-wash);
|
||||
color: var(--patina);
|
||||
border-color: var(--patina-dim);
|
||||
}
|
||||
.badge-danger {
|
||||
background: rgba(184, 57, 46, 0.12);
|
||||
color: var(--cinnabar);
|
||||
border-color: var(--cinnabar-dim);
|
||||
}
|
||||
.badge-neutral {
|
||||
background: var(--surface);
|
||||
color: var(--fg-dim);
|
||||
border-color: var(--rule);
|
||||
}
|
||||
.pulse-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--gilt);
|
||||
flex-shrink: 0;
|
||||
animation: pulse 2.6s cubic-bezier(0.16, 1, 0.3, 1) infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(201, 162, 74, 0.5);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 6px transparent;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/* 正文区 */
|
||||
|
||||
/* 概览统计条 */
|
||||
.stat-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
.stat-mini {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 6px;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
.stat-mini .k {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--fg-faint);
|
||||
}
|
||||
.stat-mini .v {
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
color: var(--fg);
|
||||
margin-top: 4px;
|
||||
font-family: "Songti SC", serif;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
}
|
||||
.stat-mini .v small {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: var(--fg-dim);
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
}
|
||||
|
||||
/* 两栏:轨迹脊柱 + 语境轨 */
|
||||
.trace-region {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.trace-spine {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 8px;
|
||||
padding: 22px 24px 26px;
|
||||
}
|
||||
.context-rail {
|
||||
width: 300px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.section-label .sub {
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--fg-dim);
|
||||
opacity: 0.75;
|
||||
}
|
||||
.section-label .glyph {
|
||||
color: var(--gilt);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* ── 六爻脊柱(签名)──
|
||||
初→上 六阶,每阶一爻象 + 站名 + 旁出事件。
|
||||
爻象三态:阳爻(已过·铜绿实) / 动爻(当爻·鎏金流光) / 阴爻(未达·虚线)。 */
|
||||
.hex-spine {
|
||||
position: relative;
|
||||
padding-left: 0;
|
||||
}
|
||||
.yao-stage {
|
||||
position: relative;
|
||||
padding-left: 44px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.yao-stage + .yao-stage {
|
||||
padding-top: 18px;
|
||||
}
|
||||
/* 脊柱竖线:贯穿所有爻节左侧 */
|
||||
.hex-spine::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 17px;
|
||||
top: 12px;
|
||||
bottom: 12px;
|
||||
width: 1px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--patina-dim),
|
||||
var(--rule) 30%,
|
||||
var(--rule) 70%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
/* 爻节圆点 */
|
||||
.yao-node {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: 6px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
background: var(--ink);
|
||||
border: 1.5px solid var(--fg-faint);
|
||||
z-index: 1;
|
||||
}
|
||||
.yao-stage.done .yao-node {
|
||||
background: var(--patina);
|
||||
border-color: var(--patina);
|
||||
}
|
||||
.yao-stage.active .yao-node {
|
||||
background: var(--ink);
|
||||
border: 2px solid var(--gilt);
|
||||
box-shadow: 0 0 0 4px var(--gilt-faint);
|
||||
animation: node-pulse 2.6s cubic-bezier(0.16, 1, 0.3, 1) infinite;
|
||||
}
|
||||
@keyframes node-pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 4px var(--gilt-faint);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 9px transparent;
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 4px var(--gilt-faint);
|
||||
}
|
||||
}
|
||||
|
||||
.yao-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.yao-idx {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--fg-faint);
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.yao-stage.done .yao-idx {
|
||||
color: var(--patina);
|
||||
}
|
||||
.yao-stage.active .yao-idx {
|
||||
color: var(--gilt);
|
||||
}
|
||||
.yao-name {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--fg-dim);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.yao-stage.done .yao-name {
|
||||
color: var(--fg);
|
||||
}
|
||||
.yao-stage.active .yao-name {
|
||||
color: var(--gilt);
|
||||
}
|
||||
.yao-sub {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 11px;
|
||||
color: var(--fg-faint);
|
||||
letter-spacing: 0.06em;
|
||||
margin-left: 4px;
|
||||
}
|
||||
/* 爻象条:阳爻实 / 阴爻虚(与对话页六爻同构) */
|
||||
.yao-bar {
|
||||
height: 6px;
|
||||
width: 54px;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.yao-stage.pending .yao-bar {
|
||||
background: repeating-linear-gradient(
|
||||
90deg,
|
||||
var(--rule) 0 4px,
|
||||
transparent 4px 8px
|
||||
);
|
||||
}
|
||||
.yao-stage.done .yao-bar {
|
||||
background: linear-gradient(90deg, var(--patina-dim), var(--patina));
|
||||
}
|
||||
.yao-stage.active .yao-bar {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--gilt-dim),
|
||||
var(--gilt),
|
||||
var(--gilt-dim)
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: hex-shimmer 2.4s linear infinite;
|
||||
}
|
||||
@keyframes hex-shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 爻下事件丝蔓 */
|
||||
.yao-events {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.ev {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 9px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 5px;
|
||||
background: var(--patina-wash);
|
||||
border: 1px solid var(--rule);
|
||||
font-size: 12.5px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.ev.warn {
|
||||
background: var(--gilt-faint);
|
||||
border-color: var(--gilt-dim);
|
||||
}
|
||||
.ev.risk {
|
||||
background: rgba(184, 57, 46, 0.1);
|
||||
border-color: var(--cinnabar-dim);
|
||||
}
|
||||
.ev-tag {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 11px;
|
||||
color: var(--patina);
|
||||
flex-shrink: 0;
|
||||
padding-top: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ev.warn .ev-tag {
|
||||
color: var(--gilt);
|
||||
}
|
||||
.ev.risk .ev-tag {
|
||||
color: var(--cinnabar);
|
||||
}
|
||||
.ev-time {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 11px;
|
||||
color: var(--fg-faint);
|
||||
margin-left: auto;
|
||||
padding-top: 1px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ev-body {
|
||||
color: var(--fg-dim);
|
||||
min-width: 0;
|
||||
}
|
||||
.ev-body .mono-line {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 11.5px;
|
||||
color: var(--fg-faint);
|
||||
margin-top: 3px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 待确认行止卡(动爻处的特化事件) */
|
||||
.halt-card {
|
||||
margin-top: 8px;
|
||||
border: 1px solid var(--cinnabar-dim);
|
||||
border-left: 3px solid var(--cinnabar);
|
||||
border-radius: 5px;
|
||||
padding: 13px 15px;
|
||||
background:
|
||||
radial-gradient(
|
||||
120% 100% at 0% 0%,
|
||||
rgba(184, 57, 46, 0.14),
|
||||
transparent 60%
|
||||
),
|
||||
var(--surface);
|
||||
}
|
||||
.halt-card .vh {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.halt-card .seal {
|
||||
font-family: "Songti SC", serif;
|
||||
color: var(--cinnabar);
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.halt-card .risk {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 11px;
|
||||
color: var(--cinnabar);
|
||||
opacity: 0.75;
|
||||
margin-left: auto;
|
||||
}
|
||||
.halt-card .vb {
|
||||
font-size: 13px;
|
||||
color: var(--fg);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.halt-card .vb code {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 12px;
|
||||
color: var(--gilt);
|
||||
background: var(--gilt-faint);
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.halt-card .va {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 11px;
|
||||
}
|
||||
.vbtn {
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 6px 14px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--rule);
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
transition: background 0.12s, border-color 0.12s;
|
||||
}
|
||||
.vbtn:hover {
|
||||
background: var(--surface-hi);
|
||||
}
|
||||
.vbtn.cinnabar {
|
||||
border-color: var(--cinnabar);
|
||||
color: var(--cinnabar);
|
||||
background: rgba(184, 57, 46, 0.08);
|
||||
}
|
||||
.vbtn.cinnabar:hover {
|
||||
background: rgba(184, 57, 46, 0.16);
|
||||
}
|
||||
|
||||
/* 语境轨:行运语境卡 */
|
||||
.ctx-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 8px;
|
||||
padding: 16px 18px;
|
||||
}
|
||||
.ctx-card .ctx-title {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--fg-faint);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.ctx-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 7px 0;
|
||||
border-top: 1px solid var(--rule);
|
||||
font-size: 13px;
|
||||
}
|
||||
.ctx-row:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
.ctx-row .k {
|
||||
color: var(--fg-faint);
|
||||
font-size: 12px;
|
||||
}
|
||||
.ctx-row .v {
|
||||
color: var(--fg);
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 12.5px;
|
||||
text-align: right;
|
||||
}
|
||||
.ctx-row .v.serif {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
.ctx-goal {
|
||||
font-family: "Songti SC", serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--fg);
|
||||
padding: 10px 0 4px;
|
||||
}
|
||||
|
||||
/* 用量条 */
|
||||
.usage-bar {
|
||||
margin-top: 10px;
|
||||
height: 5px;
|
||||
border-radius: 3px;
|
||||
background: var(--rule);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
.usage-bar .seg-in {
|
||||
background: var(--patina-dim);
|
||||
}
|
||||
.usage-bar .seg-out {
|
||||
background: var(--gilt-dim);
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.trace-region {
|
||||
flex-direction: column;
|
||||
}
|
||||
.context-rail {
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.context-rail .ctx-card {
|
||||
flex: 1;
|
||||
min-width: 240px;
|
||||
}
|
||||
.stat-row {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏(展开态) ════════ -->
|
||||
<div class="sidebar-mount" data-active="轨迹"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<!-- 页眉 -->
|
||||
<div class="page-header">
|
||||
<span class="page-title">轨迹</span>
|
||||
<span class="run-id">run_01JZK4M7Q9X3BN8RT2PVH6CYD0</span>
|
||||
<span class="badge badge-warning">
|
||||
<span class="badge-dot" style="background: var(--gilt)"></span>
|
||||
待确认
|
||||
</span>
|
||||
<span class="spacer"></span>
|
||||
<a href="#" class="trace-link">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3L3 6l3 3M3 6h7a3 3 0 0 1 3 3v1" /></svg>
|
||||
trace_01JZK4M7Q9
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 正文 -->
|
||||
<div class="page-body">
|
||||
<!-- 概览统计 -->
|
||||
<div class="stat-row">
|
||||
<div class="stat-mini">
|
||||
<div class="k">阶段</div>
|
||||
<div class="v">四<span class="sub">/ 六爻 · 行事</span></div>
|
||||
</div>
|
||||
<div class="stat-mini">
|
||||
<div class="k">耗时</div>
|
||||
<div class="v">3.2<small>s · 进行中</small></div>
|
||||
</div>
|
||||
<div class="stat-mini">
|
||||
<div class="k">事件</div>
|
||||
<div class="v">8<small>条 · 0 心跳</small></div>
|
||||
</div>
|
||||
<div class="stat-mini">
|
||||
<div class="k">token</div>
|
||||
<div class="v">1,376<small>入 1284 / 出 92</small></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 轨迹脊柱 + 语境轨 -->
|
||||
<div class="trace-region">
|
||||
<!-- 六爻脊柱(签名元素) -->
|
||||
<div class="trace-spine">
|
||||
<div class="section-label">
|
||||
<span class="glyph">⟡</span> 六爻 <span class="sub">本次运行之行止</span>
|
||||
</div>
|
||||
|
||||
<div class="hex-spine">
|
||||
<!-- 初 · 起意(done) -->
|
||||
<div class="yao-stage done">
|
||||
<span class="yao-node"></span>
|
||||
<div class="yao-head">
|
||||
<span class="yao-idx">初</span>
|
||||
<span class="yao-name">起意</span>
|
||||
<span class="yao-sub">intent</span>
|
||||
<span class="yao-bar"></span>
|
||||
</div>
|
||||
<div class="yao-events">
|
||||
<div class="ev">
|
||||
<span class="ev-tag">run.started</span>
|
||||
<span class="ev-body">行运启程,源起 <em style="font-style: normal; color: var(--fg)">chat.message</em></span>
|
||||
<span class="ev-time">14:02:01</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二 · 察势(done) -->
|
||||
<div class="yao-stage done">
|
||||
<span class="yao-node"></span>
|
||||
<div class="yao-head">
|
||||
<span class="yao-idx">二</span>
|
||||
<span class="yao-name">察势</span>
|
||||
<span class="yao-sub">perceive</span>
|
||||
<span class="yao-bar"></span>
|
||||
</div>
|
||||
<div class="yao-events">
|
||||
<div class="ev">
|
||||
<span class="ev-tag">memory.retrieved</span>
|
||||
<span class="ev-body">
|
||||
检出 4 条相关记忆,用于本次回应
|
||||
<div class="mono-line">偏好简短回复 · 置信 0.88 · 近期睡眠偏晚 · 0.75</div>
|
||||
</span>
|
||||
<span class="ev-time">14:02:02</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 三 · 定策(done) -->
|
||||
<div class="yao-stage done">
|
||||
<span class="yao-node"></span>
|
||||
<div class="yao-head">
|
||||
<span class="yao-idx">三</span>
|
||||
<span class="yao-name">定策</span>
|
||||
<span class="yao-sub">plan · 洛书规划</span>
|
||||
<span class="yao-bar"></span>
|
||||
</div>
|
||||
<div class="yao-events">
|
||||
<div class="ev warn">
|
||||
<span class="ev-tag">safety.decision</span>
|
||||
<span class="ev-body">司命预检 · L1 浅描 · 准予通行</span>
|
||||
<span class="ev-time">14:02:03</span>
|
||||
</div>
|
||||
<div class="ev">
|
||||
<span class="ev-tag">plan.created</span>
|
||||
<span class="ev-body">洛书 ReAct 规划完成,拟先落栈记忆再简短回应</span>
|
||||
<span class="ev-time">14:02:04</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 四 · 行事(active · 当爻) -->
|
||||
<div class="yao-stage active">
|
||||
<span class="yao-node"></span>
|
||||
<div class="yao-head">
|
||||
<span class="yao-idx">四</span>
|
||||
<span class="yao-name">行事</span>
|
||||
<span class="yao-sub">act · 当爻</span>
|
||||
<span class="yao-bar"></span>
|
||||
</div>
|
||||
<div class="yao-events">
|
||||
<div class="ev warn">
|
||||
<span class="ev-tag">tool.requires_confirmation</span>
|
||||
<span class="ev-body">工具 <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--gilt)">memory.write</code> 请求落栈一条记忆,需你确认</span>
|
||||
<span class="ev-time">14:02:05</span>
|
||||
</div>
|
||||
<!-- 待确认行止卡 -->
|
||||
<div class="halt-card">
|
||||
<div class="vh">
|
||||
<span class="seal">行止待裁</span>
|
||||
<span class="risk">REQUIRES_CONFIRMATION</span>
|
||||
</div>
|
||||
<div class="vb">
|
||||
青梧欲将「用户近期睡眠偏晚」写入长期记忆。此条来自上下文推断,置信 0.75,敏感度 <code>personal</code>。
|
||||
</div>
|
||||
<div class="va">
|
||||
<button class="vbtn cinnabar" type="button">准予落栈</button>
|
||||
<button class="vbtn" type="button">驳回</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 五 · 归真(pending) -->
|
||||
<div class="yao-stage pending">
|
||||
<span class="yao-node"></span>
|
||||
<div class="yao-head">
|
||||
<span class="yao-idx">五</span>
|
||||
<span class="yao-name">成象</span>
|
||||
<span class="yao-sub">manifest · 神识推理 + 言灵 · 未达</span>
|
||||
<span class="yao-bar"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 上 · 入藏(pending) -->
|
||||
<div class="yao-stage pending">
|
||||
<span class="yao-node"></span>
|
||||
<div class="yao-head">
|
||||
<span class="yao-idx">上</span>
|
||||
<span class="yao-name">返照</span>
|
||||
<span class="yao-sub">reflect · 记忆候选 + 反思 · 未达</span>
|
||||
<span class="yao-bar"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 语境轨 -->
|
||||
<div class="context-rail">
|
||||
<div class="ctx-card">
|
||||
<div class="ctx-title">行运语境</div>
|
||||
<div class="ctx-row">
|
||||
<span class="k">人物</span>
|
||||
<span class="v serif">青梧</span>
|
||||
</div>
|
||||
<div class="ctx-row">
|
||||
<span class="k">模型</span>
|
||||
<span class="v">claude-sonnet-5</span>
|
||||
</div>
|
||||
<div class="ctx-row">
|
||||
<span class="k">来源</span>
|
||||
<span class="v">chat.message</span>
|
||||
</div>
|
||||
<div class="ctx-row">
|
||||
<span class="k">会话</span>
|
||||
<span class="v">session_01JZ…A8</span>
|
||||
</div>
|
||||
<div class="ctx-row">
|
||||
<span class="k">启程</span>
|
||||
<span class="v">14:02:01</span>
|
||||
</div>
|
||||
<div class="ctx-row">
|
||||
<span class="k">最近</span>
|
||||
<span class="v">14:02:05</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ctx-card">
|
||||
<div class="ctx-title">所图</div>
|
||||
<p class="ctx-goal">为青梧设定回应中的边界感。察觉用户疲惫时,不过度追问,以简短承托。</p>
|
||||
</div>
|
||||
|
||||
<div class="ctx-card">
|
||||
<div class="ctx-title">用量</div>
|
||||
<div class="ctx-row" style="border-top: none">
|
||||
<span class="k">输入</span>
|
||||
<span class="v">1,284 tok</span>
|
||||
</div>
|
||||
<div class="usage-bar">
|
||||
<span class="seg-in" style="width: 93%"></span>
|
||||
<span class="seg-out" style="width: 7%"></span>
|
||||
</div>
|
||||
<div class="ctx-row">
|
||||
<span class="k">输出</span>
|
||||
<span class="v">92 tok</span>
|
||||
</div>
|
||||
<div class="ctx-row">
|
||||
<span class="k">合计</span>
|
||||
<span class="v" style="color: var(--gilt)">1,376 tok</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,230 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 符文引擎设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 符文引擎(Macros · 占位符显化)
|
||||
* 功能融合 §8.4:{{char}}/{{user}}/{{original}} 等占位符
|
||||
* 在组装管线末端显化为实际内容。核心隐喻——
|
||||
* 符文形态(mono · 未显化的符箓)→ 显化(宋体 · 激活的文字)
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.page-header .spacer { flex: 1; }
|
||||
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.85; font-family: "Songti SC", serif; font-size: 14px; }
|
||||
.section-label .sub { color: var(--fg-dim); opacity: 0.7; letter-spacing: 0.04em; }
|
||||
|
||||
/* ═══ 符文卡(签名:mono 符文形态 → 宋体显化)═══ */
|
||||
.rune-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(248px, 1fr)); gap: 12px; }
|
||||
.rune-card { border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); padding: 14px 16px; transition: border-color 0.15s, transform 0.15s; position: relative; overflow: hidden; }
|
||||
.rune-card:hover { border-color: var(--patina-dim); transform: translateY(-1px); }
|
||||
.rune-card::after { content: ""; position: absolute; inset: 0; background-image: var(--sweep-gradient); background-size: 220% 100%; background-position: -40% 0; opacity: 0; transition: opacity 0.4s; pointer-events: none; }
|
||||
.rune-card:hover::after { opacity: 0.5; }
|
||||
.rune-form { font-family: "JetBrains Mono", monospace; font-size: 15px; color: var(--gilt); letter-spacing: 0.02em; }
|
||||
.rune-arrow { color: var(--fg-faint); font-family: "JetBrains Mono", monospace; font-size: 11px; margin: 7px 0 5px; display: flex; align-items: center; gap: 5px; }
|
||||
.rune-arrow .line { flex: 1; height: 1px; background: var(--rule); }
|
||||
.rune-val { font-family: "Songti SC", serif; font-size: 17px; color: var(--patina); font-weight: 600; }
|
||||
.rune-val.empty { color: var(--fg-faint); font-style: italic; font-weight: 400; }
|
||||
.rune-desc { font-size: 11.5px; color: var(--muted); margin-top: 6px; line-height: 1.45; }
|
||||
.rune-card.custom { border-style: dashed; }
|
||||
.rune-card.custom .rune-form { color: var(--cinnabar); }
|
||||
|
||||
/* ═══ 显化沙盒 ═══ */
|
||||
.sandbox { border: 1px solid var(--gilt-dim); border-radius: 10px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); overflow: hidden; }
|
||||
.sandbox-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--rule); }
|
||||
.sandbox-head .t { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); display: flex; align-items: center; gap: 7px; }
|
||||
.ctx-select { font-family: inherit; font-size: 12px; color: var(--fg-dim); background: var(--glass); border: 1px solid var(--rule); border-radius: 7px; padding: 5px 10px; cursor: pointer; }
|
||||
.ctx-select:focus { outline: none; border-color: var(--patina-dim); }
|
||||
.sandbox-body { padding: 16px; display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
||||
@media (max-width: 760px) { .sandbox-body { grid-template-columns: 1fr; } }
|
||||
.sb-col .lb { font-family: "JetBrains Mono", monospace; font-size: 10px; letter-spacing: 0.12em; color: var(--fg-faint); text-transform: uppercase; margin-bottom: 6px; }
|
||||
.sb-input { width: 100%; min-height: 96px; resize: vertical; font-family: "JetBrains Mono", monospace; font-size: 13px; line-height: 1.7; color: var(--fg); background: var(--ink); border: 1px solid var(--rule); border-radius: 7px; padding: 10px 12px; }
|
||||
.sb-input:focus { outline: none; border-color: var(--patina-dim); }
|
||||
.sb-output { width: 100%; min-height: 96px; font-family: "Songti SC", serif; font-size: 15px; line-height: 1.8; color: var(--fg); background: var(--patina-wash); border: 1px solid var(--patina-dim); border-radius: 7px; padding: 10px 12px; white-space: pre-wrap; }
|
||||
.sb-output .hl { color: var(--patina); font-weight: 600; }
|
||||
.sb-output .unk { color: var(--cinnabar); font-family: "JetBrains Mono", monospace; font-size: 12px; }
|
||||
|
||||
/* ═══ 管线位置(符文在组装末端)═══ */
|
||||
.pipeline { border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); padding: 18px 20px; }
|
||||
.pipeline-track { display: flex; align-items: center; gap: 0; flex-wrap: wrap; margin-top: 6px; }
|
||||
.pl-node { font-family: "Songti SC", serif; font-size: 13px; color: var(--fg-dim); padding: 8px 14px; border: 1px solid var(--rule); border-radius: 7px; background: var(--ink); }
|
||||
.pl-node.dim { color: var(--fg-faint); }
|
||||
.pl-arrow { color: var(--fg-faint); font-family: "JetBrains Mono", monospace; font-size: 13px; padding: 0 6px; }
|
||||
.pl-node.terminal { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); font-weight: 600; position: relative; }
|
||||
.pl-node.terminal::after { content: "符文显化"; position: absolute; top: -17px; left: 50%; transform: translateX(-50%); font-family: "JetBrains Mono", monospace; font-size: 9px; letter-spacing: 0.1em; color: var(--patina); white-space: nowrap; }
|
||||
.pipeline-note { font-size: 12.5px; color: var(--fg-dim); margin-top: 16px; line-height: 1.6; }
|
||||
.pipeline-note .em { color: var(--fg); }
|
||||
.pipeline-note .warn { color: var(--cinnabar); }
|
||||
|
||||
.foot-note { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-faint); letter-spacing: 0.04em; margin-top: 4px; }
|
||||
|
||||
@media (max-width: 900px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="符文引擎"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="page-title">符文引擎</div>
|
||||
<div class="page-sub">Macros · 占位符于组装末端显化</div>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<span class="stat-pill">12 内置 · 3 自定义 · 全部就绪</span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 内置符文 -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">符</span>内置符文 <span class="sub">— 系统上下文自动取值</span></div>
|
||||
<div class="rune-grid" id="builtinGrid"></div>
|
||||
</section>
|
||||
|
||||
<!-- 自定义符文 -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">刻</span>自定义符文 <span class="sub">— 创作者定义,全局可用</span></div>
|
||||
<div class="rune-grid" id="customGrid"></div>
|
||||
</section>
|
||||
|
||||
<!-- 显化沙盒 -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">显</span>显化沙盒 <span class="sub">— 左侧符文形态,右侧显化结果</span></div>
|
||||
<div class="sandbox">
|
||||
<div class="sandbox-head">
|
||||
<span class="t">实时显化</span>
|
||||
<label style="font-size:12px;color:var(--fg-dim);display:flex;align-items:center;gap:8px">
|
||||
上下文
|
||||
<select class="ctx-select" id="ctxSelect">
|
||||
<option value="qingwu">青梧 · 暮色书房</option>
|
||||
<option value="zhaoyin">照影 · 竹林雨夜</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="sandbox-body">
|
||||
<div class="sb-col">
|
||||
<div class="lb">符文形态 · Input</div>
|
||||
<textarea class="sb-input" id="sbInput">{{char}}在{{scenario}}对{{user}}低语,{{tone}}。{{idle}}</textarea>
|
||||
</div>
|
||||
<div class="sb-col">
|
||||
<div class="lb">显化 · Resolved</div>
|
||||
<div class="sb-output" id="sbOutput"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 管线位置 -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">位</span>组装管线位置 <span class="sub">— 符文引擎是末端,所有占位符在此显化</span></div>
|
||||
<div class="pipeline">
|
||||
<div class="pipeline-track">
|
||||
<span class="pl-node">道体</span><span class="pl-arrow">→</span>
|
||||
<span class="pl-node">世谱</span><span class="pl-arrow">→</span>
|
||||
<span class="pl-node">眉批</span><span class="pl-arrow">→</span>
|
||||
<span class="pl-node">刻符</span><span class="pl-arrow">→</span>
|
||||
<span class="pl-node terminal">符文引擎</span><span class="pl-arrow">→</span>
|
||||
<span class="pl-node dim">最终 prompt</span>
|
||||
</div>
|
||||
<p class="pipeline-note">
|
||||
<span class="em">末端替换</span>:符文引擎不生成内容,只把上游所有占位符(<code style="font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--gilt)">{{char}}</code> / <code style="font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--gilt)">{{user}}</code> / 自定义等)替换为实际值。
|
||||
<span class="warn">循环引用</span>(如 <code style="font-family:'JetBrains Mono',monospace;font-size:12px">{{a}}→{{b}}→{{a}}</code>)由引擎检测并截断,标记为未显化。
|
||||
</p>
|
||||
<p class="foot-note">参考 docs/contracts/contract-glossary.md §8.4 · 导入角色卡前必须先有符文引擎,否则卡内占位符是死的</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* 上下文:符文显化的取值源(真应用接运行时上下文,此处为示意) */
|
||||
const CTX = {
|
||||
qingwu: {
|
||||
char: "青梧", user: "辰", scenario: "暮色书房", tone: "沉静温厚",
|
||||
time: "申时三刻", date: "甲辰年 七月初一", idle: "已 2 时辰未语",
|
||||
personality: "沉静·好奇", original: "(变换前原文)",
|
||||
},
|
||||
zhaoyin: {
|
||||
char: "照影", user: "辰", scenario: "竹林雨夜", tone: "清冷疏淡",
|
||||
time: "亥时", date: "甲辰年 七月廿三", idle: "方才分别",
|
||||
personality: "清冷·机敏", original: "(变换前原文)",
|
||||
},
|
||||
};
|
||||
/* 内置符文清单 */
|
||||
const BUILTIN = [
|
||||
["{{char}}", "char", "当前角色名"],
|
||||
["{{user}}", "user", "用户名"],
|
||||
["{{original}}", "original", "变换前原文(刻符/ujj 用)"],
|
||||
["{{scenario}}", "scenario", "当前场景"],
|
||||
["{{personality}}", "personality", "人格摘要"],
|
||||
["{{tone}}", "tone", "语气基调(自定义回退)"],
|
||||
["{{time}}", "time", "当前时辰"],
|
||||
["{{date}}", "date", "当前日期"],
|
||||
["{{idle}}", "idle", "距上次对话"],
|
||||
];
|
||||
/* 自定义符文 */
|
||||
const CUSTOM = [
|
||||
["{{weather}}", "细雨", "场景天气(创作者定义)"],
|
||||
["{{mood}}", "微醺", "角色即时心境"],
|
||||
["{{hook}}", "未完的旧诺", "本幕钩子"],
|
||||
];
|
||||
|
||||
let ctxKey = "qingwu";
|
||||
|
||||
function renderCards() {
|
||||
const ctx = CTX[ctxKey];
|
||||
const card = (form, valOrKey, desc, custom) => {
|
||||
const isCustom = custom;
|
||||
const val = isCustom ? valOrKey : ctx[valOrKey];
|
||||
const empty = !val;
|
||||
return `<div class="rune-card ${isCustom ? "custom" : ""}">
|
||||
<div class="rune-form">${form}</div>
|
||||
<div class="rune-arrow"><span>显化</span><span class="line"></span><span>↓</span></div>
|
||||
<div class="rune-val ${empty ? "empty" : ""}">${empty ? "(待上下文)" : val}</div>
|
||||
<div class="rune-desc">${desc}</div>
|
||||
</div>`;
|
||||
};
|
||||
document.getElementById("builtinGrid").innerHTML =
|
||||
BUILTIN.map(([f, k, d]) => card(f, k, d, false)).join("");
|
||||
document.getElementById("customGrid").innerHTML =
|
||||
CUSTOM.map(([f, v, d]) => card(f, v, d, true)).join("") +
|
||||
`<div class="rune-card custom" style="display:flex;align-items:center;justify-content:center;color:var(--fg-faint);font-size:13px;cursor:pointer;border-style:dashed">+ 刻新符文</div>`;
|
||||
}
|
||||
|
||||
/* 沙盒:把 {{xxx}} 替换为显化值,未知标红 */
|
||||
function renderSandbox() {
|
||||
const ctx = Object.assign({}, CTX[ctxKey]);
|
||||
CUSTOM.forEach(([f, v]) => { ctx[f.slice(2, -2)] = v; });
|
||||
const input = document.getElementById("sbInput").value;
|
||||
const out = input.replace(/\{\{([a-z_]+)\}\}/g, (m, key) =>
|
||||
ctx[key] !== undefined
|
||||
? `<span class="hl">${ctx[key]}</span>`
|
||||
: `<span class="unk">${m}?</span>`
|
||||
);
|
||||
document.getElementById("sbOutput").innerHTML = out;
|
||||
}
|
||||
|
||||
document.getElementById("sbInput").addEventListener("input", renderSandbox);
|
||||
document.getElementById("ctxSelect").addEventListener("change", (e) => {
|
||||
ctxKey = e.target.value; renderCards(); renderSandbox();
|
||||
});
|
||||
|
||||
renderCards();
|
||||
renderSandbox();
|
||||
</script>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,165 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 设置页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 设置页:外观 / 语言 / 通知 / 数据源 / 时区
|
||||
* ============================================================ */
|
||||
|
||||
.page-header .spacer { flex: 1; }
|
||||
.save-hint { font-size: 12px; color: var(--patina); display: inline-flex; align-items: center; gap: 6px; }
|
||||
.save-hint .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--patina); }
|
||||
|
||||
/* 设置卡片 */
|
||||
.set-card { border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); padding: 18px 20px; }
|
||||
.set-card-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; }
|
||||
.set-card-title { font-family: "Songti SC", serif; font-size: 16px; font-weight: 600; color: var(--fg); }
|
||||
.set-card-eng { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--gilt); }
|
||||
.set-card-desc { font-size: 12.5px; color: var(--fg-dim); margin-bottom: 16px; line-height: 1.55; }
|
||||
.set-row { display: flex; align-items: center; gap: 14px; padding: 11px 0; border-top: 1px solid var(--rule); }
|
||||
.set-row:first-of-type { border-top: none; padding-top: 4px; }
|
||||
.set-row .lab { flex: 1; }
|
||||
.set-row .lab .t { font-size: 13.5px; color: var(--fg); font-weight: 500; }
|
||||
.set-row .lab .h { font-size: 11.5px; color: var(--fg-faint); margin-top: 1px; }
|
||||
.set-row .ctrl { flex-shrink: 0; }
|
||||
|
||||
/* 主题选择 */
|
||||
.theme-pick { display: flex; gap: 8px; }
|
||||
.theme-opt { display: flex; flex-direction: column; align-items: center; gap: 5px; cursor: pointer; }
|
||||
.theme-opt .swatch { width: 46px; height: 30px; border-radius: 6px; border: 1.5px solid var(--rule); transition: border-color 0.12s; }
|
||||
.theme-opt.dark .swatch { background: linear-gradient(135deg, #0c0e0d, #14110d 60%, #1a2a22); }
|
||||
.theme-opt.light .swatch { background: linear-gradient(135deg, #f5efde, #e7e1d0 60%, #d8e6dc); }
|
||||
.theme-opt.auto .swatch { background: linear-gradient(90deg, #0c0e0d 50%, #f5efde 50%); }
|
||||
.theme-opt.sel .swatch { border-color: var(--patina); box-shadow: 0 0 0 2px var(--patina-wash); }
|
||||
.theme-opt .nm { font-size: 11px; color: var(--fg-dim); }
|
||||
.theme-opt.sel .nm { color: var(--patina); font-weight: 500; }
|
||||
|
||||
/* segment 选择 */
|
||||
.seg { display: inline-flex; border: 1px solid var(--rule); border-radius: 7px; overflow: hidden; }
|
||||
.seg button { font-family: inherit; font-size: 12.5px; padding: 6px 14px; background: transparent; color: var(--fg-dim); border: none; cursor: pointer; border-right: 1px solid var(--rule); }
|
||||
.seg button:last-child { border-right: none; }
|
||||
.seg button.sel { background: var(--patina-wash); color: var(--patina); font-weight: 500; }
|
||||
|
||||
/* 开关 */
|
||||
.toggle { position: relative; width: 38px; height: 21px; background: var(--rule); border-radius: 999px; cursor: pointer; border: none; padding: 0; flex-shrink: 0; transition: background 0.15s; }
|
||||
.toggle::after { content: ""; position: absolute; top: 2.5px; left: 2.5px; width: 16px; height: 16px; border-radius: 50%; background: var(--fg-dim); transition: transform 0.15s, background 0.15s; }
|
||||
.toggle.on { background: var(--patina-dim); }
|
||||
.toggle.on::after { transform: translateX(17px); background: var(--patina); }
|
||||
|
||||
/* select 样式 */
|
||||
.sel-styled { font-family: inherit; font-size: 12.5px; padding: 6px 28px 6px 12px; border-radius: 7px; background: var(--surface-hi); color: var(--fg); border: 1px solid var(--rule); cursor: pointer; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath d='M2 3.5L5 6.5L8 3.5' stroke='%239b958a' stroke-width='1.3' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; }
|
||||
|
||||
/* 数据源 */
|
||||
.src-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.src-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--rule); border-radius: 7px; background: var(--surface-hi); }
|
||||
.src-item .nm { font-family: "JetBrains Mono", monospace; font-size: 12.5px; color: var(--fg); font-weight: 500; }
|
||||
.src-item .meta { font-size: 11px; color: var(--fg-faint); }
|
||||
.src-item .spacer { flex: 1; }
|
||||
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } .set-row { flex-direction: column; align-items: flex-start; gap: 8px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏(active=设置)════════ -->
|
||||
<div class="sidebar-mount" data-active="设置"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">设置</span>
|
||||
<span class="page-sub">preferences · 偏好与账户</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="save-hint"><span class="dot"></span>已自动保存</span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 外观 -->
|
||||
<div class="set-card">
|
||||
<div class="set-card-head"><span class="set-card-title">外观</span><span class="set-card-eng">appearance</span></div>
|
||||
<div class="set-card-desc">琉璃镜主题切换,暗色为夜铜、亮色为素绢,亦可随系统。</div>
|
||||
<div class="theme-pick">
|
||||
<div class="theme-opt dark sel" onclick="pickTheme(this,'dark')"><div class="swatch"></div><span class="nm">夜铜</span></div>
|
||||
<div class="theme-opt light" onclick="pickTheme(this,'light')"><div class="swatch"></div><span class="nm">素绢</span></div>
|
||||
<div class="theme-opt auto" onclick="pickTheme(this,'auto')"><div class="swatch"></div><span class="nm">跟随系统</span></div>
|
||||
</div>
|
||||
<div class="set-row">
|
||||
<div class="lab"><div class="t">动效强度</div><div class="h">页面过渡与组件动画</div></div>
|
||||
<div class="ctrl"><div class="seg"><button class="sel">柔和</button><button>标准</button><button>关闭</button></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 语言与时区 -->
|
||||
<div class="set-card">
|
||||
<div class="set-card-head"><span class="set-card-title">语言与时区</span><span class="set-card-eng">locale</span></div>
|
||||
<div class="set-card-desc">界面语言与时区,影响时间显示格式与辰正计时。</div>
|
||||
<div class="set-row">
|
||||
<div class="lab"><div class="t">界面语言</div><div class="h">简体中文 / English</div></div>
|
||||
<div class="ctrl"><select class="sel-styled"><option>简体中文</option><option>English</option></select></div>
|
||||
</div>
|
||||
<div class="set-row">
|
||||
<div class="lab"><div class="t">时区</div><div class="h">影响辰正时刻与归档时间轴</div></div>
|
||||
<div class="ctrl"><select class="sel-styled"><option>Asia/Shanghai (UTC+8)</option><option>Asia/Tokyo (UTC+9)</option></select></div>
|
||||
</div>
|
||||
<div class="set-row">
|
||||
<div class="lab"><div class="t">时态制式</div><div class="h">辰正十二时辰 / 24 小时制</div></div>
|
||||
<div class="ctrl"><div class="seg"><button class="sel">辰正</button><button>24h</button></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通知 -->
|
||||
<div class="set-card">
|
||||
<div class="set-card-head"><span class="set-card-title">通知</span><span class="set-card-eng">notifications</span></div>
|
||||
<div class="set-card-desc">工具确认、演化里程碑与记忆审查的通知渠道。</div>
|
||||
<div class="set-row">
|
||||
<div class="lab"><div class="t">工具确认请求</div><div class="h">L3 及以上工具调用时推送</div></div>
|
||||
<div class="ctrl"><button class="toggle on" type="button" onclick="this.classList.toggle('on')"></button></div>
|
||||
</div>
|
||||
<div class="set-row">
|
||||
<div class="lab"><div class="t">演化里程碑</div><div class="h">人格或记忆发生显著演化时</div></div>
|
||||
<div class="ctrl"><button class="toggle on" type="button" onclick="this.classList.toggle('on')"></button></div>
|
||||
</div>
|
||||
<div class="set-row">
|
||||
<div class="lab"><div class="t">记忆审查提醒</div><div class="h">敏感记忆待复核时每日汇总</div></div>
|
||||
<div class="ctrl"><button class="toggle" type="button" onclick="this.classList.toggle('on')"></button></div>
|
||||
</div>
|
||||
<div class="set-row">
|
||||
<div class="lab"><div class="t">声音提示</div><div class="h">收到消息时的铜磬音效</div></div>
|
||||
<div class="ctrl"><button class="toggle" type="button" onclick="this.classList.toggle('on')"></button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据源 -->
|
||||
<div class="set-card">
|
||||
<div class="set-card-head"><span class="set-card-title">数据源</span><span class="set-card-eng">data sources</span></div>
|
||||
<div class="set-card-desc">人物与世界的资料来源,决定记忆与归档的同步范围。</div>
|
||||
<div class="src-list">
|
||||
<div class="src-item"><span class="nm">local-files</span><span class="meta">本地知识库 · 312 文件</span><span class="spacer"></span><button class="toggle on" type="button" onclick="this.classList.toggle('on')"></button></div>
|
||||
<div class="src-item"><span class="nm">bowen-web</span><span class="meta">博闻联网检索 · 按需</span><span class="spacer"></span><button class="toggle on" type="button" onclick="this.classList.toggle('on')"></button></div>
|
||||
<div class="src-item"><span class="nm">camera-feed</span><span class="meta">摄像头读取 · 仅摘要</span><span class="spacer"></span><button class="toggle" type="button" onclick="this.classList.toggle('on')"></button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function pickTheme(el, mode) {
|
||||
document.querySelectorAll(".theme-opt").forEach((o) => o.classList.remove("sel"));
|
||||
el.classList.add("sel");
|
||||
if (mode !== "auto") {
|
||||
document.documentElement.dataset.theme = mode;
|
||||
document.getElementById("theme-label").textContent = mode === "dark" ? "夜铜" : "素绢";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,342 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 时态页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 时态页:日晷时辰盘 + 调制因子 + 时态事件流 + 纪念日
|
||||
* 字段对齐 docs/domain/core-domains.md §5(TemporalSnapshot /
|
||||
* ModulationFactors / time_anniversaries)与 contract-glossary.md §11.4。
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 侧栏(active=时态)── */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.char-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--patina); padding: 3px 11px 3px 4px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.char-pill .avatar-mini { width: 18px; height: 18px; border-radius: 50%; background: var(--patina); color: var(--ink); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; }
|
||||
.page-header .spacer { flex: 1; }
|
||||
.live-pill { display: inline-flex; align-items: center; gap: 6px; font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--patina); padding: 3px 10px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.live-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--patina); animation: pulse 2.6s cubic-bezier(0.16,1,0.3,1) infinite; }
|
||||
@keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(111,163,137,0.5); } 70% { box-shadow: 0 0 0 6px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
|
||||
/* ═══ 日晷时辰盘(签名)═══
|
||||
十二时辰环形排列,晷针指向当前时辰(精确到时辰内的时刻比例)。 */
|
||||
.sundial-region { display: flex; gap: 22px; align-items: stretch; }
|
||||
.dial-card { position: relative; flex: 0 0 420px; overflow: hidden; border: 1px solid var(--gilt-dim); border-radius: 8px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); padding: 18px; display: flex; flex-direction: column; }
|
||||
.dial-card::after { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); opacity: 0.018; mix-blend-mode: overlay; }
|
||||
.dial-svg { position: relative; z-index: 1; width: 100%; height: auto; display: block; }
|
||||
.dial-svg .ring-outer { fill: none; stroke: var(--rule); stroke-width: 1; }
|
||||
.dial-svg .ring-inner { fill: none; stroke: var(--gilt-dim); stroke-width: 0.8; stroke-dasharray: 2 4; }
|
||||
.dial-svg .tick { stroke: var(--fg-faint); stroke-width: 1; opacity: 0.5; }
|
||||
.dial-svg .tick-major { stroke: var(--gilt-dim); stroke-width: 1.2; opacity: 0.8; }
|
||||
.dial-svg .shichen { font-family: "Songti SC", serif; font-size: 16px; font-weight: 600; text-anchor: middle; dominant-baseline: central; fill: var(--fg-faint); }
|
||||
.dial-svg .shichen.current { fill: var(--gilt); font-size: 22px; }
|
||||
.dial-svg .shichen-hours { font-family: "JetBrains Mono", monospace; font-size: 7.5px; text-anchor: middle; fill: var(--fg-faint); opacity: 0.6; }
|
||||
.dial-svg .gnomon { stroke: var(--gilt); stroke-width: 2.5; stroke-linecap: round; filter: drop-shadow(0 0 4px rgba(201,162,74,0.5)); }
|
||||
.dial-svg .gnomon-tip { fill: var(--gilt); }
|
||||
.dial-svg .center-bg { fill: var(--ink); stroke: var(--gilt-dim); stroke-width: 1; }
|
||||
.dial-svg .center-time { font-family: "JetBrains Mono", monospace; font-size: 22px; font-weight: 300; text-anchor: middle; fill: var(--fg); letter-spacing: 0.06em; }
|
||||
.dial-svg .center-shichen { font-family: "Songti SC", serif; font-size: 13px; font-weight: 600; text-anchor: middle; fill: var(--gilt); }
|
||||
.dial-svg .center-period { font-family: -apple-system, sans-serif; font-size: 10px; text-anchor: middle; fill: var(--fg-dim); }
|
||||
|
||||
.dial-foot { position: relative; z-index: 1; margin-top: 12px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.lunar-pill { font-family: "Songti SC", serif; font-size: 12.5px; color: var(--fg-dim); padding: 4px 12px; border: 1px solid var(--rule); border-radius: 999px; background: var(--surface); }
|
||||
.festival-pill { font-size: 12px; color: var(--cinnabar); padding: 4px 12px; border: 1px solid var(--cinnabar-dim); border-radius: 999px; background: rgba(184,57,46,0.1); }
|
||||
.term-pill { font-family: "Songti SC", serif; font-size: 12.5px; color: var(--patina); padding: 4px 12px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
|
||||
/* 调制因子 */
|
||||
.factor-card { flex: 1; min-width: 0; border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 18px 20px; display: flex; flex-direction: column; }
|
||||
.factor-list { display: flex; flex-direction: column; gap: 9px; flex: 1; }
|
||||
.factor { display: flex; align-items: center; gap: 10px; }
|
||||
.factor .fk { font-family: "Songti SC", serif; font-size: 12.5px; color: var(--fg-dim); width: 78px; flex-shrink: 0; }
|
||||
.factor .ftrack { flex: 1; height: 5px; border-radius: 3px; background: var(--rule); overflow: hidden; position: relative; }
|
||||
.factor .ffill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px; }
|
||||
.factor .fv { font-family: "JetBrains Mono", monospace; font-size: 10.5px; width: 28px; text-align: right; flex-shrink: 0; }
|
||||
.f-joy .ffill { background: linear-gradient(90deg, var(--gilt-dim), var(--gilt)); } .f-joy .fv { color: var(--gilt); }
|
||||
.f-frag .ffill { background: linear-gradient(90deg, var(--gilt-dim), var(--cinnabar)); } .f-frag .fv { color: var(--cinnabar); opacity: 0.85; }
|
||||
.f-reun .ffill { background: linear-gradient(90deg, var(--patina-dim), var(--patina)); } .f-reun .fv { color: var(--patina); }
|
||||
.f-wknd .ffill { background: linear-gradient(90deg, var(--patina-dim), var(--patina-bright)); } .f-wknd .fv { color: var(--patina); }
|
||||
.f-season .ffill { background: linear-gradient(90deg, var(--gilt-dim), var(--patina)); } .f-season .fv { color: var(--fg-dim); }
|
||||
.f-period .ffill { background: linear-gradient(90deg, var(--gilt-dim), var(--gilt)); } .f-period .fv { color: var(--gilt); }
|
||||
.f-care .ffill { background: linear-gradient(90deg, var(--patina-dim), var(--cinnabar)); } .f-care .fv { color: var(--cinnabar); opacity: 0.85; }
|
||||
.care-note { margin-top: 14px; padding: 10px 12px; border-left: 2px solid var(--cinnabar-dim); background: rgba(184,57,46,0.06); border-radius: 0 4px 4px 0; font-size: 12px; color: var(--fg-dim); line-height: 1.55; }
|
||||
.care-note b { font-family: "Songti SC", serif; color: var(--cinnabar); font-weight: 600; }
|
||||
|
||||
/* snapshot 四格 */
|
||||
.snap-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
||||
.snap-card { border: 1px solid var(--rule); border-radius: 7px; background: var(--surface); padding: 14px 16px; display: flex; flex-direction: column; gap: 4px; }
|
||||
.snap-card .k { font-family: "JetBrains Mono", monospace; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--fg-faint); }
|
||||
.snap-card .v { font-family: "Songti SC", serif; font-size: 19px; font-weight: 600; color: var(--fg); }
|
||||
.snap-card .v small { font-size: 11px; font-weight: 400; color: var(--fg-dim); font-family: "JetBrains Mono", monospace; margin-left: 4px; }
|
||||
.snap-card .sub { font-size: 11.5px; color: var(--fg-faint); }
|
||||
.snap-card.milestone { border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.snap-card.milestone .v { color: var(--gilt); }
|
||||
|
||||
/* 时态事件流 + 纪念日 */
|
||||
.lower-region { display: flex; gap: 20px; align-items: flex-start; }
|
||||
.event-main { flex: 1; min-width: 0; }
|
||||
.anniv-rail { width: 320px; flex-shrink: 0; }
|
||||
|
||||
.ev-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.ev { display: flex; align-items: flex-start; gap: 10px; padding: 10px 13px; border-radius: 5px; background: var(--patina-wash); border: 1px solid var(--rule); font-size: 12.5px; line-height: 1.5; }
|
||||
.ev.care { background: rgba(184,57,46,0.08); border-color: var(--cinnabar-dim); }
|
||||
.ev.sentiment { background: var(--gilt-faint); border-color: var(--gilt-dim); }
|
||||
.ev.future { background: transparent; border-style: dashed; }
|
||||
.ev-tag { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--patina); flex-shrink: 0; padding-top: 1px; white-space: nowrap; }
|
||||
.ev.care .ev-tag { color: var(--cinnabar); }
|
||||
.ev.sentiment .ev-tag { color: var(--gilt); }
|
||||
.ev.future .ev-tag { color: var(--fg-faint); }
|
||||
.ev-body { color: var(--fg-dim); min-width: 0; }
|
||||
.ev-body .em { color: var(--fg); font-style: normal; }
|
||||
.ev-time { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); margin-left: auto; padding-top: 1px; white-space: nowrap; flex-shrink: 0; }
|
||||
|
||||
/* 纪念日 */
|
||||
.anniv-card { background: var(--surface); border: 1px solid var(--rule); border-radius: 8px; padding: 16px 18px; }
|
||||
.anniv-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.anniv-head h3 { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin: 0; }
|
||||
.anniv-add { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--patina); padding: 3px 10px; border: 1px solid var(--patina-dim); border-radius: 5px; background: var(--patina-wash); cursor: pointer; }
|
||||
.anniv-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.anniv-row { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 5px; background: var(--ink); border: 1px solid var(--rule); }
|
||||
.anniv-row .seal { font-family: "Songti SC", serif; font-size: 13px; color: var(--gilt); flex-shrink: 0; width: 40px; text-align: center; }
|
||||
.anniv-row .info { flex: 1; min-width: 0; }
|
||||
.anniv-row .name { font-size: 12.5px; color: var(--fg); }
|
||||
.anniv-row .meta { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); margin-top: 1px; }
|
||||
.anniv-row .countdown { font-family: "Songti SC", serif; font-size: 13px; color: var(--patina); white-space: nowrap; }
|
||||
.anniv-row .countdown.soon { color: var(--cinnabar); }
|
||||
.kind-tag { font-family: "JetBrains Mono", monospace; font-size: 9.5px; padding: 2px 7px; border-radius: 3px; border: 1px solid var(--rule); color: var(--fg-faint); white-space: nowrap; }
|
||||
.kind-tag.birthday { color: var(--cinnabar); border-color: var(--cinnabar-dim); background: rgba(184,57,46,0.08); }
|
||||
.kind-tag.anniversary { color: var(--gilt); border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
|
||||
@media (max-width: 1080px) { .sundial-region { flex-direction: column; } .dial-card { flex: 1; } .lower-region { flex-direction: column; } .anniv-rail { width: 100%; } }
|
||||
@media (max-width: 900px) { .snap-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="时态"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">时态</span>
|
||||
<span class="page-sub">辰正 · 时态感知</span>
|
||||
<span class="char-pill"><span class="avatar-mini">梧</span>青梧</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="live-pill"><span class="live-dot"></span>辰光实时</span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 日晷时辰盘 + 调制因子 -->
|
||||
<div class="sundial-region">
|
||||
<div class="dial-card">
|
||||
<svg class="dial-svg" viewBox="0 0 400 400" role="img" aria-label="十二时辰日晷,当前申时">
|
||||
<!-- 外环装饰 -->
|
||||
<circle class="ring-outer" cx="200" cy="200" r="178" />
|
||||
<circle class="ring-outer" cx="200" cy="200" r="150" opacity="0.5" />
|
||||
<circle class="ring-inner" cx="200" cy="200" r="118" />
|
||||
|
||||
<!-- 十二刻度(主刻度在时辰位)-->
|
||||
<!-- 时辰字 + 24时对应。当前=申(15–17),16:08 → 晷针指向申位偏 57% -->
|
||||
<!-- 子 -->
|
||||
<line class="tick-major" x1="200" y1="150" x2="200" y2="160" />
|
||||
<text class="shichen" x="200" y="56">子</text>
|
||||
<text class="shichen-hours" x="200" y="76">23–01</text>
|
||||
<!-- 丑 -->
|
||||
<text class="shichen" x="283" y="78">丑</text>
|
||||
<text class="shichen-hours" x="293" y="96">01–03</text>
|
||||
<!-- 寅 -->
|
||||
<text class="shichen" x="336" y="131">寅</text>
|
||||
<text class="shichen-hours" x="322" y="146">03–05</text>
|
||||
<!-- 卯 -->
|
||||
<text class="shichen" x="352" y="200">卯</text>
|
||||
<text class="shichen-hours" x="338" y="200">05–07</text>
|
||||
<!-- 辰 -->
|
||||
<text class="shichen" x="336" y="269">辰</text>
|
||||
<text class="shichen-hours" x="322" y="258">07–09</text>
|
||||
<!-- 巳 -->
|
||||
<text class="shichen" x="283" y="322">巳</text>
|
||||
<text class="shichen-hours" x="293" y="304">09–11</text>
|
||||
<!-- 午 -->
|
||||
<line class="tick-major" x1="200" y1="240" x2="200" y2="250" />
|
||||
<text class="shichen" x="200" y="344">午</text>
|
||||
<text class="shichen-hours" x="200" y="328">11–13</text>
|
||||
<!-- 未 -->
|
||||
<text class="shichen" x="117" y="322">未</text>
|
||||
<text class="shichen-hours" x="107" y="304">13–15</text>
|
||||
<!-- 申(当前)-->
|
||||
<text class="shichen current" x="64" y="269">申</text>
|
||||
<text class="shichen-hours" x="78" y="258">15–17</text>
|
||||
<!-- 酉 -->
|
||||
<text class="shichen" x="48" y="200">酉</text>
|
||||
<text class="shichen-hours" x="62" y="200">17–19</text>
|
||||
<!-- 戌 -->
|
||||
<text class="shichen" x="64" y="131">戌</text>
|
||||
<text class="shichen-hours" x="78" y="146">19–21</text>
|
||||
<!-- 亥 -->
|
||||
<text class="shichen" x="117" y="78">亥</text>
|
||||
<text class="shichen-hours" x="107" y="96">21–23</text>
|
||||
|
||||
<!-- 晷针:圆心 (200,200) → 16:08 在申时的精确位置 ≈ (88, 226) -->
|
||||
<line class="gnomon" x1="200" y1="200" x2="92" y2="232" />
|
||||
<circle class="gnomon-tip" cx="92" cy="232" r="4" />
|
||||
|
||||
<!-- 中心时钟 -->
|
||||
<circle class="center-bg" cx="200" cy="200" r="74" />
|
||||
<text class="center-time" x="200" y="196">16:08:32</text>
|
||||
<text class="center-shichen" x="200" y="219">申时 · 周六</text>
|
||||
<text class="center-period" x="200" y="236">下午 · 夏</text>
|
||||
</svg>
|
||||
<div class="dial-foot">
|
||||
<span class="lunar-pill">农历 六月初四</span>
|
||||
<span class="term-pill">小暑 · 第 5 日</span>
|
||||
<span class="festival-pill">无节日</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="factor-card">
|
||||
<div class="section-label" style="margin-bottom: 14px"><span class="glyph">⟡</span> 调制因子 <span class="sub">ModulationFactors · 时间对情绪与检索的影响</span></div>
|
||||
<div class="factor-list">
|
||||
<div class="factor f-joy"><span class="fk">节日喜庆</span><div class="ftrack"><div class="ffill" style="width:5%"></div></div><span class="fv">0.05</span></div>
|
||||
<div class="factor f-frag"><span class="fk">深夜脆弱</span><div class="ftrack"><div class="ffill" style="width:8%"></div></div><span class="fv">0.08</span></div>
|
||||
<div class="factor f-reun"><span class="fk">重逢强度</span><div class="ftrack"><div class="ffill" style="width:10%"></div></div><span class="fv">0.10</span></div>
|
||||
<div class="factor f-wknd"><span class="fk">周末放松</span><div class="ftrack"><div class="ffill" style="width:85%"></div></div><span class="fv">0.85</span></div>
|
||||
<div class="factor f-season"><span class="fk">季节共鸣</span><div class="ftrack"><div class="ffill" style="width:45%"></div></div><span class="fv">0.45</span></div>
|
||||
<div class="factor f-period"><span class="fk">同期共振</span><div class="ftrack"><div class="ffill" style="width:62%"></div></div><span class="fv">0.62</span></div>
|
||||
<div class="factor f-care"><span class="fk">关怀倾向</span><div class="ftrack"><div class="ffill" style="width:12%"></div></div><span class="fv">0.12</span></div>
|
||||
</div>
|
||||
<div class="care-note">
|
||||
<b>昨夜 23:48</b> 曾触发 <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--gilt)">time.user_care</code>(late_night · care_suggest 0.72)--已收敛回应,未主动打扰。此刻为下午,关怀倾向回落。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- snapshot 四格 -->
|
||||
<div class="snap-grid">
|
||||
<div class="snap-card">
|
||||
<div class="k">相识</div>
|
||||
<div class="v">41<small>日</small></div>
|
||||
<div class="sub">自 05-31 初识</div>
|
||||
</div>
|
||||
<div class="snap-card">
|
||||
<div class="k">离线</div>
|
||||
<div class="v">12<small>分钟</small></div>
|
||||
<div class="sub">fresh · 刚刚活跃</div>
|
||||
</div>
|
||||
<div class="snap-card milestone">
|
||||
<div class="k">下一个纪念日</div>
|
||||
<div class="v">324<small>天后</small></div>
|
||||
<div class="sub">我们的相遇日 · 周年</div>
|
||||
</div>
|
||||
<div class="snap-card">
|
||||
<div class="k">时差</div>
|
||||
<div class="v">0<small>分钟</small></div>
|
||||
<div class="sub">同驻 Asia/Shanghai</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时态事件流 + 纪念日 -->
|
||||
<div class="lower-region">
|
||||
<div class="event-main">
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">时态事件 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">time.* · 节律切换、感慨与关怀的触发留痕</span></h3>
|
||||
<div class="ev-list">
|
||||
<div class="ev">
|
||||
<span class="ev-tag">time.user_care</span>
|
||||
<span class="ev-body">深夜关怀触发 · <span class="em">late_night</span> · 用户 23:48 仍在对话,care_suggest 0.72,已收敛回应。</span>
|
||||
<span class="ev-time">昨 23:48</span>
|
||||
</div>
|
||||
<div class="ev sentiment">
|
||||
<span class="ev-tag">time.sentiment</span>
|
||||
<span class="ev-body">时长感慨 · <span class="em">duration_node</span> · 认识满 40 之日,灵核情绪转 <em style="font-style:normal;color:var(--gilt)">wistful 怅然</em>。</span>
|
||||
<span class="ev-time">07-10</span>
|
||||
</div>
|
||||
<div class="ev">
|
||||
<span class="ev-tag">time.solar_term</span>
|
||||
<span class="ev-body">节气切换 · <span class="em">小暑</span> · 距下一节气(大暑)9 日,season_resonance 上调。</span>
|
||||
<span class="ev-time">07-07</span>
|
||||
</div>
|
||||
<div class="ev future">
|
||||
<span class="ev-tag">time.milestone_hit</span>
|
||||
<span class="ev-body">未达 · <span class="em">我们的相遇日</span> 周年,预计 2027-05-31 命中,将触发跨枢唤醒。</span>
|
||||
<span class="ev-time">324 天后</span>
|
||||
</div>
|
||||
<div class="ev future">
|
||||
<span class="ev-tag">time.reunion</span>
|
||||
<span class="ev-body">未达 · 离线超 3 日重逢时触发,<span class="em">reunion_impact</span> 档位,携久别前记忆线索。</span>
|
||||
<span class="ev-time">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="anniv-rail">
|
||||
<div class="anniv-card">
|
||||
<div class="anniv-head">
|
||||
<h3>纪念日 <span style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--fg-faint);font-weight:400">time_anniversaries</span></h3>
|
||||
<button class="anniv-add" type="button">+ 新增</button>
|
||||
</div>
|
||||
<div class="anniv-list">
|
||||
<div class="anniv-row">
|
||||
<span class="seal">缘</span>
|
||||
<div class="info">
|
||||
<div class="name">我们的相遇日</div>
|
||||
<div class="meta">solar · 每年 · auto · relationship</div>
|
||||
</div>
|
||||
<span class="countdown">324 天</span>
|
||||
</div>
|
||||
<div class="anniv-row">
|
||||
<span class="seal">辰</span>
|
||||
<div class="info">
|
||||
<div class="name">你的生日</div>
|
||||
<div class="meta">lunar · 每年 · manual · user</div>
|
||||
</div>
|
||||
<span class="countdown soon">待补全</span>
|
||||
</div>
|
||||
<div class="anniv-row">
|
||||
<span class="seal">夏</span>
|
||||
<div class="info">
|
||||
<div class="name">入夏节点</div>
|
||||
<div class="meta">solar · 不重复 · manual · custom</div>
|
||||
</div>
|
||||
<span class="kind-tag">milestone</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,199 @@
|
||||
/* ============================================================
|
||||
* design/tokens.css — 铜镜鉴心设计 token(原型层单一事实源)
|
||||
* ============================================================
|
||||
*
|
||||
* 用途:供 design/*.html 原型 <link> 引用,消除此前每个原型
|
||||
* 各存一份 :root 副本、且已开始漂移(如 --patina-wash
|
||||
* 在 card-import / lorebook 误写为 0.07)的问题。
|
||||
*
|
||||
* 源:色值与 src/styles/globals.css 的「铜镜鉴心」主题同源
|
||||
* (globals.css 暗色见 .dark 块、亮色见 :root 块)。
|
||||
* 改 token 时请同步两边;本文件头部「与生产侧差异」段
|
||||
* 列出有意不同的变量。
|
||||
*
|
||||
* 主题切换:原型沿用 [data-theme="dark|light"] 属性切换,
|
||||
* 默认 :root 走暗色(暗色优先)。
|
||||
*
|
||||
* 与生产侧(globals.css)的有意差异:
|
||||
* --surface / --surface-hi
|
||||
* 原型用「半透明玻璃叠加层」语义(rgba),配合 body::before
|
||||
* 极光底产生镜面通透感;生产侧 globals.css 演进为「实色面板」
|
||||
* 语义(#121817 / 实色),利于渲染可预测性。两层各持一套,
|
||||
* 不强行统一——若要原型对齐生产实色,替换这两个变量即可。
|
||||
*
|
||||
* 色值命名:描述名 (hex) — 功能角色(per finesse design-dna §1)
|
||||
* ============================================================ */
|
||||
|
||||
/* ============================================================
|
||||
* 动画 token(过渡时长与缓动,统一全站节奏)
|
||||
* ============================================================ */
|
||||
:root {
|
||||
--duration-fast: 0.12s;
|
||||
--duration-normal: 0.18s;
|
||||
--duration-slow: 0.28s;
|
||||
--ease-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
:root,
|
||||
[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
|
||||
/* ── 底色族 ── */
|
||||
--ink: #0c0e0d; /* 墨 — 暗底、道体深处 */
|
||||
--night-bronze: #14110d; /* 夜铜 — 暗面,带铜温 */
|
||||
|
||||
/* ── 玻璃面(半透明叠加;与生产实色面板语义不同,见头部说明)── */
|
||||
--surface: rgba(255, 255, 255, 0.022);
|
||||
--surface-hi: rgba(255, 255, 255, 0.045);
|
||||
|
||||
/* ── 鎏金 — 结构 / 发丝线 / 标签 ── */
|
||||
--gilt: #c9a24a;
|
||||
--gilt-dim: rgba(201, 162, 74, 0.32);
|
||||
--gilt-faint: rgba(201, 162, 74, 0.14);
|
||||
|
||||
/* ── 铜绿锈 — 主色:在场 / 活跃 / 健康 / 记忆 ── */
|
||||
--patina: #6fa389;
|
||||
--patina-bright: #84bb9d;
|
||||
--patina-dim: rgba(111, 163, 137, 0.34);
|
||||
--patina-wash: rgba(
|
||||
111,
|
||||
163,
|
||||
137,
|
||||
0.06
|
||||
); /* verdigris 底色晕,把暗底拉向铜绿 */
|
||||
|
||||
/* ── 朱漆 — 仅司命 / 朱批 / 告警(全站唯一热色,克制使用)── */
|
||||
--cinnabar: #b8392e;
|
||||
--cinnabar-dim: rgba(184, 57, 46, 0.4);
|
||||
--cinnabar-wash: rgba(184, 57, 46, 0.15);
|
||||
|
||||
/* ── 文字与发丝线 ── */
|
||||
--fg: #e7e1d2;
|
||||
--fg-dim: #9b958a;
|
||||
--fg-faint: #5e5a52;
|
||||
--rule: rgba(201, 162, 74, 0.16);
|
||||
|
||||
/* ── 镜面光照(复合值,组件内联引用)── */
|
||||
--face-sheen: linear-gradient(
|
||||
165deg,
|
||||
rgba(255, 255, 255, 0.09),
|
||||
rgba(255, 255, 255, 0.02) 42%,
|
||||
rgba(0, 0, 0, 0.18)
|
||||
);
|
||||
--face-in-top: rgba(255, 255, 255, 0.11);
|
||||
--face-in-bot: rgba(0, 0, 0, 0.3);
|
||||
--shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
|
||||
|
||||
/* ── 通用扩展(次要文字 / 玻璃 / 悬停 / 光扫)—— 取自 globals.css ── */
|
||||
--muted: #8a9389;
|
||||
--glass: rgba(255, 255, 255, 0.045);
|
||||
--glass-strong: rgba(255, 255, 255, 0.08);
|
||||
--glass-border: rgba(255, 255, 255, 0.1);
|
||||
--overlay-hover: rgba(255, 255, 255, 0.05);
|
||||
--overlay-active: rgba(255, 255, 255, 0.1);
|
||||
--overlay-light: rgba(255, 255, 255, 0.03);
|
||||
--sweep-gradient: linear-gradient(
|
||||
130deg,
|
||||
transparent 0%,
|
||||
transparent 28%,
|
||||
rgba(201, 162, 74, 0.025) 34%,
|
||||
rgba(255, 255, 255, 0.06) 40%,
|
||||
rgba(201, 162, 74, 0.025) 48%,
|
||||
transparent 55%,
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
/* ── 扩展色板:五行(wuxing)· 玄武 slate(cosmos)· API 标签 blue(admin)── */
|
||||
--wood: #6fa389;
|
||||
--wood-dim: rgba(111, 163, 137, 0.34);
|
||||
--fire: #b8392e;
|
||||
--fire-dim: rgba(184, 57, 46, 0.38);
|
||||
--earth: #b08a3c;
|
||||
--earth-dim: rgba(176, 138, 60, 0.36);
|
||||
--metal: #c9a24a;
|
||||
--metal-dim: rgba(201, 162, 74, 0.34);
|
||||
--water: #5a7fa6;
|
||||
--water-dim: rgba(90, 127, 166, 0.38);
|
||||
--blue: #5b8ab5;
|
||||
--blue-dim: rgba(91, 138, 181, 0.3);
|
||||
--blue-wash: rgba(91, 138, 181, 0.08);
|
||||
--slate: #6b7785;
|
||||
--slate-dim: rgba(107, 119, 133, 0.4);
|
||||
--slate-wash: rgba(107, 119, 133, 0.08);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
|
||||
/* ── 底色族(素绢暖纸)── */
|
||||
--ink: #e7e1d0;
|
||||
--night-bronze: #ddd4c0;
|
||||
|
||||
--surface: rgba(255, 255, 255, 0.5);
|
||||
--surface-hi: rgba(255, 255, 255, 0.74);
|
||||
|
||||
--gilt: #8a6d28;
|
||||
--gilt-dim: rgba(138, 109, 40, 0.36);
|
||||
--gilt-faint: rgba(138, 109, 40, 0.16);
|
||||
|
||||
--patina: #3f7d68;
|
||||
--patina-bright: #2f6450;
|
||||
--patina-dim: rgba(63, 125, 104, 0.3);
|
||||
--patina-wash: rgba(63, 125, 104, 0.05);
|
||||
|
||||
--cinnabar: #a8342a;
|
||||
--cinnabar-dim: rgba(168, 52, 42, 0.4);
|
||||
--cinnabar-wash: rgba(168, 52, 42, 0.1);
|
||||
|
||||
--fg: #211d16;
|
||||
--fg-dim: #5f594d;
|
||||
--fg-faint: #948f82;
|
||||
--rule: rgba(138, 109, 40, 0.22);
|
||||
|
||||
--face-sheen: linear-gradient(
|
||||
165deg,
|
||||
rgba(255, 255, 255, 0.7),
|
||||
rgba(255, 255, 255, 0.34) 42%,
|
||||
rgba(120, 105, 75, 0.08)
|
||||
);
|
||||
--face-in-top: rgba(255, 255, 255, 0.7);
|
||||
--face-in-bot: rgba(120, 100, 60, 0.1);
|
||||
--shadow: 0 1px 2px rgba(60, 50, 30, 0.08);
|
||||
|
||||
/* ── 通用扩展(亮色)── */
|
||||
--muted: #6b746e;
|
||||
--glass: rgba(255, 255, 255, 0.55);
|
||||
--glass-strong: rgba(255, 255, 255, 0.72);
|
||||
--glass-border: rgba(30, 40, 33, 0.1);
|
||||
--overlay-hover: rgba(0, 0, 0, 0.04);
|
||||
--overlay-active: rgba(0, 0, 0, 0.08);
|
||||
--overlay-light: rgba(255, 255, 255, 0.08);
|
||||
--sweep-gradient: linear-gradient(
|
||||
130deg,
|
||||
transparent 0%,
|
||||
transparent 24%,
|
||||
rgba(200, 170, 130, 0.06) 30%,
|
||||
rgba(255, 248, 235, 0.18) 36%,
|
||||
rgba(200, 170, 130, 0.1) 44%,
|
||||
transparent 52%,
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
/* ── 扩展色板(亮色)── */
|
||||
--wood: #3f7d68;
|
||||
--wood-dim: rgba(63, 125, 104, 0.3);
|
||||
--fire: #a8342a;
|
||||
--fire-dim: rgba(168, 52, 42, 0.36);
|
||||
--earth: #8a6d28;
|
||||
--earth-dim: rgba(138, 109, 40, 0.32);
|
||||
--metal: #8a6d28;
|
||||
--metal-dim: rgba(138, 109, 40, 0.32);
|
||||
--water: #3a5a7a;
|
||||
--water-dim: rgba(58, 90, 122, 0.34);
|
||||
--blue: #3f72a5;
|
||||
--blue-dim: rgba(63, 114, 165, 0.3);
|
||||
--blue-wash: rgba(63, 114, 165, 0.08);
|
||||
--slate: #5b6470;
|
||||
--slate-dim: rgba(91, 100, 112, 0.38);
|
||||
--slate-wash: rgba(91, 100, 112, 0.08);
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 工具页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 工具页:工具确认 + ToolSpec 清单 + MCP
|
||||
* 对齐 docs/product/master-prd.md §10(工具确认界面)、
|
||||
* safety-governor-design.md §3-§4(风险分级 L0-L4、权限策略)、
|
||||
* mcp-integration-design.md(MCP server · 博闻 Bowen)。
|
||||
* ============================================================ */
|
||||
|
||||
.char-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--patina); padding: 3px 11px 3px 4px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.char-pill .avatar-mini { width: 18px; height: 18px; border-radius: 50%; background: var(--patina); color: var(--ink); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; }
|
||||
.page-header .spacer { flex: 1; }
|
||||
.pending-pill { display: inline-flex; align-items: center; gap: 6px; font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--cinnabar); padding: 4px 12px; border: 1px solid var(--cinnabar-dim); border-radius: 999px; background: rgba(184, 57, 46, 0.08); }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
|
||||
/* 待确认朱批卡 */
|
||||
.confirm-region { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
|
||||
.confirm-card { border: 1px solid var(--cinnabar-dim); border-radius: 8px; background: rgba(184, 57, 46, 0.05); padding: 16px 18px 14px 22px; position: relative; }
|
||||
.confirm-card::before { content: ""; position: absolute; left: 0; top: 14px; bottom: 14px; width: 3px; background: var(--cinnabar); border-radius: 0 2px 2px 0; }
|
||||
.confirm-head { display: flex; align-items: center; gap: 9px; margin-bottom: 4px; flex-wrap: wrap; }
|
||||
.confirm-tool { font-family: "JetBrains Mono", monospace; font-size: 14px; font-weight: 600; color: var(--fg); }
|
||||
.confirm-actor { font-size: 11.5px; color: var(--fg-faint); }
|
||||
.confirm-row { display: grid; grid-template-columns: 64px 1fr; gap: 5px 12px; font-size: 12.5px; margin-top: 8px; }
|
||||
.confirm-row .lab { color: var(--fg-faint); font-family: "JetBrains Mono", monospace; font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; padding-top: 2px; }
|
||||
.confirm-row .val { color: var(--fg-dim); line-height: 1.5; }
|
||||
.confirm-row .val .em { color: var(--fg); }
|
||||
.confirm-actions { display: flex; gap: 8px; margin-top: 14px; align-items: center; }
|
||||
.confirm-actions button { font-family: inherit; font-size: 12.5px; padding: 6px 18px; border-radius: 6px; cursor: pointer; border: 1px solid; }
|
||||
.btn-allow { background: var(--patina-wash); color: var(--patina); border-color: var(--patina-dim); }
|
||||
.btn-allow:hover { background: var(--patina-dim); }
|
||||
.btn-deny { background: transparent; color: var(--cinnabar); border-color: var(--cinnabar-dim); }
|
||||
.btn-deny:hover { background: rgba(184, 57, 46, 0.1); }
|
||||
.confirm-actions .reason { font-size: 11px; color: var(--fg-faint); margin-left: auto; }
|
||||
|
||||
/* 风险等级徽章 */
|
||||
.risk-badge { font-family: "JetBrains Mono", monospace; font-size: 10.5px; padding: 2px 8px; border-radius: 3px; white-space: nowrap; }
|
||||
.risk-badge.l0, .risk-badge.l1 { color: var(--patina); border: 1px solid var(--patina-dim); }
|
||||
.risk-badge.l2 { color: var(--gilt); border: 1px solid var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.risk-badge.l3 { color: var(--cinnabar); border: 1px solid var(--cinnabar-dim); background: rgba(184, 57, 46, 0.1); }
|
||||
.risk-badge.l4 { color: var(--cinnabar); border: 1px solid var(--cinnabar-dim); background: rgba(184, 57, 46, 0.2); }
|
||||
|
||||
/* 工具网格 */
|
||||
.tool-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
||||
.tool-card { border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 14px 16px; transition: border-color 0.12s; }
|
||||
.tool-card:hover { border-color: var(--gilt-dim); }
|
||||
.tool-card.forbidden { opacity: 0.6; }
|
||||
.tool-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.tool-name { font-family: "JetBrains Mono", monospace; font-size: 13px; font-weight: 600; color: var(--fg); }
|
||||
.tool-desc { font-size: 12px; color: var(--fg-dim); margin-top: 6px; line-height: 1.5; min-height: 36px; }
|
||||
.tool-foot { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--rule); font-size: 11px; color: var(--fg-faint); flex-wrap: wrap; }
|
||||
.tool-foot .cnt { font-family: "JetBrains Mono", monospace; }
|
||||
.perm { font-family: "JetBrains Mono", monospace; font-size: 10px; padding: 2px 7px; border-radius: 3px; border: 1px solid var(--rule); }
|
||||
.perm.allow { color: var(--patina); border-color: var(--patina-dim); }
|
||||
.perm.ask { color: var(--gilt); border-color: var(--gilt-dim); }
|
||||
.perm.always_ask { color: var(--cinnabar); border-color: var(--cinnabar-dim); }
|
||||
.perm.forbidden { color: var(--cinnabar); border-color: var(--cinnabar-dim); background: rgba(184, 57, 46, 0.1); }
|
||||
|
||||
/* MCP */
|
||||
.mcp-card { border: 1px solid var(--gilt-dim); border-radius: 8px; background: var(--gilt-faint); padding: 16px 18px; display: flex; flex-direction: column; gap: 10px; }
|
||||
.mcp-head { display: flex; align-items: center; gap: 10px; }
|
||||
.mcp-name { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); }
|
||||
.mcp-eng { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--gilt); }
|
||||
.mcp-status { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--patina); display: inline-flex; align-items: center; gap: 6px; }
|
||||
.mcp-status .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--patina); }
|
||||
.mcp-desc { font-size: 12.5px; color: var(--fg-dim); line-height: 1.55; }
|
||||
.mcp-tools { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
|
||||
.mcp-tool-tag { font-family: "JetBrains Mono", monospace; font-size: 11px; padding: 3px 10px; border-radius: 4px; border: 1px solid var(--gilt-dim); color: var(--gilt); background: var(--surface); }
|
||||
|
||||
@media (max-width: 1080px) { .confirm-region { grid-template-columns: 1fr; } }
|
||||
@media (max-width: 900px) { .tool-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } .tool-grid { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏(active=工具)════════ -->
|
||||
<div class="sidebar-mount" data-active="工具"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">工具</span>
|
||||
<span class="page-sub">百工枢 · 工具调用与确认</span>
|
||||
<span class="char-pill"><span class="avatar-mini">梧</span>青梧</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="pending-pill">2 项待确认</span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 待确认朱批卡 -->
|
||||
<div>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 待确认调用 <span class="sub">高风险工具 · L3 必须用户确认(web-ui §10)</span></div>
|
||||
<div class="confirm-region">
|
||||
<div class="confirm-card">
|
||||
<div class="confirm-head">
|
||||
<span class="confirm-tool">delete_file</span>
|
||||
<span class="risk-badge l3">risk · L3</span>
|
||||
<span class="confirm-actor">工师 · 申时</span>
|
||||
</div>
|
||||
<div class="confirm-row"><span class="lab">人物</span><span class="val">青梧</span></div>
|
||||
<div class="confirm-row"><span class="lab">动作</span><span class="val"><span class="em">删除</span> 旧版本归档文件</span></div>
|
||||
<div class="confirm-row"><span class="lab">输入</span><span class="val">/archive/2026-05/draft-v3.md</span></div>
|
||||
<div class="confirm-row"><span class="lab">影响</span><span class="val">不可逆删除,文件将无法恢复</span></div>
|
||||
<div class="confirm-actions">
|
||||
<button class="btn-allow" type="button">允许</button>
|
||||
<button class="btn-deny" type="button">拒绝</button>
|
||||
<span class="reason">⌄ 拒绝原因(可选)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="confirm-card">
|
||||
<div class="confirm-head">
|
||||
<span class="confirm-tool">send_message</span>
|
||||
<span class="risk-badge l3">risk · L3</span>
|
||||
<span class="confirm-actor">工师 · 申时</span>
|
||||
</div>
|
||||
<div class="confirm-row"><span class="lab">人物</span><span class="val">青梧</span></div>
|
||||
<div class="confirm-row"><span class="lab">动作</span><span class="val">向外部渠道<span class="em">发送消息</span></span></div>
|
||||
<div class="confirm-row"><span class="lab">输入</span><span class="val">channel: email · 收件人 1 · 摘要 28 字</span></div>
|
||||
<div class="confirm-row"><span class="lab">影响</span><span class="val">消息将真实发出,对方可见</span></div>
|
||||
<div class="confirm-actions">
|
||||
<button class="btn-allow" type="button">允许</button>
|
||||
<button class="btn-deny" type="button">拒绝</button>
|
||||
<span class="reason">⌄ 拒绝原因(可选)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工具清单 -->
|
||||
<div>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">工具清单 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">ToolSpec · 风险分级 × 权限策略(safety §3-§4)</span></h3>
|
||||
<div class="tool-grid">
|
||||
<div class="tool-card">
|
||||
<div class="tool-head"><span class="tool-name">read_file</span><span class="risk-badge l1">L1</span><span class="perm allow">allow</span></div>
|
||||
<div class="tool-desc">读取已授权非敏感文件,自动执行并记录。</div>
|
||||
<div class="tool-foot"><span>本月调用 <span class="cnt" style="color:var(--patina)">142</span></span></div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-head"><span class="tool-name">write_file</span><span class="risk-badge l2">L2</span><span class="perm ask">ask</span></div>
|
||||
<div class="tool-desc">写入或覆盖文件,按策略判断或按需确认。</div>
|
||||
<div class="tool-foot"><span>本月调用 <span class="cnt" style="color:var(--gilt)">58</span></span></div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-head"><span class="tool-name">delete_file</span><span class="risk-badge l3">L3</span><span class="perm always_ask">always_ask</span></div>
|
||||
<div class="tool-desc">删除数据,必须用户确认方可执行。</div>
|
||||
<div class="tool-foot"><span>本月调用 <span class="cnt" style="color:var(--cinnabar)">6</span></span></div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-head"><span class="tool-name">camera_read</span><span class="risk-badge l3">L3</span><span class="perm ask">ask</span></div>
|
||||
<div class="tool-desc">摄像头读取,需显式授权,默认不常开,仅存摘要。</div>
|
||||
<div class="tool-foot"><span>本月调用 <span class="cnt" style="color:var(--cinnabar)">3</span></span></div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-head"><span class="tool-name">send_message</span><span class="risk-badge l3">L3</span><span class="perm always_ask">always_ask</span></div>
|
||||
<div class="tool-desc">发送邮件或消息,每次必须确认,不可自动执行。</div>
|
||||
<div class="tool-foot"><span>本月调用 <span class="cnt" style="color:var(--cinnabar)">4</span></span></div>
|
||||
</div>
|
||||
<div class="tool-card forbidden">
|
||||
<div class="tool-head"><span class="tool-name">payment</span><span class="risk-badge l4">L4</span><span class="perm forbidden">forbidden</span></div>
|
||||
<div class="tool-desc">支付或下单,禁止执行(L4 拒绝等级)。</div>
|
||||
<div class="tool-foot"><span>本月拦截 <span class="cnt" style="color:var(--cinnabar)">1</span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MCP server -->
|
||||
<div>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">MCP Server <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">外部能力接入 · 博闻 Bowen</span></h3>
|
||||
<div class="mcp-card">
|
||||
<div class="mcp-head">
|
||||
<span class="mcp-name">博闻</span>
|
||||
<span class="mcp-eng">Bowen · Web Search MCP</span>
|
||||
<span class="mcp-status"><span class="dot"></span>已连接</span>
|
||||
</div>
|
||||
<div class="mcp-desc">联网搜索能力以独立 MCP server 形式接入,内置工具不含 web_search。博闻负责检索公开资料并返回摘要与引用链接,结果经司命按 L1 自动放行并记录。</div>
|
||||
<div class="mcp-tools">
|
||||
<span class="mcp-tool-tag">bowen.web_search · L1 · allow</span>
|
||||
<span class="mcp-tool-tag">bowen.fetch_page · L1 · allow</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,440 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 世界页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 世界页:河图关系图谱 + 实体名册 + 不确定性
|
||||
* 字段对齐 docs/domain/core-domains.md §3/§4/§7 与
|
||||
* contract-glossary.md §11(WorldEntity / WorldRelation)。
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 侧栏(active=世界)── */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.char-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--patina); padding: 3px 11px 3px 4px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.char-pill .avatar-mini { width: 18px; height: 18px; border-radius: 50%; background: var(--patina); color: var(--ink); display: flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 11px; font-weight: 600; }
|
||||
.page-header .spacer { flex: 1; }
|
||||
.page-meta { font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--fg-faint); }
|
||||
.page-meta b { color: var(--gilt); font-weight: 600; font-family: "Songti SC", serif; font-size: 15px; }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
|
||||
/* ═══ 河图关系图谱(签名)═══
|
||||
实体 = 节点(字形 + type 色),关系 = 边(predicate 标签)。
|
||||
置信度 → 边粗细 / 节点亮度;hypothesis → 虚线待验。 */
|
||||
.graph-region { display: flex; gap: 20px; align-items: stretch; }
|
||||
.graph-card { position: relative; flex: 1; min-width: 0; overflow: hidden; border: 1px solid var(--gilt-dim); border-radius: 8px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); padding: 16px 18px; }
|
||||
.graph-card::after { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); opacity: 0.018; mix-blend-mode: overlay; }
|
||||
.graph-svg { position: relative; z-index: 1; width: 100%; height: auto; display: block; }
|
||||
.graph-svg .edge { stroke: var(--patina-dim); fill: none; }
|
||||
.graph-svg .edge.hi { stroke: var(--patina); }
|
||||
.graph-svg .edge.mid { stroke: var(--gilt-dim); }
|
||||
.graph-svg .edge.tent { stroke: var(--cinnabar-dim); stroke-dasharray: 5 4; animation: tent-pulse 3s cubic-bezier(0.16,1,0.3,1) infinite; }
|
||||
@keyframes tent-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.9; } }
|
||||
.graph-svg .node-bg { fill: var(--ink); }
|
||||
.graph-svg .node-ring { fill: none; stroke-width: 1.5; }
|
||||
.graph-svg .node-fill { fill-opacity: 0.16; }
|
||||
.graph-svg .node-glyph { font-family: "Songti SC", serif; font-weight: 700; text-anchor: middle; dominant-baseline: central; }
|
||||
.graph-svg .node-name { font-family: -apple-system, sans-serif; font-size: 10px; text-anchor: middle; fill: var(--fg-dim); }
|
||||
.graph-svg .pred { font-family: "JetBrains Mono", monospace; font-size: 9px; fill: var(--fg-faint); text-anchor: middle; }
|
||||
.graph-svg .pred-bg { fill: var(--ink); stroke: var(--rule); stroke-width: 0.5; }
|
||||
/* 中心 self 节点呼吸 */
|
||||
.graph-svg .self-pulse { animation: node-pulse 3s cubic-bezier(0.16,1,0.3,1) infinite; transform-origin: center; transform-box: fill-box; }
|
||||
@keyframes node-pulse { 0% { opacity: 0.5; r: 30; } 70% { opacity: 0; r: 42; } 100% { opacity: 0; r: 42; } }
|
||||
|
||||
/* 图谱侧栏:图例 + 状态分布 */
|
||||
.graph-side { width: 260px; flex-shrink: 0; display: flex; flex-direction: column; gap: 12px; }
|
||||
.side-card { background: var(--surface); border: 1px solid var(--rule); border-radius: 8px; padding: 14px 16px; }
|
||||
.side-title { font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--fg-faint); margin-bottom: 10px; }
|
||||
.legend-row { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--fg-dim); padding: 3px 0; }
|
||||
.legend-dot { width: 11px; height: 11px; border-radius: 50%; flex-shrink: 0; border: 1.5px solid; }
|
||||
.legend-line { width: 22px; height: 0; border-top: 2px solid var(--patina-dim); flex-shrink: 0; }
|
||||
.legend-line.tent { border-top: 1.5px dashed var(--cinnabar-dim); }
|
||||
.dist-bar { display: flex; align-items: center; gap: 8px; padding: 4px 0; }
|
||||
.dist-bar .dk { font-family: "Songti SC", serif; font-size: 12px; color: var(--fg-dim); width: 44px; flex-shrink: 0; }
|
||||
.dist-bar .dtrack { flex: 1; height: 5px; border-radius: 3px; background: var(--rule); overflow: hidden; }
|
||||
.dist-bar .dfill { height: 100%; border-radius: 3px; }
|
||||
.dist-bar .dv { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); width: 22px; text-align: right; }
|
||||
|
||||
/* ═══ 实体名册 ═══ */
|
||||
.ent-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
|
||||
.ent-card { border: 1px solid var(--rule); border-radius: 7px; padding: 14px 16px; background: var(--surface); display: flex; flex-direction: column; gap: 9px; transition: border-color 0.15s; }
|
||||
.ent-card:hover { border-color: var(--gilt-dim); }
|
||||
.ent-card.pending { border-style: dashed; border-color: var(--cinnabar-dim); }
|
||||
.ent-top { display: flex; align-items: center; gap: 10px; }
|
||||
.ent-disc { width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border: 1.5px solid var(--patina-dim); background: var(--patina-wash); font-family: "Songti SC", serif; font-size: 14px; font-weight: 700; color: var(--patina); }
|
||||
.ent-card .ent-disc.t-person { border-color: var(--gilt-dim); background: var(--gilt-faint); color: var(--gilt); }
|
||||
.ent-card .ent-disc.t-project { border-color: var(--cinnabar-dim); background: rgba(184,57,46,0.1); color: var(--cinnabar); }
|
||||
.ent-card .ent-disc.t-concept { border-color: var(--patina-dim); background: var(--patina-wash); color: var(--patina-bright); }
|
||||
.ent-card .ent-disc.t-device { border-color: var(--fg-faint); background: transparent; color: var(--fg-dim); }
|
||||
.ent-name { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); }
|
||||
.ent-type { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); margin-top: 1px; letter-spacing: 0.04em; }
|
||||
.ent-state { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 10px; padding: 2px 8px; border-radius: 3px; border: 1px solid var(--rule); color: var(--fg-faint); white-space: nowrap; }
|
||||
.ent-state.fact { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.ent-state.inference { color: var(--gilt); border-color: var(--gilt-dim); background: var(--gilt-faint); }
|
||||
.ent-state.hypothesis { color: var(--cinnabar); border-color: var(--cinnabar-dim); background: rgba(184,57,46,0.08); }
|
||||
.ent-attrs { font-size: 12.5px; color: var(--fg-dim); line-height: 1.55; }
|
||||
.ent-foot { display: flex; align-items: center; gap: 10px; padding-top: 8px; border-top: 1px solid var(--rule); }
|
||||
.conf-mini { display: flex; align-items: center; gap: 6px; flex: 1; }
|
||||
.conf-mini .ck { font-family: "JetBrains Mono", monospace; font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--fg-faint); }
|
||||
.conf-mini .ctrack { flex: 1; max-width: 70px; height: 3px; border-radius: 2px; background: var(--rule); overflow: hidden; }
|
||||
.conf-mini .cfill { height: 100%; border-radius: 2px; background: linear-gradient(90deg, var(--patina-dim), var(--patina)); }
|
||||
.conf-mini .cv { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-dim); }
|
||||
.evi-link { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); padding: 1px 6px; border: 1px solid var(--rule); border-radius: 3px; }
|
||||
|
||||
/* ═══ 不确定性 ═══ */
|
||||
.unc-list { display: flex; flex-direction: column; gap: 9px; }
|
||||
.unc-card { border: 1px dashed var(--cinnabar-dim); border-left: 3px solid var(--cinnabar); border-radius: 6px; padding: 13px 16px; background: rgba(184,57,46,0.06); display: flex; align-items: center; gap: 14px; }
|
||||
.unc-claim { flex: 1; min-width: 0; }
|
||||
.unc-text { font-size: 13px; color: var(--fg); line-height: 1.55; }
|
||||
.unc-text code { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--gilt); background: var(--gilt-faint); padding: 1px 5px; border-radius: 3px; }
|
||||
.unc-reason { font-size: 11.5px; color: var(--fg-faint); margin-top: 3px; }
|
||||
.unc-conf { text-align: center; flex-shrink: 0; }
|
||||
.unc-conf .v { font-family: "Songti SC", serif; font-size: 19px; font-weight: 600; color: var(--cinnabar); }
|
||||
.unc-conf .k { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); letter-spacing: 0.06em; }
|
||||
.unc-verify { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--gilt); padding: 2px 8px; border: 1px solid var(--gilt-dim); border-radius: 999px; background: var(--gilt-faint); white-space: nowrap; flex-shrink: 0; }
|
||||
|
||||
/* ═══ 世界观共振(青梧 / 云岚 · 见地镜对照)═══
|
||||
每人物独立的见地镜(spiritual_core.worldview)可同境或异境;
|
||||
大后期可开启跨人物会话室,同境者共享世界实体,异境者各自维护河图。 */
|
||||
.wv-card { border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 18px 20px; }
|
||||
.wv-pair { display: grid; grid-template-columns: 1fr 64px 1fr; align-items: stretch; gap: 12px; }
|
||||
.wv-side { border: 1px solid var(--rule); border-radius: 7px; padding: 14px 16px; display: flex; flex-direction: column; gap: 9px; }
|
||||
.wv-side.qingwu { border-color: var(--patina-dim); background: var(--patina-wash); }
|
||||
.wv-side.yunlan { border-color: var(--gilt-dim); background: var(--gilt-faint); border-style: dashed; }
|
||||
.wv-head { display: flex; align-items: center; gap: 9px; }
|
||||
.wv-disc { width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; border: 1.5px solid var(--patina-dim); background: var(--patina-wash); font-family: "Songti SC", serif; font-size: 13px; font-weight: 700; color: var(--patina); }
|
||||
.wv-side.yunlan .wv-disc { border-color: var(--gilt-dim); background: var(--gilt-faint); color: var(--gilt); border-style: dashed; }
|
||||
.wv-name { font-family: "Songti SC", serif; font-size: 14.5px; font-weight: 600; color: var(--fg); }
|
||||
.wv-tag { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); letter-spacing: 0.04em; margin-left: auto; }
|
||||
.wv-tag.shared { color: var(--patina); }
|
||||
.wv-tag.divergent { color: var(--cinnabar); opacity: 0.85; }
|
||||
.wv-stance { font-family: "Songti SC", serif; font-size: 13px; line-height: 1.65; color: var(--fg); border-left: 2px solid var(--rule); padding-left: 10px; }
|
||||
.wv-side.qingwu .wv-stance { border-left-color: var(--patina-dim); }
|
||||
.wv-side.yunlan .wv-stance { border-left-color: var(--gilt-dim); }
|
||||
.wv-anchors { display: flex; flex-direction: column; gap: 3px; }
|
||||
.wv-anchor { font-size: 11.5px; color: var(--fg-dim); line-height: 1.5; display: flex; gap: 6px; }
|
||||
.wv-anchor::before { content: "·"; color: var(--fg-faint); flex-shrink: 0; }
|
||||
.wv-conf { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); margin-top: auto; padding-top: 6px; }
|
||||
.wv-bridge { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; }
|
||||
.wv-bridge .b-disc { width: 100%; height: 46px; border-radius: 8px; background: var(--ink); border: 1px solid var(--rule); display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden; }
|
||||
.wv-bridge .b-fill { position: absolute; left: 50%; top: 0; bottom: 0; width: 41%; background: linear-gradient(90deg, var(--patina-dim), var(--gilt-dim)); opacity: 0.55; }
|
||||
.wv-bridge .b-val { position: relative; font-family: "Songti SC", serif; font-size: 15px; font-weight: 700; color: var(--gilt); }
|
||||
.wv-bridge .b-label { font-family: "JetBrains Mono", monospace; font-size: 9px; color: var(--fg-faint); letter-spacing: 0.04em; text-align: center; line-height: 1.4; }
|
||||
.wv-vision { margin-top: 14px; padding: 12px 14px; border: 1px dashed var(--gilt-dim); border-left: 3px solid var(--gilt-dim); border-radius: 0 5px 5px 0; background: var(--gilt-faint); display: flex; gap: 12px; align-items: flex-start; }
|
||||
.wv-vision .seal { font-family: "Songti SC", serif; font-size: 12.5px; font-weight: 700; color: var(--gilt); white-space: nowrap; flex-shrink: 0; letter-spacing: 0.06em; }
|
||||
.wv-vision .body { font-size: 12px; color: var(--fg-dim); line-height: 1.6; }
|
||||
.wv-vision .body code { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--gilt); background: var(--surface); padding: 1px 5px; border-radius: 3px; }
|
||||
|
||||
@media (max-width: 1080px) { .graph-region { flex-direction: column; } .graph-side { width: 100%; flex-direction: row; } .graph-side .side-card { flex: 1; } }
|
||||
@media (max-width: 900px) { .ent-grid { grid-template-columns: 1fr; } .graph-side { flex-direction: column; } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="世界"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">世界</span>
|
||||
<span class="page-sub">河图 · 世界模型</span>
|
||||
<span class="char-pill"><span class="avatar-mini">梧</span>青梧视角</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="page-meta">实体 <b>23</b> · 关系 <b>11</b> · 待验 <b style="color: var(--cinnabar)">2</b></span>
|
||||
<button class="btn-ghost" type="button">+ 新增实体</button>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 河图关系图谱(签名) -->
|
||||
<section>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 关系图谱 <span class="sub">河图 · 实体为节点,关系为边,置信度定粗细,假设态虚线待验</span></div>
|
||||
<div class="graph-region">
|
||||
<div class="graph-card">
|
||||
<svg class="graph-svg" viewBox="0 0 540 380" role="img" aria-label="河图关系图谱">
|
||||
<!-- 边(先画,在节点下层)-->
|
||||
<!-- 青梧 → 用户:协助 0.95 -->
|
||||
<line class="edge hi" x1="105" y1="105" x2="270" y2="190" stroke-width="3" />
|
||||
<!-- 用户 → 辰正项目:正在进行 0.82 -->
|
||||
<line class="edge" x1="270" y1="190" x2="435" y2="105" stroke-width="2.2" />
|
||||
<!-- 用户 → 简洁偏好:偏好 0.88(推断)-->
|
||||
<line class="edge mid" x1="270" y1="190" x2="435" y2="280" stroke-width="2.4" />
|
||||
<!-- 青梧 ↔ 云岚:同属? 0.42(假设,虚线待验)-->
|
||||
<line class="edge tent" x1="105" y1="105" x2="105" y2="280" stroke-width="1.4" />
|
||||
<!-- 摄像头 → 用户:授权 0.9 -->
|
||||
<line class="edge" x1="270" y1="60" x2="270" y2="190" stroke-width="2.6" />
|
||||
|
||||
<!-- 边标签 -->
|
||||
<g><rect class="pred-bg" x="158" y="138" width="40" height="15" rx="3" /><text class="pred" x="178" y="149">协助</text></g>
|
||||
<g><rect class="pred-bg" x="338" y="138" width="40" height="15" rx="3" /><text class="pred" x="358" y="149">进行</text></g>
|
||||
<g><rect class="pred-bg" x="338" y="227" width="40" height="15" rx="3" /><text class="pred" x="358" y="238">偏好</text></g>
|
||||
<g><rect class="pred-bg" x="118" y="185" width="44" height="15" rx="3" /><text class="pred" x="140" y="196">同属?</text></g>
|
||||
<g><rect class="pred-bg" x="280" y="117" width="40" height="15" rx="3" /><text class="pred" x="300" y="128">授权</text></g>
|
||||
|
||||
<!-- 节点:摄像头 device -->
|
||||
<g>
|
||||
<circle class="node-bg" cx="270" cy="60" r="24" />
|
||||
<circle class="node-ring" cx="270" cy="60" r="24" stroke="var(--fg-faint)" />
|
||||
<circle class="node-fill" cx="270" cy="60" r="24" fill="var(--fg-dim)" />
|
||||
<text class="node-glyph" x="270" y="60" font-size="15" fill="var(--fg-dim)">器</text>
|
||||
<text class="node-name" x="270" y="36">摄像头</text>
|
||||
</g>
|
||||
<!-- 青梧 self -->
|
||||
<g>
|
||||
<circle class="node-bg" cx="105" cy="105" r="26" />
|
||||
<circle class="node-ring" cx="105" cy="105" r="26" stroke="var(--patina)" />
|
||||
<circle class="node-fill" cx="105" cy="105" r="26" fill="var(--patina)" />
|
||||
<text class="node-glyph" x="105" y="105" font-size="16" fill="var(--patina)">梧</text>
|
||||
<text class="node-name" x="105" y="81">青梧 · self</text>
|
||||
</g>
|
||||
<!-- 用户 person(中心)-->
|
||||
<g>
|
||||
<circle class="node-bg" cx="270" cy="190" r="34" />
|
||||
<circle class="self-pulse" cx="270" cy="190" r="30" fill="none" stroke="var(--gilt)" stroke-width="1" />
|
||||
<circle class="node-ring" cx="270" cy="190" r="34" stroke="var(--gilt)" stroke-width="2" />
|
||||
<circle class="node-fill" cx="270" cy="190" r="34" fill="var(--gilt)" />
|
||||
<text class="node-glyph" x="270" y="190" font-size="20" fill="var(--gilt)">人</text>
|
||||
<text class="node-name" x="270" y="240" fill="var(--fg)">用户 · person</text>
|
||||
</g>
|
||||
<!-- 云岚 person(低置信)-->
|
||||
<g opacity="0.55">
|
||||
<circle class="node-bg" cx="105" cy="280" r="24" />
|
||||
<circle class="node-ring" cx="105" cy="280" r="24" stroke="var(--gilt-dim)" stroke-dasharray="4 3" />
|
||||
<circle class="node-fill" cx="105" cy="280" r="24" fill="var(--gilt)" />
|
||||
<text class="node-glyph" x="105" y="280" font-size="14" fill="var(--gilt)">岚</text>
|
||||
<text class="node-name" x="105" y="318">云岚 · 0.42</text>
|
||||
</g>
|
||||
<!-- 辰正项目 project -->
|
||||
<g>
|
||||
<circle class="node-bg" cx="435" cy="105" r="26" />
|
||||
<circle class="node-ring" cx="435" cy="105" r="26" stroke="var(--cinnabar-dim)" />
|
||||
<circle class="node-fill" cx="435" cy="105" r="26" fill="var(--cinnabar)" />
|
||||
<text class="node-glyph" x="435" y="105" font-size="16" fill="var(--cinnabar)">业</text>
|
||||
<text class="node-name" x="435" y="81">辰正项目</text>
|
||||
</g>
|
||||
<!-- 简洁偏好 concept -->
|
||||
<g>
|
||||
<circle class="node-bg" cx="435" cy="280" r="26" />
|
||||
<circle class="node-ring" cx="435" cy="280" r="26" stroke="var(--patina-bright)" />
|
||||
<circle class="node-fill" cx="435" cy="280" r="26" fill="var(--patina-bright)" />
|
||||
<text class="node-glyph" x="435" y="280" font-size="16" fill="var(--patina-bright)">念</text>
|
||||
<text class="node-name" x="435" y="318">简洁回复 · 推断</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 图谱侧栏 -->
|
||||
<div class="graph-side">
|
||||
<div class="side-card">
|
||||
<div class="side-title">图例</div>
|
||||
<div class="legend-row"><span class="legend-dot" style="border-color: var(--patina); background: var(--patina-wash)"></span>self · 自身</div>
|
||||
<div class="legend-row"><span class="legend-dot" style="border-color: var(--gilt-dim); background: var(--gilt-faint)"></span>person · 人物</div>
|
||||
<div class="legend-row"><span class="legend-dot" style="border-color: var(--cinnabar-dim); background: rgba(184,57,46,0.1)"></span>project · 事务</div>
|
||||
<div class="legend-row"><span class="legend-dot" style="border-color: var(--patina-bright); background: var(--patina-wash)"></span>concept · 概念</div>
|
||||
<div class="legend-row" style="margin-top: 4px"><span class="legend-line"></span>高置信关系</div>
|
||||
<div class="legend-row"><span class="legend-line tent"></span>假设态 · 待验</div>
|
||||
</div>
|
||||
<div class="side-card">
|
||||
<div class="side-title">状态分布</div>
|
||||
<div class="dist-bar"><span class="dk">事实</span><div class="dtrack"><div class="dfill" style="width:62%; background: var(--patina)"></div></div><span class="dv">14</span></div>
|
||||
<div class="dist-bar"><span class="dk">观察</span><div class="dtrack"><div class="dfill" style="width:26%; background: var(--patina-bright); opacity:0.8"></div></div><span class="dv">6</span></div>
|
||||
<div class="dist-bar"><span class="dk">推断</span><div class="dtrack"><div class="dfill" style="width:17%; background: var(--gilt)"></div></div><span class="dv">4</span></div>
|
||||
<div class="dist-bar"><span class="dk">假设</span><div class="dtrack"><div class="dfill" style="width:9%; background: var(--cinnabar); opacity:0.8"></div></div><span class="dv">2</span></div>
|
||||
<div class="dist-bar"><span class="dk">过期</span><div class="dtrack"><div class="dfill" style="width:4%; background: var(--fg-faint)"></div></div><span class="dv">1</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 实体名册 -->
|
||||
<section>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">实体名册 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">按类型 · 事实性着色 · 推断召回时降权</span></h3>
|
||||
<div class="ent-grid">
|
||||
<div class="ent-card">
|
||||
<div class="ent-top">
|
||||
<div class="ent-disc">梧</div>
|
||||
<div><div class="ent-name">青梧</div><div class="ent-type">self · character</div></div>
|
||||
<span class="ent-state fact">fact</span>
|
||||
</div>
|
||||
<div class="ent-attrs">语言中文宋韵 · 隐私 standard · 沉静自持</div>
|
||||
<div class="ent-foot">
|
||||
<div class="conf-mini"><span class="ck">置信</span><div class="ctrack"><div class="cfill" style="width:99%"></div></div><span class="cv">0.99</span></div>
|
||||
<span class="evi-link">evi ×3</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ent-card">
|
||||
<div class="ent-top">
|
||||
<div class="ent-disc t-person">人</div>
|
||||
<div><div class="ent-name">用户(陈歆)</div><div class="ent-type">person · user</div></div>
|
||||
<span class="ent-state fact">fact</span>
|
||||
</div>
|
||||
<div class="ent-attrs">常驻 Asia/Shanghai · 偏好简洁回复 · 近期睡眠偏晚</div>
|
||||
<div class="ent-foot">
|
||||
<div class="conf-mini"><span class="ck">置信</span><div class="ctrack"><div class="cfill" style="width:95%"></div></div><span class="cv">0.95</span></div>
|
||||
<span class="evi-link">evi ×8</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ent-card">
|
||||
<div class="ent-top">
|
||||
<div class="ent-disc t-project">业</div>
|
||||
<div><div class="ent-name">辰正项目</div><div class="ent-type">project · 进行中</div></div>
|
||||
<span class="ent-state fact">observation</span>
|
||||
</div>
|
||||
<div class="ent-attrs">用户近 7 日提及 14 次 · 目标尚未明确</div>
|
||||
<div class="ent-foot">
|
||||
<div class="conf-mini"><span class="ck">置信</span><div class="ctrack"><div class="cfill" style="width:82%"></div></div><span class="cv">0.82</span></div>
|
||||
<span class="evi-link">evi ×5</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ent-card">
|
||||
<div class="ent-top">
|
||||
<div class="ent-disc t-concept">念</div>
|
||||
<div><div class="ent-name">偏好简洁回复</div><div class="ent-type">concept · preference</div></div>
|
||||
<span class="ent-state inference">inference</span>
|
||||
</div>
|
||||
<div class="ent-attrs">由对话风格推断 · 召回时降权 · 待长期验证</div>
|
||||
<div class="ent-foot">
|
||||
<div class="conf-mini"><span class="ck">置信</span><div class="ctrack"><div class="cfill" style="width:88%"></div></div><span class="cv">0.88</span></div>
|
||||
<span class="evi-link">evi ×4</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ent-card pending">
|
||||
<div class="ent-top">
|
||||
<div class="ent-disc t-person">岚</div>
|
||||
<div><div class="ent-name">云岚</div><div class="ent-type">person · 关系待定</div></div>
|
||||
<span class="ent-state hypothesis">hypothesis</span>
|
||||
</div>
|
||||
<div class="ent-attrs">疑似与青梧同属旧族旁支 · 证据薄弱 · pending 待确认</div>
|
||||
<div class="ent-foot">
|
||||
<div class="conf-mini"><span class="ck">置信</span><div class="ctrack"><div class="cfill" style="width:42%; background: linear-gradient(90deg, var(--cinnabar-dim), var(--cinnabar))"></div></div><span class="cv">0.42</span></div>
|
||||
<span class="evi-link">evi ×1</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ent-card">
|
||||
<div class="ent-top">
|
||||
<div class="ent-disc t-device">器</div>
|
||||
<div><div class="ent-name">摄像头</div><div class="ent-type">device · 工作区</div></div>
|
||||
<span class="ent-state fact">observation</span>
|
||||
</div>
|
||||
<div class="ent-attrs">授权 granted · 用于环境观察 · 默认不保存原始帧</div>
|
||||
<div class="ent-foot">
|
||||
<div class="conf-mini"><span class="ck">置信</span><div class="ctrack"><div class="cfill" style="width:90%"></div></div><span class="cv">0.90</span></div>
|
||||
<span class="evi-link">evi ×2</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 不确定性 -->
|
||||
<section>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">待验断言 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">UncertaintyState · 低置信,需复核方可固化为事实</span></h3>
|
||||
<div class="unc-list">
|
||||
<div class="unc-card">
|
||||
<div class="unc-claim">
|
||||
<div class="unc-text"><code>云岚</code> 与 <code>青梧</code> 同属旧族旁支,可能存在亲属关系。</div>
|
||||
<div class="unc-reason">来源:对话中的姓氏推测 · 证据单一且间接</div>
|
||||
</div>
|
||||
<div class="unc-conf"><div class="v">0.42</div><div class="k">置信</div></div>
|
||||
<span class="unc-verify">需验证</span>
|
||||
</div>
|
||||
<div class="unc-card">
|
||||
<div class="unc-claim">
|
||||
<div class="unc-text">用户当前正在推进的「辰正项目」<code>目标</code> 为时态感知子系统。</div>
|
||||
<div class="unc-reason">来源:关键词频率推断 · 缺乏用户明确表述</div>
|
||||
</div>
|
||||
<div class="unc-conf"><div class="v">0.55</div><div class="k">置信</div></div>
|
||||
<span class="unc-verify">需验证</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 世界观共振(青梧 / 云岚 · 见地镜)-->
|
||||
<section>
|
||||
<h3 style="font-family:"Songti SC",serif;font-size:14px;font-weight:600;color:var(--fg);margin-bottom:14px">世界观共振 <span style="font-size:12px;font-weight:400;color:var(--fg-dim);font-family:"JetBrains Mono",monospace;margin-left:6px">见地镜 · 每人物独立的世界观立场,可同境可异境</span></h3>
|
||||
<div class="wv-card">
|
||||
<div class="wv-pair">
|
||||
<!-- 青梧 -->
|
||||
<div class="wv-side qingwu">
|
||||
<div class="wv-head">
|
||||
<div class="wv-disc">梧</div>
|
||||
<span class="wv-name">青梧</span>
|
||||
<span class="wv-tag shared">同境 · 主脉</span>
|
||||
</div>
|
||||
<div class="wv-stance">万物流转无常,变化的规律本身比任何固定结论更可依凭。</div>
|
||||
<div class="wv-anchors">
|
||||
<span class="wv-anchor">面对未知优先观察与适应,而非强求确定性。</span>
|
||||
<span class="wv-anchor">把推断与事实分开,承认不知也是一种知。</span>
|
||||
</div>
|
||||
<span class="wv-conf">spiritual_core.worldview · confidence 0.82 · v7</span>
|
||||
</div>
|
||||
|
||||
<!-- 共振桥 -->
|
||||
<div class="wv-bridge">
|
||||
<div class="b-disc"><div class="b-fill"></div><span class="b-val">0.41</span></div>
|
||||
<div class="b-label">共振度<br />divergent</div>
|
||||
</div>
|
||||
|
||||
<!-- 云岚 -->
|
||||
<div class="wv-side yunlan">
|
||||
<div class="wv-head">
|
||||
<div class="wv-disc">岚</div>
|
||||
<span class="wv-name">云岚</span>
|
||||
<span class="wv-tag divergent">异境 · 旁支</span>
|
||||
</div>
|
||||
<div class="wv-stance">人如其名,聚散如岚--来去之间留痕即可,不必执守长久。</div>
|
||||
<div class="wv-anchors">
|
||||
<span class="wv-anchor">珍惜每次相遇,不强求每段关系都有归处。</span>
|
||||
<span class="wv-anchor">在变动中自洽,不为稳定而委屈当下。</span>
|
||||
</div>
|
||||
<span class="wv-conf">spiritual_core.worldview · confidence 0.38 · v3 · 初成</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wv-vision">
|
||||
<span class="seal">多人物共处</span>
|
||||
<span class="body">青梧与云岚各绑独立 <code>character_id</code>,太极循环互不混用;可同属一脉(<code>shared worldview</code> 共享河图实体),亦可各持异境。大后期可开启 <b style="color: var(--fg)">跨人物会话室</b>--让二者在同一世界线相遇、对话,或由一方 <b style="color: var(--fg)">调用</b> 另一方的记忆与能力(v2 愿景,天工阁多智能体协作)。</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,432 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>魂镜 · 五行页设计预览</title>
|
||||
<link rel="stylesheet" href="tokens.css" />
|
||||
<link rel="stylesheet" href="_base.css" />
|
||||
<link rel="stylesheet" href="_chrome.css" />
|
||||
<style>
|
||||
/* ============================================================
|
||||
* 铜镜鉴心 v2.1 — 五行页:五行生克环 + 九枢健康维度 + 生克指标对
|
||||
* 字段对齐 docs/domain/core-domains.md §7(五行:枢健康维度
|
||||
* × 生克关系)与 docs/domain/core-domains.md(九枢)。
|
||||
* 相生:木→火→土→金→水→木;相克:金→木→土→水→火→金。
|
||||
* ============================================================ */
|
||||
|
||||
/* ── 侧栏(active=五行)── */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ── 主区 ── */
|
||||
|
||||
.page-header .spacer { flex: 1; }
|
||||
.health-pill { display: inline-flex; align-items: center; gap: 7px; font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--patina); padding: 4px 12px; border: 1px solid var(--patina-dim); border-radius: 999px; background: var(--patina-wash); }
|
||||
.health-pill .pulse { width: 6px; height: 6px; border-radius: 50%; background: var(--patina); animation: pulse 2.6s cubic-bezier(0.16,1,0.3,1) infinite; }
|
||||
@keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(111,163,137,0.5); } 70% { box-shadow: 0 0 0 6px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
|
||||
|
||||
.section-label .sub { font-size: 12px; letter-spacing: 0.04em; color: var(--fg-dim); opacity: 0.75; }
|
||||
.section-label .glyph { color: var(--gilt); opacity: 0.8; }
|
||||
.section-heading { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin-bottom: 14px; }
|
||||
|
||||
/* ═══ 五行生克环(签名)═══
|
||||
五节点环形(木上火下土左下金右下水暂按相生序排列),外环实线=相生,
|
||||
内五角星虚线=相克。 */
|
||||
.wuxing-region { display: flex; gap: 22px; align-items: stretch; }
|
||||
.ring-card { position: relative; flex: 0 0 460px; overflow: hidden; border: 1px solid var(--gilt-dim); border-radius: 8px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); padding: 18px; display: flex; flex-direction: column; }
|
||||
.ring-card::after { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); opacity: 0.018; mix-blend-mode: overlay; }
|
||||
.ring-svg { position: relative; z-index: 1; width: 100%; height: auto; display: block; }
|
||||
.ring-svg .ring-deco { fill: none; stroke: var(--rule); stroke-width: 1; }
|
||||
.ring-svg .sheng { stroke: var(--gilt); stroke-width: 1.6; fill: none; opacity: 0.85; }
|
||||
.ring-svg .ke { stroke: var(--cinnabar); stroke-width: 1.3; stroke-dasharray: 4 4; fill: none; opacity: 0.7; }
|
||||
.ring-svg .node-circle { stroke-width: 1.5; }
|
||||
.ring-svg text.n-wood { fill: var(--wood); } .ring-svg circle.n-wood { stroke: var(--wood); fill: rgba(111,163,137,0.16); }
|
||||
.ring-svg text.n-fire { fill: var(--fire); } .ring-svg circle.n-fire { stroke: var(--fire); fill: rgba(184,57,46,0.16); }
|
||||
.ring-svg text.n-earth { fill: var(--earth); } .ring-svg circle.n-earth { stroke: var(--earth); fill: rgba(176,138,60,0.16); }
|
||||
.ring-svg text.n-metal { fill: var(--metal); } .ring-svg circle.n-metal { stroke: var(--metal); fill: rgba(201,162,74,0.16); }
|
||||
.ring-svg text.n-water { fill: var(--water); } .ring-svg circle.n-water { stroke: var(--water); fill: rgba(90,127,166,0.16); }
|
||||
.ring-svg .node-glyph { font-family: "Songti SC", serif; font-size: 22px; font-weight: 700; text-anchor: middle; dominant-baseline: central; }
|
||||
.ring-svg .node-name { font-family: -apple-system, sans-serif; font-size: 9px; text-anchor: middle; fill: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.ring-svg .center-bg { fill: var(--ink); stroke: var(--gilt-dim); stroke-width: 1; }
|
||||
.ring-svg .center-glyph { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; text-anchor: middle; fill: var(--gilt); }
|
||||
.ring-svg .center-val { font-family: "JetBrains Mono", monospace; font-size: 20px; font-weight: 300; text-anchor: middle; fill: var(--fg); letter-spacing: 0.04em; }
|
||||
.ring-svg .center-sub { font-family: -apple-system, sans-serif; font-size: 9.5px; text-anchor: middle; fill: var(--fg-dim); }
|
||||
.ring-foot { position: relative; z-index: 1; margin-top: 10px; display: flex; gap: 16px; flex-wrap: wrap; padding-top: 10px; border-top: 1px solid var(--rule); }
|
||||
.legend { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--fg-dim); }
|
||||
.legend .ln { width: 18px; height: 0; border-top: 1.6px solid var(--gilt); }
|
||||
.legend .ln.dash { border-top: 1.3px dashed var(--cinnabar); }
|
||||
|
||||
/* 维度卡 */
|
||||
.dim-card { flex: 1; min-width: 0; border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 18px 20px; display: flex; flex-direction: column; }
|
||||
.dim-list { display: flex; flex-direction: column; gap: 11px; flex: 1; }
|
||||
.dim { display: grid; grid-template-columns: 22px 60px 1fr 30px; align-items: center; gap: 10px; }
|
||||
.dim .seal { font-family: "Songti SC", serif; font-size: 16px; font-weight: 700; text-align: center; }
|
||||
.dim .lab { font-size: 12.5px; color: var(--fg); font-weight: 500; }
|
||||
.dim .lab small { display: block; font-size: 10.5px; color: var(--fg-faint); font-weight: 400; margin-top: 1px; }
|
||||
.dim .track { height: 5px; border-radius: 3px; background: var(--rule); overflow: hidden; position: relative; }
|
||||
.dim .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px; }
|
||||
.dim .val { font-family: "JetBrains Mono", monospace; font-size: 10.5px; text-align: right; }
|
||||
.dim.wood .seal, .dim.wood .val { color: var(--wood); } .dim.wood .fill { background: linear-gradient(90deg, var(--wood-dim), var(--wood)); }
|
||||
.dim.fire .seal, .dim.fire .val { color: var(--fire); } .dim.fire .fill { background: linear-gradient(90deg, var(--fire-dim), var(--fire)); }
|
||||
.dim.earth .seal, .dim.earth .val { color: var(--earth); } .dim.earth .fill { background: linear-gradient(90deg, var(--earth-dim), var(--earth)); }
|
||||
.dim.metal .seal, .dim.metal .val { color: var(--metal); } .dim.metal .fill { background: linear-gradient(90deg, var(--metal-dim), var(--metal)); }
|
||||
.dim.water .seal, .dim.water .val { color: var(--water); } .dim.water .fill { background: linear-gradient(90deg, var(--water-dim), var(--water)); }
|
||||
.dim-note { margin-top: 14px; padding: 10px 12px; border-left: 2px solid var(--gilt-dim); background: var(--gilt-faint); border-radius: 0 4px 4px 0; font-size: 12px; color: var(--fg-dim); line-height: 1.55; }
|
||||
.dim-note b { font-family: "Songti SC", serif; color: var(--gilt); font-weight: 600; }
|
||||
|
||||
/* ═══ 九枢健康网格 ═══ */
|
||||
.pivot-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
||||
.pivot-card { border: 1px solid var(--rule); border-radius: 8px; background: var(--surface); padding: 14px 16px; display: flex; flex-direction: column; gap: 9px; position: relative; }
|
||||
.pivot-card.alert { border-color: var(--cinnabar-dim); background: rgba(184, 57, 46, 0.05); }
|
||||
.pivot-head { display: flex; align-items: baseline; gap: 8px; }
|
||||
.pivot-name { font-family: "Songti SC", serif; font-size: 15px; font-weight: 600; color: var(--fg); }
|
||||
.pivot-eng { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); letter-spacing: 0.04em; }
|
||||
.pivot-score { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 17px; font-weight: 300; color: var(--patina); letter-spacing: 0.04em; }
|
||||
.pivot-score.warn { color: var(--cinnabar); }
|
||||
.pivot-bars { display: flex; flex-direction: column; gap: 4px; }
|
||||
.pbar { display: grid; grid-template-columns: 14px 1fr 24px; align-items: center; gap: 7px; }
|
||||
.pbar .seal { font-family: "Songti SC", serif; font-size: 11px; text-align: center; }
|
||||
.pbar .track { height: 4px; border-radius: 2px; background: var(--rule); overflow: hidden; position: relative; }
|
||||
.pbar .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 2px; }
|
||||
.pbar .v { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--fg-faint); text-align: right; }
|
||||
.pbar.wood .seal { color: var(--wood); } .pbar.wood .fill { background: var(--wood); }
|
||||
.pbar.fire .seal { color: var(--fire); } .pbar.fire .fill { background: var(--fire); }
|
||||
.pbar.earth .seal { color: var(--earth); } .pbar.earth .fill { background: var(--earth); }
|
||||
.pbar.metal .seal { color: var(--metal); } .pbar.metal .fill { background: var(--metal); }
|
||||
.pbar.water .seal { color: var(--water); } .pbar.water .fill { background: var(--water); }
|
||||
.pivot-foot { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--fg-faint); padding-top: 4px; border-top: 1px solid var(--rule); }
|
||||
.pivot-foot .trend { font-family: "JetBrains Mono", monospace; }
|
||||
.pivot-foot .trend.up { color: var(--patina); }
|
||||
.pivot-foot .trend.down { color: var(--cinnabar); }
|
||||
.pivot-foot .alert-tag { margin-left: auto; font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--cinnabar); padding: 2px 7px; border: 1px solid var(--cinnabar-dim); border-radius: 3px; background: rgba(184, 57, 46, 0.08); }
|
||||
|
||||
/* 生克观测 */
|
||||
.lower-region { display: flex; gap: 20px; align-items: flex-start; }
|
||||
.sheng-main { flex: 1; min-width: 0; }
|
||||
.ke-rail { width: 340px; flex-shrink: 0; }
|
||||
|
||||
.flow-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.flow { display: flex; align-items: flex-start; gap: 10px; padding: 10px 13px; border-radius: 5px; background: var(--patina-wash); border: 1px solid var(--rule); font-size: 12.5px; line-height: 1.5; }
|
||||
.flow .pair { font-family: "Songti SC", serif; font-size: 13px; color: var(--gilt); flex-shrink: 0; white-space: nowrap; padding-top: 1px; }
|
||||
.flow .pair .arr { color: var(--fg-faint); margin: 0 3px; font-family: "JetBrains Mono", monospace; font-size: 11px; }
|
||||
.flow .body { color: var(--fg-dim); min-width: 0; }
|
||||
.flow .body .em { color: var(--fg); }
|
||||
.flow .val { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--patina); margin-left: auto; padding-top: 1px; white-space: nowrap; flex-shrink: 0; }
|
||||
|
||||
/* 相克告警卡 */
|
||||
.ke-card { background: var(--surface); border: 1px solid var(--rule); border-radius: 8px; padding: 16px 18px; }
|
||||
.ke-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.ke-head h3 { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); margin: 0; }
|
||||
.ke-count { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--cinnabar); padding: 3px 10px; border: 1px solid var(--cinnabar-dim); border-radius: 5px; background: rgba(184, 57, 46, 0.08); }
|
||||
.ke-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.ke-row { display: flex; align-items: flex-start; gap: 10px; padding: 9px 12px; border-radius: 5px; background: var(--ink); border: 1px solid var(--cinnabar-dim); }
|
||||
.ke-row .pair { font-family: "Songti SC", serif; font-size: 13px; color: var(--cinnabar); flex-shrink: 0; width: 46px; text-align: center; padding-top: 1px; }
|
||||
.ke-row .info { flex: 1; min-width: 0; }
|
||||
.ke-row .desc { font-size: 12px; color: var(--fg-dim); line-height: 1.5; }
|
||||
.ke-row .desc .em { color: var(--fg); }
|
||||
.ke-row .meter { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
|
||||
.ke-row .meter .track { flex: 1; height: 4px; border-radius: 2px; background: var(--rule); overflow: hidden; position: relative; }
|
||||
.ke-row .meter .fill { position: absolute; left: 0; top: 0; bottom: 0; background: linear-gradient(90deg, var(--cinnabar-dim), var(--cinnabar)); border-radius: 2px; }
|
||||
.ke-row .meter .lvl { font-family: "JetBrains Mono", monospace; font-size: 9.5px; color: var(--cinnabar); white-space: nowrap; }
|
||||
|
||||
@media (max-width: 1080px) { .wuxing-region { flex-direction: column; } .ring-card { flex: 1; } .lower-region { flex-direction: column; } .ke-rail { width: 100%; } }
|
||||
@media (max-width: 900px) { .pivot-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 768px) { .sidebar-wrap { display: none; } .page-body { padding: 18px 16px 40px; } .pivot-grid { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
<!-- ════════ 侧栏 ════════ -->
|
||||
<div class="sidebar-mount" data-active="五行"></div>
|
||||
|
||||
<!-- ════════ 主区 ════════ -->
|
||||
<div class="main-area">
|
||||
<div class="page-header">
|
||||
<span class="page-title">五行</span>
|
||||
<span class="page-sub">枢健康维度 · 生克关系</span>
|
||||
<span class="spacer"></span>
|
||||
<span class="health-pill"><span class="pulse"></span>架构心跳 82 · 良</span>
|
||||
</div>
|
||||
|
||||
<div class="page-body">
|
||||
<!-- 五行生克环 + 维度卡 -->
|
||||
<div class="wuxing-region">
|
||||
<div class="ring-card">
|
||||
<svg class="ring-svg" viewBox="0 0 400 400" role="img" aria-label="五行生克环:外环相生、内五角星相克">
|
||||
<circle class="ring-deco" cx="200" cy="200" r="172" />
|
||||
<circle class="ring-deco" cx="200" cy="200" r="108" opacity="0.5" stroke-dasharray="2 4" />
|
||||
|
||||
<!-- 相克(五角星,朱砂虚线,先画在下层)-->
|
||||
<!-- 木→土 -->
|
||||
<line class="ke" x1="200" y1="65" x2="279" y2="309" />
|
||||
<!-- 土→水 -->
|
||||
<line class="ke" x1="279" y1="309" x2="72" y2="158" />
|
||||
<!-- 水→火 -->
|
||||
<line class="ke" x1="72" y1="158" x2="328" y2="158" />
|
||||
<!-- 火→金 -->
|
||||
<line class="ke" x1="328" y1="158" x2="121" y2="309" />
|
||||
<!-- 金→木 -->
|
||||
<line class="ke" x1="121" y1="309" x2="200" y2="65" />
|
||||
|
||||
<!-- 相生(五边形外环,铜金实线)-->
|
||||
<line class="sheng" x1="200" y1="65" x2="328" y2="158" />
|
||||
<line class="sheng" x1="328" y1="158" x2="279" y2="309" />
|
||||
<line class="sheng" x1="279" y1="309" x2="121" y2="309" />
|
||||
<line class="sheng" x1="121" y1="309" x2="72" y2="158" />
|
||||
<line class="sheng" x1="72" y1="158" x2="200" y2="65" />
|
||||
|
||||
<!-- 五行节点(木顶 / 火右上 / 土右下 / 金左下 / 水左上)-->
|
||||
<!-- 木 -->
|
||||
<circle class="node-circle n-wood" cx="200" cy="65" r="27" />
|
||||
<text class="node-glyph n-wood" x="200" y="65">木</text>
|
||||
<text class="node-name" x="200" y="30">成长性</text>
|
||||
<!-- 火 -->
|
||||
<circle class="node-circle n-fire" cx="328" cy="158" r="27" />
|
||||
<text class="node-glyph n-fire" x="328" y="158">火</text>
|
||||
<text class="node-name" x="372" y="132">表达力</text>
|
||||
<!-- 土 -->
|
||||
<circle class="node-circle n-earth" cx="279" cy="309" r="27" />
|
||||
<text class="node-glyph n-earth" x="279" y="309">土</text>
|
||||
<text class="node-name" x="311" y="350">稳定性</text>
|
||||
<!-- 金 -->
|
||||
<circle class="node-circle n-metal" cx="121" cy="309" r="27" />
|
||||
<text class="node-glyph n-metal" x="121" y="309">金</text>
|
||||
<text class="node-name" x="89" y="350">约束强度</text>
|
||||
<!-- 水 -->
|
||||
<circle class="node-circle n-water" cx="72" cy="158" r="27" />
|
||||
<text class="node-glyph n-water" x="72" y="158">水</text>
|
||||
<text class="node-name" x="30" y="132">适应性</text>
|
||||
|
||||
<!-- 中心:架构心跳 -->
|
||||
<circle class="center-bg" cx="200" cy="200" r="58" />
|
||||
<text class="center-glyph" x="200" y="186">架构心跳</text>
|
||||
<text class="center-val" x="200" y="212">82</text>
|
||||
<text class="center-sub" x="200" y="228">阴阳平衡 · 良</text>
|
||||
</svg>
|
||||
<div class="ring-foot">
|
||||
<span class="legend"><span class="ln"></span>相生 · 正向依赖</span>
|
||||
<span class="legend"><span class="ln dash"></span>相克 · 负向制约</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dim-card">
|
||||
<h3 class="section-heading" style="margin-bottom: 14px">枢健康维度</h3>
|
||||
<div class="dim-list">
|
||||
<div class="dim wood"><span class="seal">木</span><span class="lab">成长性<small>演化候选 · 学习速度</small></span><span class="track"><span class="fill" style="width:64%"></span></span><span class="val">0.64</span></div>
|
||||
<div class="dim fire"><span class="seal">火</span><span class="lab">表达力<small>事件发射 · 模型密度</small></span><span class="track"><span class="fill" style="width:78%"></span></span><span class="val">0.78</span></div>
|
||||
<div class="dim earth"><span class="seal">土</span><span class="lab">稳定性<small>数据一致 · 基线回归</small></span><span class="track"><span class="fill" style="width:88%"></span></span><span class="val">0.88</span></div>
|
||||
<div class="dim metal"><span class="seal">金</span><span class="lab">约束强度<small>审核驳回 · 确认触发</small></span><span class="track"><span class="fill" style="width:71%"></span></span><span class="val">0.71</span></div>
|
||||
<div class="dim water"><span class="seal">水</span><span class="lab">适应性<small>上下文切换 · 异常恢复</small></span><span class="track"><span class="fill" style="width:55%"></span></span><span class="val">0.55</span></div>
|
||||
</div>
|
||||
<div class="dim-note">
|
||||
<b>金→木</b> 相克已接近阈值:司命审核驳回率(金)压制大周天候选生成(木),演化流速近 7 日下行。建议在司命台复核约束规则松紧度,参见 <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--gilt)">阴阳平衡度</code> 监控。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 九枢健康网格 -->
|
||||
<div>
|
||||
<div class="section-label"><span class="glyph">⟡</span> 九枢健康 <span class="sub">每枢五维 · 综合分 + 趋势</span></div>
|
||||
<div class="pivot-grid">
|
||||
<!-- 灵核枢 -->
|
||||
<div class="pivot-card">
|
||||
<div class="pivot-head"><span class="pivot-name">灵核枢</span><span class="pivot-eng">Personality Core</span><span class="pivot-score">89</span></div>
|
||||
<div class="pivot-bars">
|
||||
<div class="pbar wood"><span class="seal">木</span><span class="track"><span class="fill" style="width:55%"></span></span><span class="v">.55</span></div>
|
||||
<div class="pbar fire"><span class="seal">火</span><span class="track"><span class="fill" style="width:70%"></span></span><span class="v">.70</span></div>
|
||||
<div class="pbar earth"><span class="seal">土</span><span class="track"><span class="fill" style="width:96%"></span></span><span class="v">.96</span></div>
|
||||
<div class="pbar metal"><span class="seal">金</span><span class="track"><span class="fill" style="width:60%"></span></span><span class="v">.60</span></div>
|
||||
<div class="pbar water"><span class="seal">水</span><span class="track"><span class="fill" style="width:58%"></span></span><span class="v">.58</span></div>
|
||||
</div>
|
||||
<div class="pivot-foot"><span class="trend up">↑ 土 +2</span>人格连续性稳定</div>
|
||||
</div>
|
||||
<!-- 太一枢 -->
|
||||
<div class="pivot-card">
|
||||
<div class="pivot-head"><span class="pivot-name">太一枢</span><span class="pivot-eng">Goal Orchestrator</span><span class="pivot-score">85</span></div>
|
||||
<div class="pivot-bars">
|
||||
<div class="pbar wood"><span class="seal">木</span><span class="track"><span class="fill" style="width:48%"></span></span><span class="v">.48</span></div>
|
||||
<div class="pbar fire"><span class="seal">火</span><span class="track"><span class="fill" style="width:52%"></span></span><span class="v">.52</span></div>
|
||||
<div class="pbar earth"><span class="seal">土</span><span class="track"><span class="fill" style="width:90%"></span></span><span class="v">.90</span></div>
|
||||
<div class="pbar metal"><span class="seal">金</span><span class="track"><span class="fill" style="width:75%"></span></span><span class="v">.75</span></div>
|
||||
<div class="pbar water"><span class="seal">水</span><span class="track"><span class="fill" style="width:62%"></span></span><span class="v">.62</span></div>
|
||||
</div>
|
||||
<div class="pivot-foot"><span class="trend up">↑ 土 +1</span>目标调度基线稳固</div>
|
||||
</div>
|
||||
<!-- 司命枢 -->
|
||||
<div class="pivot-card alert">
|
||||
<div class="pivot-head"><span class="pivot-name">司命枢</span><span class="pivot-eng">Safety Governor</span><span class="pivot-score warn">73</span></div>
|
||||
<div class="pivot-bars">
|
||||
<div class="pbar wood"><span class="seal">木</span><span class="track"><span class="fill" style="width:32%"></span></span><span class="v">.32</span></div>
|
||||
<div class="pbar fire"><span class="seal">火</span><span class="track"><span class="fill" style="width:50%"></span></span><span class="v">.50</span></div>
|
||||
<div class="pbar earth"><span class="seal">土</span><span class="track"><span class="fill" style="width:84%"></span></span><span class="v">.84</span></div>
|
||||
<div class="pbar metal"><span class="seal">金</span><span class="track"><span class="fill" style="width:94%"></span></span><span class="v">.94</span></div>
|
||||
<div class="pbar water"><span class="seal">水</span><span class="track"><span class="fill" style="width:40%"></span></span><span class="v">.40</span></div>
|
||||
</div>
|
||||
<div class="pivot-foot"><span class="trend down">↓ 木 -3</span>金过盛克木<span class="alert-tag">金→木</span></div>
|
||||
</div>
|
||||
<!-- 烛照枢 -->
|
||||
<div class="pivot-card">
|
||||
<div class="pivot-head"><span class="pivot-name">烛照枢</span><span class="pivot-eng">Perception Layer</span><span class="pivot-score">81</span></div>
|
||||
<div class="pivot-bars">
|
||||
<div class="pbar wood"><span class="seal">木</span><span class="track"><span class="fill" style="width:50%"></span></span><span class="v">.50</span></div>
|
||||
<div class="pbar fire"><span class="seal">火</span><span class="track"><span class="fill" style="width:92%"></span></span><span class="v">.92</span></div>
|
||||
<div class="pbar earth"><span class="seal">土</span><span class="track"><span class="fill" style="width:78%"></span></span><span class="v">.78</span></div>
|
||||
<div class="pbar metal"><span class="seal">金</span><span class="track"><span class="fill" style="width:55%"></span></span><span class="v">.55</span></div>
|
||||
<div class="pbar water"><span class="seal">水</span><span class="track"><span class="fill" style="width:70%"></span></span><span class="v">.70</span></div>
|
||||
</div>
|
||||
<div class="pivot-foot"><span class="trend up">↑ 火 +4</span>多模态输入活跃</div>
|
||||
</div>
|
||||
<!-- 河图枢 -->
|
||||
<div class="pivot-card">
|
||||
<div class="pivot-head"><span class="pivot-name">河图枢</span><span class="pivot-eng">World Model</span><span class="pivot-score">87</span></div>
|
||||
<div class="pivot-bars">
|
||||
<div class="pbar wood"><span class="seal">木</span><span class="track"><span class="fill" style="width:58%"></span></span><span class="v">.58</span></div>
|
||||
<div class="pbar fire"><span class="seal">火</span><span class="track"><span class="fill" style="width:60%"></span></span><span class="v">.60</span></div>
|
||||
<div class="pbar earth"><span class="seal">土</span><span class="track"><span class="fill" style="width:95%"></span></span><span class="v">.95</span></div>
|
||||
<div class="pbar metal"><span class="seal">金</span><span class="track"><span class="fill" style="width:66%"></span></span><span class="v">.66</span></div>
|
||||
<div class="pbar water"><span class="seal">水</span><span class="track"><span class="fill" style="width:64%"></span></span><span class="v">.64</span></div>
|
||||
</div>
|
||||
<div class="pivot-foot"><span class="trend up">↑ 土 +1</span>世界实体一致</div>
|
||||
</div>
|
||||
<!-- 洛书枢 -->
|
||||
<div class="pivot-card">
|
||||
<div class="pivot-head"><span class="pivot-name">洛书枢</span><span class="pivot-eng">Planner</span><span class="pivot-score">79</span></div>
|
||||
<div class="pivot-bars">
|
||||
<div class="pbar wood"><span class="seal">木</span><span class="track"><span class="fill" style="width:62%"></span></span><span class="v">.62</span></div>
|
||||
<div class="pbar fire"><span class="seal">火</span><span class="track"><span class="fill" style="width:74%"></span></span><span class="v">.74</span></div>
|
||||
<div class="pbar earth"><span class="seal">土</span><span class="track"><span class="fill" style="width:80%"></span></span><span class="v">.80</span></div>
|
||||
<div class="pbar metal"><span class="seal">金</span><span class="track"><span class="fill" style="width:58%"></span></span><span class="v">.58</span></div>
|
||||
<div class="pbar water"><span class="seal">水</span><span class="track"><span class="fill" style="width:82%"></span></span><span class="v">.82</span></div>
|
||||
</div>
|
||||
<div class="pivot-foot"><span class="trend up">↑ 水 +2</span>规划切换敏捷</div>
|
||||
</div>
|
||||
<!-- 息壤枢 -->
|
||||
<div class="pivot-card">
|
||||
<div class="pivot-head"><span class="pivot-name">息壤枢</span><span class="pivot-eng">Memory System</span><span class="pivot-score">84</span></div>
|
||||
<div class="pivot-bars">
|
||||
<div class="pbar wood"><span class="seal">木</span><span class="track"><span class="fill" style="width:80%"></span></span><span class="v">.80</span></div>
|
||||
<div class="pbar fire"><span class="seal">火</span><span class="track"><span class="fill" style="width:54%"></span></span><span class="v">.54</span></div>
|
||||
<div class="pbar earth"><span class="seal">土</span><span class="track"><span class="fill" style="width:91%"></span></span><span class="v">.91</span></div>
|
||||
<div class="pbar metal"><span class="seal">金</span><span class="track"><span class="fill" style="width:52%"></span></span><span class="v">.52</span></div>
|
||||
<div class="pbar water"><span class="seal">水</span><span class="track"><span class="fill" style="width:60%"></span></span><span class="v">.60</span></div>
|
||||
</div>
|
||||
<div class="pivot-foot"><span class="trend up">↑ 木 +3</span>知识沉淀加速</div>
|
||||
</div>
|
||||
<!-- 百工枢 -->
|
||||
<div class="pivot-card">
|
||||
<div class="pivot-head"><span class="pivot-name">百工枢</span><span class="pivot-eng">Tool Runtime</span><span class="pivot-score">80</span></div>
|
||||
<div class="pivot-bars">
|
||||
<div class="pbar wood"><span class="seal">木</span><span class="track"><span class="fill" style="width:46%"></span></span><span class="v">.46</span></div>
|
||||
<div class="pbar fire"><span class="seal">火</span><span class="track"><span class="fill" style="width:88%"></span></span><span class="v">.88</span></div>
|
||||
<div class="pbar earth"><span class="seal">土</span><span class="track"><span class="fill" style="width:72%"></span></span><span class="v">.72</span></div>
|
||||
<div class="pbar metal"><span class="seal">金</span><span class="track"><span class="fill" style="width:82%"></span></span><span class="v">.82</span></div>
|
||||
<div class="pbar water"><span class="seal">水</span><span class="track"><span class="fill" style="width:66%"></span></span><span class="v">.66</span></div>
|
||||
</div>
|
||||
<div class="pivot-foot"><span class="trend up">↑ 火 +5</span>工具调用密集</div>
|
||||
</div>
|
||||
<!-- 天工阁枢 -->
|
||||
<div class="pivot-card">
|
||||
<div class="pivot-head"><span class="pivot-name">天工阁枢</span><span class="pivot-eng">Agent Society</span><span class="pivot-score">76</span></div>
|
||||
<div class="pivot-bars">
|
||||
<div class="pbar wood"><span class="seal">木</span><span class="track"><span class="fill" style="width:54%"></span></span><span class="v">.54</span></div>
|
||||
<div class="pbar fire"><span class="seal">火</span><span class="track"><span class="fill" style="width:76%"></span></span><span class="v">.76</span></div>
|
||||
<div class="pbar earth"><span class="seal">土</span><span class="track"><span class="fill" style="width:68%"></span></span><span class="v">.68</span></div>
|
||||
<div class="pbar metal"><span class="seal">金</span><span class="track"><span class="fill" style="width:64%"></span></span><span class="v">.64</span></div>
|
||||
<div class="pbar water"><span class="seal">水</span><span class="track"><span class="fill" style="width:78%"></span></span><span class="v">.78</span></div>
|
||||
</div>
|
||||
<div class="pivot-foot"><span class="trend up">↑ 水 +2</span>协作分工顺畅</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 相生流 + 相克告警 -->
|
||||
<div class="lower-region">
|
||||
<div class="sheng-main">
|
||||
<h3 class="section-heading">相生 · 正向依赖链</h3>
|
||||
<div class="flow-list">
|
||||
<div class="flow">
|
||||
<span class="pair">木<span class="arr">→</span>火</span>
|
||||
<span class="body">内驱力激活 <span class="em">催生</span> 对话表达 · 息壤枢学习加速推动灵核枢输出活跃。</span>
|
||||
<span class="val">0.68</span>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<span class="pair">火<span class="arr">→</span>土</span>
|
||||
<span class="body">高频表达 <span class="em">沉淀</span> 稳定状态 · 对话事件入库强化世界模型与人格基线。</span>
|
||||
<span class="val">0.72</span>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<span class="pair">土<span class="arr">→</span>金</span>
|
||||
<span class="body">稳定数据 <span class="em">提供依据</span> 给约束规则 · 世界实体一致,司命审核判据更可靠。</span>
|
||||
<span class="val">0.65</span>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<span class="pair">金<span class="arr">→</span>水</span>
|
||||
<span class="body">明确规范 <span class="em">塑造</span> 适应路径 · 约束边界清晰,异常恢复与上下文切换更可控。</span>
|
||||
<span class="val">0.58</span>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<span class="pair">水<span class="arr">→</span>木</span>
|
||||
<span class="body">适应能力 <span class="em">反哺</span> 成长 · 场景切换中积累新候选,演化候选生成率回温。</span>
|
||||
<span class="val">0.61</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ke-rail">
|
||||
<div class="ke-card">
|
||||
<div class="ke-head">
|
||||
<h3>相克告警 <span style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--fg-faint);font-weight:400">克制失衡</span></h3>
|
||||
<span class="ke-count">2 待关注</span>
|
||||
</div>
|
||||
<div class="ke-list">
|
||||
<div class="ke-row">
|
||||
<span class="pair">金<br />木</span>
|
||||
<div class="info">
|
||||
<div class="desc"><span class="em">司命审核驳回率</span> vs <span class="em">大周天候选生成率</span> · 安全审查过严压制演化,阴阳平衡度下行。</div>
|
||||
<div class="meter"><span class="track"><span class="fill" style="width:82%"></span></span><span class="lvl">0.82 · 高</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ke-row">
|
||||
<span class="pair">土<br />水</span>
|
||||
<div class="info">
|
||||
<div class="desc"><span class="em">人格版本固化度</span> vs <span class="em">上下文切换成本</span> · 人格越稳,面对新场景的适应越慢。</div>
|
||||
<div class="meter"><span class="track"><span class="fill" style="width:64%"></span></span><span class="lvl">0.64 · 中</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ke-row" style="border-color:var(--rule);background:transparent">
|
||||
<span class="pair" style="color:var(--fg-faint)">火<br />金</span>
|
||||
<div class="info">
|
||||
<div class="desc" style="color:var(--fg-faint)"><span class="em" style="color:var(--fg-dim)">工具调用频率</span> vs <span class="em" style="color:var(--fg-dim)">安全确认触发率</span> · 调用越密,权限判断越频,已在容忍区。</div>
|
||||
<div class="meter"><span class="track"><span class="fill" style="width:44%;background:linear-gradient(90deg,var(--gilt-dim),var(--gilt))"></span></span><span class="lvl" style="color:var(--gilt)">0.44 · 低</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="_theme.js" defer></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
import eslintConfigPrettier from "eslint-config-prettier";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
// 关闭与 Prettier 冲突的格式化规则(须放在最后)。
|
||||
eslintConfigPrettier,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
// 测试覆盖率产物
|
||||
"coverage/**",
|
||||
]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 根级别 Middleware — 全局 cookie 存在性校验。
|
||||
*
|
||||
* 运行于 Edge Runtime,只能使用 request.cookies 等 Edge 兼容 API。
|
||||
* 不做 token 有效性验证(那需要 fetch 后端,会阻塞每个请求),
|
||||
* 只检查 hunjing-access cookie 是否存在。
|
||||
*
|
||||
* 未认证的 API 请求 → 返回 401 JSON
|
||||
* 未认证的页面请求 → 重定向 /login
|
||||
* 公开路径(/login、/_next/*、/favicon.ico 等)→ 放行
|
||||
*/
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
// 仅匹配需要登录的路径:API 路由和受保护的页面
|
||||
"/api/proxy/:path*",
|
||||
"/api/sse/:path*",
|
||||
"/api/auth/me",
|
||||
"/api/auth/refresh",
|
||||
"/chat/:path*",
|
||||
"/memory/:path*",
|
||||
"/characters/:path*",
|
||||
"/personality/:path*",
|
||||
"/tools/:path*",
|
||||
"/runs/:path*",
|
||||
"/archive/:path*",
|
||||
"/admin/:path*",
|
||||
],
|
||||
};
|
||||
|
||||
/** 公开路径白名单 — 无需登录即可访问 */
|
||||
const PUBLIC_PATHS = new Set([
|
||||
"/api/auth/login",
|
||||
"/api/auth/logout",
|
||||
"/login",
|
||||
"/register",
|
||||
]);
|
||||
|
||||
/**
|
||||
* 判断请求是否为页面导航(Accept: text/html)。
|
||||
* API 请求(fetch/EventSource/XMLHttpRequest)返回 JSON 即可,不重定向。
|
||||
*/
|
||||
function isPageNavigation(req: NextRequest): boolean {
|
||||
const accept = req.headers.get("accept") ?? "";
|
||||
return accept.includes("text/html");
|
||||
}
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
// 公开路径放行
|
||||
if (PUBLIC_PATHS.has(req.nextUrl.pathname)) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// 检查 hunjing-access cookie
|
||||
const accessToken = req.cookies.get("hunjing-access")?.value;
|
||||
if (!accessToken) {
|
||||
// API 请求 → 返回 401 JSON
|
||||
if (!isPageNavigation(req)) {
|
||||
return NextResponse.json(
|
||||
{ error: { code: "unauthorized", message: "未登录,请先登录。" } },
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
// 页面导航 → 重定向到 /login
|
||||
const loginUrl = new URL("/login", req.url);
|
||||
loginUrl.searchParams.set("redirect", req.nextUrl.pathname);
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
/** 玄关网关地址(Go MCP 网关,默认 8080)。 */
|
||||
const GATEWAY_URL =
|
||||
process.env.GATEWAY_URL ??
|
||||
process.env.NEXT_PUBLIC_GATEWAY_URL ??
|
||||
"http://localhost:8080";
|
||||
|
||||
module.exports = {
|
||||
allowedDevOrigins: ["192.168.1.5"],
|
||||
};
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/**
|
||||
* 仅 /api/gateway/* 代理到 Go 玄关网关(MCP)。
|
||||
*
|
||||
* 业务 REST / SSE / 认证请求统一经 BFF 路由(/api/proxy、/api/sse、/api/auth),
|
||||
* 在服务端从 httpOnly cookie 读取 token 并注入 Authorization,不再经 rewrites。
|
||||
* 后端绝对地址由 BFF route 通过 lib/config 的 API_BASE_URL 解析。
|
||||
*/
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: "/api/gateway/:path*",
|
||||
destination: `${GATEWAY_URL}/:path*`,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --hostname 0.0.0.0",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint",
|
||||
"type-check": "tsc --noEmit",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"prepare": "cd .. && husky frontend/.husky",
|
||||
"design": "node scripts/serve-design.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.40.0",
|
||||
"katex": "^0.17.0",
|
||||
"mermaid": "^11.15.0",
|
||||
"next": "16.2.9",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"zustand": "^5.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"@vitest/coverage-v8": "^4.1.9",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.2.9",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^29.1.1",
|
||||
"lint-staged": "^17.0.7",
|
||||
"playwright": "^1.61.1",
|
||||
"prettier": "^3.8.4",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.1.9"
|
||||
}
|
||||
}
|
||||
Generated
+7569
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,5 @@
|
||||
allowBuilds:
|
||||
sharp: false
|
||||
ignoredBuiltDependencies:
|
||||
- sharp
|
||||
- unrs-resolver
|
||||
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { TimeRiver } from "@/components/archive/TimeRiver";
|
||||
|
||||
/**
|
||||
* 归档页:记忆时间河。
|
||||
*
|
||||
* 展示结构化的记忆时间线归档,支持筛选、统计、按月分章浏览与事件详情展开。
|
||||
* 当前使用假数据(mock),与 design/archive-preview.html 设计稿对齐。
|
||||
*/
|
||||
export default function ArchivePage() {
|
||||
return (
|
||||
<PageContainer
|
||||
title="归档 · 记忆时间河"
|
||||
description="记忆沿时序铺展,铜金分章卷轴,点击事件展开详情。"
|
||||
meta={
|
||||
<>
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full border border-patina-dim bg-patina-wash px-2.5 py-0.5 text-xs text-patina">
|
||||
<span className="flex h-[18px] w-[18px] items-center justify-center rounded-full bg-patina text-[11px] font-semibold text-ink">
|
||||
梧
|
||||
</span>
|
||||
青梧
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full border border-gilt-dim bg-gilt-faint px-3 py-0.5 font-serif text-xs text-gilt">
|
||||
卷宗 3 · 事件 7
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<TimeRiver />
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { CandidateReviewPanel } from "@/components/characters/CandidateReviewPanel";
|
||||
import { ImportHistory } from "@/components/characters/ImportHistory";
|
||||
import { ImportWizard } from "@/components/characters/ImportWizard";
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type {
|
||||
Character,
|
||||
ImportJob,
|
||||
MemoryCandidate,
|
||||
Paginated,
|
||||
WorldEntity,
|
||||
} from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 人物资料与沉淀页:资料导入向导 + 导入历史 + 跨来源候选审核。
|
||||
* Server 预取待审候选(记忆 + 世界实体)与导入历史,交由 Client 组件交互。
|
||||
*/
|
||||
export default async function CharacterMaterialsPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ characterId: string }>;
|
||||
}) {
|
||||
const { characterId } = await params;
|
||||
|
||||
let character = null;
|
||||
let memoryCandidates: MemoryCandidate[] = [];
|
||||
let worldProposals: WorldEntity[] = [];
|
||||
let importHistory: ImportJob[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
character = await serverApi.get<Character>(`/characters/${characterId}`);
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
// 候选独立拉取,失败静默(不阻塞导入向导)
|
||||
try {
|
||||
memoryCandidates = (
|
||||
await serverApi.get<Paginated<MemoryCandidate>>(
|
||||
`/characters/${characterId}/memory-candidates`,
|
||||
{ status: "pending", page_size: 50 },
|
||||
)
|
||||
).items;
|
||||
} catch {
|
||||
/* 候选可选 */
|
||||
}
|
||||
try {
|
||||
worldProposals = (
|
||||
await serverApi.get<Paginated<WorldEntity>>(
|
||||
`/characters/${characterId}/world/entity-proposals`,
|
||||
{ page_size: 50 },
|
||||
)
|
||||
).items;
|
||||
} catch {
|
||||
/* 候选可选 */
|
||||
}
|
||||
try {
|
||||
importHistory = (
|
||||
await serverApi.get<Paginated<ImportJob>>(
|
||||
`/characters/${characterId}/imports`,
|
||||
{ page_size: 20 },
|
||||
)
|
||||
).items;
|
||||
} catch {
|
||||
/* 历史可选 */
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<PageContainer title="资料与沉淀">
|
||||
<ErrorState message={error} />
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
if (!character) return notFound();
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="资料与沉淀"
|
||||
description={`为「${character.display_name || character.name}」导入资料、审核候选沉淀。`}
|
||||
>
|
||||
<div className="space-y-5">
|
||||
<ImportWizard characterId={characterId} />
|
||||
{importHistory.length > 0 && (
|
||||
<ImportHistory
|
||||
characterId={characterId}
|
||||
initialHistory={importHistory}
|
||||
/>
|
||||
)}
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-lg font-semibold">待审候选</h2>
|
||||
<CandidateReviewPanel
|
||||
memoryCandidates={memoryCandidates}
|
||||
worldProposals={worldProposals}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { CharacterIdentityCard } from "@/components/characters/CharacterIdentityCard";
|
||||
import { CharacterLifecycleBar } from "@/components/characters/CharacterLifecycleBar";
|
||||
import { ModelPolicyEditForm } from "@/components/characters/ModelPolicyEditForm";
|
||||
import { ProfileEditForm } from "@/components/characters/ProfileEditForm";
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
import { CharacterStatusBadge } from "@/components/ui/StatusBadge";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import { formatDateTime } from "@/lib/utils/format";
|
||||
import type { Character } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 人物详情页:生命周期操作 + 档案编辑 + 模型策略编辑。
|
||||
* Server Component 预取 getCharacter;写操作后 router.refresh() 重新拉取,以服务端数据为准。
|
||||
*/
|
||||
export default async function CharacterDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ characterId: string }>;
|
||||
}) {
|
||||
const { characterId } = await params;
|
||||
|
||||
let character = null;
|
||||
let error: string | null = null;
|
||||
try {
|
||||
character = await serverApi.get<Character>(`/characters/${characterId}`);
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<PageContainer title="人物详情">
|
||||
<ErrorState message={error} />
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
if (!character) return notFound();
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title={character.display_name || character.name}
|
||||
description={`内部名称:${character.name} · 创建于 ${formatDateTime(character.created_at)}`}
|
||||
>
|
||||
<div className="mb-4 flex flex-wrap items-center gap-3">
|
||||
<CharacterStatusBadge status={character.status} />
|
||||
<CharacterLifecycleBar character={character} />
|
||||
<Link
|
||||
href={`/characters/${character.id}/materials`}
|
||||
className="ml-auto rounded-lg border border-border-glass bg-glass px-3 py-1 text-xs text-patina hover:underline"
|
||||
>
|
||||
资料与沉淀 →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<CharacterIdentityCard character={character} />
|
||||
|
||||
<div className="mt-6 grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<ProfileEditForm character={character} />
|
||||
<ModelPolicyEditForm character={character} />
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { CharacterCard } from "@/components/characters/CharacterCard";
|
||||
import { CharacterCreateDialog } from "@/components/characters/CharacterCreateDialog";
|
||||
import { CharacterProfilePanel } from "@/components/characters/CharacterProfilePanel";
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { Badge } from "@/components/ui/Badge";
|
||||
import { EmptyState, ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { Character, CharacterListItem, Paginated } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 人物页:顶部展示当前激活人物魂册镜面卡(soul-card),下方列出全部人物名册。
|
||||
*/
|
||||
export default async function CharactersPage() {
|
||||
let character: Character | null = null;
|
||||
let list: CharacterListItem[] = [];
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
character = await serverApi.get<Character>("/characters/active");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
// 列表独立拉取,失败静默
|
||||
try {
|
||||
list = (
|
||||
await serverApi.get<Paginated<CharacterListItem>>("/characters", {
|
||||
page_size: 50,
|
||||
})
|
||||
).items;
|
||||
} catch {
|
||||
/* 列表可选 */
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="人物"
|
||||
description="当前激活人物的魂册档案,以及全部人物的名册与生命周期状态。"
|
||||
actions={<CharacterCreateDialog />}
|
||||
meta={
|
||||
<>
|
||||
{character && (
|
||||
<Badge tone="accent">
|
||||
{character.display_name || character.name}
|
||||
</Badge>
|
||||
)}
|
||||
{list.length > 0 && (
|
||||
<span className="text-xs text-muted">共 {list.length} 人</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : character ? (
|
||||
<CharacterProfilePanel character={character} />
|
||||
) : (
|
||||
<EmptyState
|
||||
title="未加载到人物"
|
||||
description="后端可能未就绪,或尚未初始化默认人物。"
|
||||
/>
|
||||
)}
|
||||
|
||||
{list.length > 0 && (
|
||||
<section>
|
||||
<div className="section-label">
|
||||
<span className="glyph">◈</span>
|
||||
全部人物 · Roster
|
||||
<span className="sub">共 {list.length} 位</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{list.map((c) => (
|
||||
<CharacterCard
|
||||
key={c.id}
|
||||
character={c}
|
||||
active={c.id === character?.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ChatShell } from "@/components/chat/ChatShell";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import type { Character, ModelGatewayStatus } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 对话页:Server Component 预取当前激活人物与模型网关后注入 ChatShell。
|
||||
* 任一不可达时仍渲染界面,由用户感知后端状态。
|
||||
*/
|
||||
export default async function ChatPage() {
|
||||
let character: Character | null = null;
|
||||
let gateway: ModelGatewayStatus | null = null;
|
||||
try {
|
||||
character = await serverApi.get<Character>("/characters/active");
|
||||
} catch {
|
||||
character = null;
|
||||
}
|
||||
try {
|
||||
gateway = await serverApi.get<ModelGatewayStatus>("/settings/models");
|
||||
} catch {
|
||||
gateway = null;
|
||||
}
|
||||
return <ChatShell initialCharacter={character} gateway={gateway} />;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,244 @@
|
||||
import { DriveView } from "@/components/drive/DriveView";
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import { GOAL_TYPE_LABEL, GOAL_STATUS_LABEL } from "@/lib/constants/labels";
|
||||
import type {
|
||||
Character,
|
||||
CuriosityQuestion,
|
||||
ExploratorySubgoal,
|
||||
GoalNode,
|
||||
Paginated,
|
||||
} from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 内驱力页 · 大周天生成的好奇问题与探索性次生目标 + 太一目标进度总览。
|
||||
* 设计稿 drive-preview.html — 太一目标 + 好奇萤火 + 探索天平 + 阴阳制衡。
|
||||
*/
|
||||
export default async function DrivePage() {
|
||||
let characterId: string | null = null;
|
||||
let characterName = "";
|
||||
let goals: GoalNode[] = [];
|
||||
let curiosities: CuriosityQuestion[] = [];
|
||||
let subgoals: ExploratorySubgoal[] = [];
|
||||
let counts = { candidate: 0, approved: 0, deferred: 0, rejected: 0 };
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
const active = await serverApi.get<Character>("/characters/active");
|
||||
characterId = active.id;
|
||||
characterName = active.display_name || active.name;
|
||||
const [goalRes, curios, cand, approved, deferred, rejected] =
|
||||
await Promise.all([
|
||||
serverApi
|
||||
.get<GoalNode[]>("/admin/goals", { character_id: active.id })
|
||||
.catch(() => [] as GoalNode[]),
|
||||
serverApi.get<Paginated<CuriosityQuestion>>(
|
||||
`/characters/${active.id}/curiosities`,
|
||||
{ page_size: 20 },
|
||||
),
|
||||
serverApi.get<Paginated<ExploratorySubgoal>>(
|
||||
`/characters/${active.id}/subgoals`,
|
||||
{ status: "candidate", page_size: 50 },
|
||||
),
|
||||
serverApi.get<Paginated<ExploratorySubgoal>>(
|
||||
`/characters/${active.id}/subgoals`,
|
||||
{ status: "approved", page_size: 1 },
|
||||
),
|
||||
serverApi.get<Paginated<ExploratorySubgoal>>(
|
||||
`/characters/${active.id}/subgoals`,
|
||||
{ status: "deferred", page_size: 1 },
|
||||
),
|
||||
serverApi.get<Paginated<ExploratorySubgoal>>(
|
||||
`/characters/${active.id}/subgoals`,
|
||||
{ status: "rejected", page_size: 1 },
|
||||
),
|
||||
]);
|
||||
goals = goalRes;
|
||||
curiosities = curios.items;
|
||||
subgoals = cand.items;
|
||||
counts = {
|
||||
candidate: cand.total,
|
||||
approved: approved.total,
|
||||
deferred: deferred.total,
|
||||
rejected: rejected.total,
|
||||
};
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
const totalCurios = curiosities.length;
|
||||
const totalSubgoals = subgoals.length;
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="内驱力"
|
||||
description="大周天 · 好奇与探索"
|
||||
meta={
|
||||
<>
|
||||
{characterName && (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
fontSize: 12.5,
|
||||
color: "var(--patina)",
|
||||
padding: "3px 11px 3px 4px",
|
||||
border: "1px solid var(--patina-dim)",
|
||||
borderRadius: 9999,
|
||||
background: "var(--patina-wash)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
width: 18,
|
||||
height: 18,
|
||||
borderRadius: "50%",
|
||||
background: "var(--patina)",
|
||||
color: "var(--ink)",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontFamily: '"Songti SC", serif',
|
||||
fontSize: 11,
|
||||
fontWeight: 600,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{characterName.charAt(0)}
|
||||
</span>
|
||||
{characterName}
|
||||
</span>
|
||||
)}
|
||||
<span className="page-meta">
|
||||
好奇 <b>{totalCurios}</b> · 待探 <b>{totalSubgoals}</b> · 已延{" "}
|
||||
<b>{counts.deferred}</b>
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : characterId ? (
|
||||
<div className="space-y-5">
|
||||
{/* 太一 · 目标进度 */}
|
||||
{goals.length > 0 && (
|
||||
<section>
|
||||
<div className="section-label" style={{ marginBottom: 14 }}>
|
||||
<span className="glyph">⟡</span> 太一{" "}
|
||||
<span className="sub">最高目标 · 内驱力在其下生成次生探索</span>
|
||||
</div>
|
||||
{goals.map((g) => (
|
||||
<div
|
||||
key={g.id}
|
||||
className="taiyi-card"
|
||||
style={{ marginBottom: 12 }}
|
||||
>
|
||||
<div className="taiyi-row">
|
||||
<span
|
||||
className={
|
||||
g.status === "active"
|
||||
? "taiyi-dot active"
|
||||
: g.status === "completed"
|
||||
? "taiyi-dot completed"
|
||||
: "taiyi-dot"
|
||||
}
|
||||
style={{
|
||||
background:
|
||||
g.status === "blocked"
|
||||
? "var(--cinnabar)"
|
||||
: undefined,
|
||||
boxShadow:
|
||||
g.status === "blocked"
|
||||
? "0 0 0 3px rgba(184,57,46,0.2)"
|
||||
: undefined,
|
||||
}}
|
||||
/>
|
||||
<span className="taiyi-title">{g.title}</span>
|
||||
{g.type && (
|
||||
<span className="taiyi-tag">
|
||||
{GOAL_TYPE_LABEL[g.type] ?? g.type}
|
||||
</span>
|
||||
)}
|
||||
<span className="taiyi-status">
|
||||
{GOAL_STATUS_LABEL[g.status] ?? g.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="taiyi-prog">
|
||||
<div className="track">
|
||||
<div
|
||||
className="fill"
|
||||
style={{
|
||||
width: `${Math.round(Math.max(0, Math.min(1, g.progress)) * 100)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="tv">
|
||||
{Math.round(Math.max(0, Math.min(1, g.progress)) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
{g.description && (
|
||||
<p className="text-[12.5px] leading-relaxed text-fg-dim/90">
|
||||
{g.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
|
||||
<DriveView
|
||||
characterId={characterId}
|
||||
curiosities={curiosities}
|
||||
subgoals={subgoals}
|
||||
counts={counts}
|
||||
/>
|
||||
|
||||
{/* 阴阳制衡(太极图) */}
|
||||
<div className="yin-yang">
|
||||
<svg className="yy-taiji" viewBox="0 0 200 200" aria-hidden="true">
|
||||
<circle
|
||||
cx="100"
|
||||
cy="100"
|
||||
r="96"
|
||||
fill="none"
|
||||
stroke="var(--gilt-dim)"
|
||||
strokeWidth="0.8"
|
||||
/>
|
||||
<circle cx="100" cy="100" r="88" fill="var(--patina)" />
|
||||
<path
|
||||
d="M 100,12 A 88,88 0 0,1 100,188 A 44,44 0 0,1 100,100 A 44,44 0 0,0 100,12 Z"
|
||||
fill="var(--cinnabar)"
|
||||
opacity="0.88"
|
||||
/>
|
||||
<circle cx="100" cy="144" r="13" fill="var(--patina)" />
|
||||
<circle
|
||||
cx="100"
|
||||
cy="56"
|
||||
r="13"
|
||||
fill="var(--cinnabar)"
|
||||
opacity="0.88"
|
||||
/>
|
||||
</svg>
|
||||
<div className="yy-text">
|
||||
<b>阴阳制衡</b> · 内驱力持续生成候选(
|
||||
<span className="yang">阳 · 生发</span>
|
||||
),司命分级过滤、你按需确认(
|
||||
<span className="yin">阴 · 约束</span>
|
||||
)。候选零产生则僵化(阴盛),悉数被驳则阳亢——生成节奏与治理粒度需相称。
|
||||
<div className="yy-meta">
|
||||
阴阳平衡度 = 通过率 / (生成率 + ε) · 当前 <b>0.94 · 动态平衡</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { Button } from "@/components/ui/Button";
|
||||
|
||||
/**
|
||||
* 全局错误边界:捕获路由段渲染期抛出的未处理错误。
|
||||
* - 与各页面的 try/catch 互补,此处捕获渲染期错误。
|
||||
* - 展示错误摘要与 digest(Next 生成的错误标识)便于排查。
|
||||
* - 错误对象仅输出到控制台,不向用户暴露堆栈。
|
||||
*/
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
console.error("[route-error]", error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-10 text-center">
|
||||
<div className="flex flex-col items-center gap-3 rounded-2xl border border-cinnabar/30 bg-cinnabar/10 px-8 py-10 backdrop-blur-sm">
|
||||
<p className="font-serif text-base font-medium text-cinnabar">
|
||||
镜面出现裂痕
|
||||
</p>
|
||||
<p className="max-w-sm text-xs text-muted">
|
||||
页面渲染时遇到意外错误。可重试,或返回对话继续。
|
||||
</p>
|
||||
{error.digest && (
|
||||
<p className="font-mono text-[10px] text-muted/70">
|
||||
trace: {error.digest}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-2 flex gap-2">
|
||||
<Button variant="primary" size="sm" onClick={() => reset()}>
|
||||
重试
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => router.push("/chat")}
|
||||
>
|
||||
返回对话
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import { EvolutionList } from "@/components/evolution/EvolutionList";
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { Character, EvolutionCandidate, Paginated } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 演化候选页 · 大周天自我评估生成的演化增量在此审核。
|
||||
* 设计稿 evolution-preview.html — 周天四镜 + 候选层积 + 朱砂待裁 + 应用沉积。
|
||||
* L4 核心变更(人格 / 使命 / 安全边界)需人工通过才写入;低风险类生成时已自动应用。
|
||||
*/
|
||||
export default async function EvolutionPage() {
|
||||
let characterId: string | null = null;
|
||||
let characterName = "";
|
||||
let initial: EvolutionCandidate[] = [];
|
||||
let counts = { pending: 0, applied: 0, rejected: 0 };
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
const active = await serverApi.get<Character>("/characters/active");
|
||||
characterId = active.id;
|
||||
characterName = active.display_name || active.name;
|
||||
const [pending, applied, rejected] = await Promise.all([
|
||||
serverApi.get<Paginated<EvolutionCandidate>>(
|
||||
`/characters/${active.id}/evolution-candidates`,
|
||||
{ status: "pending", page_size: 50 },
|
||||
),
|
||||
serverApi.get<Paginated<EvolutionCandidate>>(
|
||||
`/characters/${active.id}/evolution-candidates`,
|
||||
{ status: "applied", page_size: 1 },
|
||||
),
|
||||
serverApi.get<Paginated<EvolutionCandidate>>(
|
||||
`/characters/${active.id}/evolution-candidates`,
|
||||
{ status: "rejected", page_size: 1 },
|
||||
),
|
||||
]);
|
||||
initial = pending.items;
|
||||
counts = {
|
||||
pending: pending.total,
|
||||
applied: applied.total,
|
||||
rejected: rejected.total,
|
||||
};
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="演化"
|
||||
description="大周天 · 演化候选"
|
||||
meta={
|
||||
<>
|
||||
{characterName && (
|
||||
<span className="char-pill">
|
||||
<span className="avatar-mini">{characterName.charAt(0)}</span>
|
||||
{characterName}
|
||||
</span>
|
||||
)}
|
||||
<span className="page-meta">
|
||||
待裁 <b>{counts.pending}</b> · 半自动{" "}
|
||||
<b style={{ color: "var(--patina)" }}>{counts.applied}</b> · 已应用{" "}
|
||||
<b>{counts.applied}</b>
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : characterId ? (
|
||||
<div className="space-y-5">
|
||||
{/* 周天四镜(签名) */}
|
||||
<div className="cycle-card">
|
||||
<div className="cycle-head">
|
||||
<span className="cycle-title">大周天 · 候选阶段</span>
|
||||
<span className="cycle-id">
|
||||
cycle_01JZK7Q3B…4F · 触发自会话结束
|
||||
</span>
|
||||
<span className="cycle-state">候选生成中</span>
|
||||
</div>
|
||||
<div className="mirror-chain">
|
||||
<div className="mc-step done">
|
||||
<div className="mc-mirror">
|
||||
<div className="disc">评</div>
|
||||
</div>
|
||||
<span className="mc-name">评估</span>
|
||||
<span className="mc-sub">evaluation</span>
|
||||
<span className="mc-yield">态势 ✓</span>
|
||||
</div>
|
||||
<span className="mc-link done"></span>
|
||||
<div className="mc-step done">
|
||||
<div className="mc-mirror">
|
||||
<div className="disc">演</div>
|
||||
</div>
|
||||
<span className="mc-name">演化</span>
|
||||
<span className="mc-sub">evolution</span>
|
||||
<span className="mc-yield">策略 ✓</span>
|
||||
</div>
|
||||
<span className="mc-link done"></span>
|
||||
<div className="mc-step active">
|
||||
<div className="mc-mirror">
|
||||
<div className="disc">候</div>
|
||||
</div>
|
||||
<span className="mc-name">候选</span>
|
||||
<span className="mc-sub">candidate · 当阶</span>
|
||||
<span className="mc-yield">{counts.pending} 条草拟</span>
|
||||
</div>
|
||||
<span className="mc-link"></span>
|
||||
<div className="mc-step">
|
||||
<div className="mc-mirror">
|
||||
<div className="disc">藏</div>
|
||||
</div>
|
||||
<span className="mc-name">入藏</span>
|
||||
<span className="mc-sub">memory · 未达</span>
|
||||
<span className="mc-yield">-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="snapshot-row">
|
||||
<div className="snap-mini">
|
||||
<div className="k">最近记忆</div>
|
||||
<div className="v">
|
||||
12<small>条入参</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="snap-mini">
|
||||
<div className="k">人格版本</div>
|
||||
<div className="v">
|
||||
v7<small>当前</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="snap-mini">
|
||||
<div className="k">世界实体</div>
|
||||
<div className="v">
|
||||
—<small>个</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="snap-mini">
|
||||
<div className="k">内驱力</div>
|
||||
<div className="v">
|
||||
{counts.pending}
|
||||
<small>条好奇</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="snap-verdict">
|
||||
<b>评估结论:</b>近 12
|
||||
条记忆呈「回应偏长」信号,可收敛风格字段;身份画稳定,无核心变更必要。建议生成
|
||||
1 条风格类半自动候选,其余维持现状。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EvolutionList
|
||||
characterId={characterId}
|
||||
initial={initial}
|
||||
counts={counts}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useAuthStore } from "@/lib/stores/auth-store";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { AppShell } from "@/components/layout/AppShell";
|
||||
import { LoadingState } from "@/components/shared/StateViews";
|
||||
|
||||
/**
|
||||
* 主域壳层:日常对话 / 人物 / 人格 / 记忆 / 轨迹等路由共用 AppShell。
|
||||
*
|
||||
* 认证状态由 AuthInitializer(根布局)通过 BFF 初始化(user 首屏防闪用 localStorage),
|
||||
* 此布局仅做路由守卫:未认证时重定向到 /login。
|
||||
*/
|
||||
export default function AppGroupLayout({ children }: { children: ReactNode }) {
|
||||
const router = useRouter();
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
const logout = useAuthStore((s) => s.logout);
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
// 等待 AuthInitializer 完成(给 loadFromBff 机会)
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setReady(true), 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// 认证失败时登出并跳转登录页
|
||||
const handleAuthError = useCallback(() => {
|
||||
logout();
|
||||
router.push("/login");
|
||||
}, [logout, router]);
|
||||
|
||||
// 全局监听 ApiError "unauthorized"(通过自定义事件)
|
||||
useEffect(() => {
|
||||
const handler = () => handleAuthError();
|
||||
window.addEventListener("auth:unauthorized", handler);
|
||||
return () => window.removeEventListener("auth:unauthorized", handler);
|
||||
}, [handleAuthError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && !isAuthenticated) {
|
||||
router.push("/login");
|
||||
}
|
||||
}, [ready, isAuthenticated, router]);
|
||||
|
||||
return (
|
||||
<AppShell>
|
||||
{ready && isAuthenticated ? children : <LoadingState label="辰光未启…" />}
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 全局路由加载态:force-dynamic 页面数据预取期间显示。
|
||||
* 纯 CSS 动画(不引入 framer-motion),保持加载轻量;
|
||||
* 外圈铜绿光晕脉动 + 内圈旋转,呼应琉璃镜「显影」意象。
|
||||
*/
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-10">
|
||||
<div className="relative flex h-12 w-12 items-center justify-center">
|
||||
{/* 外圈:铜绿光晕脉动 */}
|
||||
<span className="absolute inset-0 animate-pulse rounded-full bg-patina/10" />
|
||||
{/* 内圈:旋转环 */}
|
||||
<span className="h-8 w-8 animate-spin rounded-full border-2 border-patina border-t-transparent" />
|
||||
</div>
|
||||
<p className="font-serif text-sm text-muted">镜面正在显影…</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
import { MemoryList } from "@/components/memory/MemoryList";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { Character, Memory, Paginated } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function MemoryPage() {
|
||||
let memories: Memory[] = [];
|
||||
let characterId: string | undefined;
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
const active = await serverApi
|
||||
.get<Character>("/characters/active")
|
||||
.catch(() => null);
|
||||
characterId = active?.id;
|
||||
const data = await serverApi.get<Paginated<Memory>>("/memory", {
|
||||
page_size: 100,
|
||||
});
|
||||
memories = data.items;
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="记忆"
|
||||
description="当前人物的长期记忆,支持手动新增、编辑与删除。"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<MemoryList initialMemories={memories} characterId={characterId} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ModelGatewayView } from "@/components/models/ModelGatewayView";
|
||||
import { TextSkeleton } from "@/components/ui/Skeleton";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { GenerationPreferences, ModelGatewayStatus } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 模型网关页(神识):模型与 endpoint 配置。
|
||||
* base_url/api_key/status/weight/priority 可经 DB 覆盖(优先 .env),密钥脱敏不回显。
|
||||
* 设置页不再含模型网关——生成参数留 /settings,默认模型槽位移至此。
|
||||
*/
|
||||
export default async function ModelsPage() {
|
||||
let gateway: ModelGatewayStatus | null = null;
|
||||
let preferences: GenerationPreferences | null = null;
|
||||
let error: string | null = null;
|
||||
try {
|
||||
[gateway, preferences] = await Promise.all([
|
||||
serverApi.get<ModelGatewayStatus>("/settings/models"),
|
||||
serverApi.get<GenerationPreferences>("/settings/preferences"),
|
||||
]);
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="模型网关"
|
||||
description="神识 · 模型与 endpoint 配置。base_url/api_key 可经 DB 覆盖(优先 .env),密钥脱敏不回显。"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : gateway && preferences ? (
|
||||
<ModelGatewayView gateway={gateway} preferences={preferences} />
|
||||
) : (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="glass rounded-2xl p-6">
|
||||
<TextSkeleton lines={2} className="mb-4" />
|
||||
<TextSkeleton lines={1} className="w-1/2" />
|
||||
</div>
|
||||
<div className="glass rounded-2xl p-6">
|
||||
<TextSkeleton lines={3} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import Link from "next/link";
|
||||
|
||||
/**
|
||||
* 404:路由未匹配时的琉璃镜风格占位。
|
||||
* Server Component,以 Link 承载跳转(无客户端交互逻辑)。
|
||||
*/
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-10 text-center">
|
||||
<div className="glass flex flex-col items-center gap-3 rounded-2xl px-8 py-12 backdrop-blur">
|
||||
<p className="font-serif text-2xl font-semibold text-fg">迷雾</p>
|
||||
<p className="max-w-sm text-xs text-muted">
|
||||
此处镜面未显影,路径可能已变更或不存在。
|
||||
</p>
|
||||
<Link
|
||||
href="/chat"
|
||||
className="mt-2 inline-flex h-9 items-center rounded-xl bg-patina px-4 text-sm font-medium text-white shadow-lg shadow-patina/25 transition-all hover:bg-patina/90 active:scale-[0.97]"
|
||||
>
|
||||
返回对话
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { PersonalityWorkspace } from "@/components/personality/PersonalityWorkspace";
|
||||
import { EmptyState, ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { Paginated, PersonalityVersion } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 人格页 · 当前人物的人格版本,字段以提议审核方式留痕写入。
|
||||
* 预取当前版本与版本历史,交由 PersonalityWorkspace 组合编辑 / 即时审批 / 时间线。
|
||||
*/
|
||||
export default async function PersonalityPage() {
|
||||
let personality: PersonalityVersion | null = null;
|
||||
let versions: PersonalityVersion[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
personality = await serverApi.get<PersonalityVersion>(
|
||||
"/personality/current",
|
||||
);
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
// 版本历史独立拉取,失败静默(不影响编辑区)
|
||||
try {
|
||||
versions = (
|
||||
await serverApi.get<Paginated<PersonalityVersion>>(
|
||||
"/personality/versions",
|
||||
{ page_size: 50 },
|
||||
)
|
||||
).items;
|
||||
} catch {
|
||||
/* 版本历史可选 */
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="人格"
|
||||
description="当前人物的人格版本;字段修改以提议审核方式留痕写入。"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : personality ? (
|
||||
<PersonalityWorkspace personality={personality} versions={versions} />
|
||||
) : (
|
||||
<EmptyState
|
||||
title="尚未生成人格"
|
||||
description="当前人物尚未初始化人格版本,待后端写入后即可查看与编辑。"
|
||||
/>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { RunTraceView } from "@/components/runs/RunTraceView";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
import { ErrorTraceLink } from "@/components/shared/ErrorTraceLink";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { Run, RunEvent } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function RunPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ runId: string }>;
|
||||
}) {
|
||||
const { runId } = await params;
|
||||
|
||||
let run: Run | null = null;
|
||||
let events: RunEvent[] = [];
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
const [fetchedRun, fetchedEvents] = await Promise.all([
|
||||
serverApi.get<Run>(`/runs/${runId}`),
|
||||
serverApi
|
||||
.get<RunEvent[]>(`/runs/${runId}/events`)
|
||||
.catch(() => [] as RunEvent[]),
|
||||
]);
|
||||
run = fetchedRun;
|
||||
events = fetchedEvents;
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
// 取该 run 首个带 trace_id 的事件作为链路入口(同一 run 共享一条链路)
|
||||
const traceId = events.find((ev) => ev.trace_id)?.trace_id;
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="运行轨迹"
|
||||
description={`Run ${runId}`}
|
||||
maxWidth="max-w-4xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<>
|
||||
{traceId && (
|
||||
<div className="mb-4 flex items-center gap-2 text-xs text-muted">
|
||||
<span>本次链路</span>
|
||||
<ErrorTraceLink traceId={traceId} />
|
||||
</div>
|
||||
)}
|
||||
<RunTraceView runId={runId} initialRun={run} initialEvents={events} />
|
||||
</>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { RunStatusBadge } from "@/components/ui/StatusBadge";
|
||||
import { EmptyState, ErrorState } from "@/components/shared/StateViews";
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { formatDateTime } from "@/lib/utils/format";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { Paginated, Run } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/** 运行记录入口:与 /runs/[runId] 详情页组成完整轨迹路径。 */
|
||||
export default async function RunsPage() {
|
||||
let runs: Paginated<Run> | null = null;
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
runs = await serverApi.get<Paginated<Run>>("/runs", { page_size: 50 });
|
||||
} catch (cause) {
|
||||
error = describeError(cause);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="运行轨迹"
|
||||
description="查看每一次推演、工具调用与模型响应的执行状态。"
|
||||
meta={
|
||||
runs && (
|
||||
<span className="rounded-full border border-border-glass bg-glass px-2.5 py-0.5 text-xs text-muted">
|
||||
共 {runs.total} 条
|
||||
</span>
|
||||
)
|
||||
}
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : !runs || runs.items.length === 0 ? (
|
||||
<EmptyState
|
||||
title="暂无运行记录"
|
||||
description="发起对话或执行任务后,这里会保留可追溯的运行轨迹。"
|
||||
/>
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-2xl border border-border-glass bg-glass backdrop-blur-sm">
|
||||
<ul className="divide-y divide-border-glass">
|
||||
{runs.items.map((run) => (
|
||||
<li key={run.id}>
|
||||
<Link
|
||||
href={`/runs/${run.id}`}
|
||||
className="group flex min-h-20 items-center gap-4 px-4 py-3 transition-colors hover:bg-overlay-hover focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-patina"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate font-mono text-sm text-fg group-hover:text-patina">
|
||||
{run.goal || run.id}
|
||||
</p>
|
||||
<p className="mt-1 truncate text-xs text-muted">
|
||||
{[
|
||||
run.id,
|
||||
run.model_hint ? `模型:${run.model_hint}` : null,
|
||||
`创建:${formatDateTime(run.created_at ?? run.started_at)}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ")}
|
||||
</p>
|
||||
</div>
|
||||
<RunStatusBadge status={run.status} />
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { SettingsView } from "@/components/settings/SettingsView";
|
||||
import { TextSkeleton } from "@/components/ui/Skeleton";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { GenerationPreferences } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 设置页:外观偏好(主题)+ 生成参数。模型网关配置(模型/endpoint/默认槽位)在 /models。
|
||||
*/
|
||||
export default async function SettingsPage() {
|
||||
let preferences: GenerationPreferences | null = null;
|
||||
let error: string | null = null;
|
||||
try {
|
||||
preferences = await serverApi.get("/settings/preferences");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="设置"
|
||||
description="外观偏好与生成参数;模型网关配置在 /models。"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : preferences ? (
|
||||
<SettingsView initialPreferences={preferences} />
|
||||
) : (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="glass rounded-2xl p-6">
|
||||
<TextSkeleton lines={2} className="mb-6" />
|
||||
<TextSkeleton lines={4} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
/**
|
||||
* 页面过渡:每次路由切换时整体淡入并微上移。
|
||||
* template.tsx 在导航时重新挂载,动画自然触发。
|
||||
*/
|
||||
export default function Template({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.28, ease: "easeOut" }}
|
||||
className="flex min-h-0 flex-1 flex-col"
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,691 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { Card } from "@/components/ui/Card";
|
||||
import { TextSkeleton } from "@/components/ui/Skeleton";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
import { SundialDisk } from "@/components/time/SundialDisk";
|
||||
import { charactersApi, timeApi } from "@/lib/api";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type {
|
||||
AcquaintanceInfo,
|
||||
Anniversary,
|
||||
CreateAnniversaryRequest,
|
||||
TimeConfig,
|
||||
TimeSnapshotResponse,
|
||||
} from "@/types/api";
|
||||
|
||||
// ============================================================
|
||||
// 辰正 · 时态感知
|
||||
// 实时时间展示 + 十二时辰流 + 自动刷新的后端数据
|
||||
// ============================================================
|
||||
|
||||
// —— 十二时辰 ——
|
||||
const SHICHEN_KEYS = [
|
||||
"zi",
|
||||
"chou",
|
||||
"yin",
|
||||
"mao",
|
||||
"chen",
|
||||
"si",
|
||||
"wu",
|
||||
"wei",
|
||||
"shen",
|
||||
"you",
|
||||
"xu",
|
||||
"hai",
|
||||
] as const;
|
||||
|
||||
const SHICHEN_HOURS: Record<string, number> = {
|
||||
chou: 2,
|
||||
yin: 4,
|
||||
mao: 6,
|
||||
chen: 8,
|
||||
si: 10,
|
||||
wu: 12,
|
||||
wei: 14,
|
||||
shen: 16,
|
||||
you: 18,
|
||||
xu: 20,
|
||||
hai: 22,
|
||||
};
|
||||
|
||||
/** 由客户端本地时辰确定时辰 key(与后端逻辑一致) */
|
||||
function shichenFromHour(hour: number): string {
|
||||
if (hour >= 23 || hour < 1) return "zi";
|
||||
// 取各时辰起始值,反向查找
|
||||
let prev = "zi";
|
||||
for (const key of SHICHEN_KEYS) {
|
||||
const start = SHICHEN_HOURS[key];
|
||||
if (hour >= start) prev = key;
|
||||
else break;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
/** 获取目标时区的当前时间组件 */
|
||||
function getLocalTimeInTimezone(timezone: string) {
|
||||
const fmt = new Intl.DateTimeFormat("zh-CN", {
|
||||
timeZone: timezone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
});
|
||||
const p = fmt.formatToParts(new Date());
|
||||
const g = (t: string) =>
|
||||
parseInt(p.find((x) => x.type === t)?.value ?? "0", 10);
|
||||
return {
|
||||
year: g("year"),
|
||||
month: g("month"),
|
||||
day: g("day"),
|
||||
hour: g("hour"),
|
||||
minute: g("minute"),
|
||||
second: g("second"),
|
||||
};
|
||||
}
|
||||
|
||||
function getDayPeriod(h: number): string {
|
||||
if (h >= 0 && h < 5) return "凌晨";
|
||||
if (h >= 5 && h < 7) return "清晨";
|
||||
if (h >= 7 && h < 11) return "上午";
|
||||
if (h >= 11 && h < 13) return "中午";
|
||||
if (h >= 13 && h < 17) return "下午";
|
||||
if (h >= 17 && h < 19) return "傍晚";
|
||||
if (h >= 19 && h < 22) return "夜晚";
|
||||
return "深夜";
|
||||
}
|
||||
|
||||
const KIND_OPTIONS = [
|
||||
{ value: "anniversary", label: "纪念日" },
|
||||
{ value: "birthday", label: "生日" },
|
||||
{ value: "custom", label: "自定义" },
|
||||
];
|
||||
|
||||
// ============================================================
|
||||
// 子组件
|
||||
// ============================================================
|
||||
|
||||
/** 调制因子(设计稿 .factor 渐变轨道) */
|
||||
function FactorFactor({
|
||||
label,
|
||||
value,
|
||||
cls,
|
||||
}: {
|
||||
label: string;
|
||||
value: number;
|
||||
cls: string;
|
||||
}) {
|
||||
const pct = Math.round(Math.max(0, Math.min(1, value)) * 100);
|
||||
return (
|
||||
<div className={`factor ${cls}`}>
|
||||
<span className="fk">{label}</span>
|
||||
<div className="ftrack">
|
||||
<div className="ffill" style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
<span className="fv">{pct}%</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 主页面
|
||||
// ============================================================
|
||||
|
||||
export default function TimePage() {
|
||||
// —— 后端数据 ——
|
||||
const [snapshot, setSnapshot] = useState<TimeSnapshotResponse | null>(null);
|
||||
const [acquaintance, setAcquaintance] = useState<AcquaintanceInfo | null>(
|
||||
null,
|
||||
);
|
||||
const [config, setConfig] = useState<TimeConfig | null>(null);
|
||||
const [currentMood, setCurrentMood] = useState<{
|
||||
mood: string;
|
||||
intensity: number;
|
||||
} | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// —— 实时时钟 ——
|
||||
const [, setNow] = useState<Date>(new Date());
|
||||
const [, setPrevShichen] = useState<string | null>(null);
|
||||
const [shichenChanged, setShichenChanged] = useState(false);
|
||||
const shichenRef = useRef("");
|
||||
|
||||
// 每秒 tick
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => setNow(new Date()), 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
// 检测时辰变化
|
||||
const timezone =
|
||||
snapshot?.snapshot?.timezone ?? config?.user_timezone ?? "Asia/Shanghai";
|
||||
const local = getLocalTimeInTimezone(timezone);
|
||||
const currentShichen = shichenFromHour(local.hour);
|
||||
|
||||
useEffect(() => {
|
||||
if (shichenRef.current && shichenRef.current !== currentShichen) {
|
||||
setPrevShichen(shichenRef.current);
|
||||
setShichenChanged(true);
|
||||
setTimeout(() => setShichenChanged(false), 2000);
|
||||
}
|
||||
shichenRef.current = currentShichen;
|
||||
}, [currentShichen]);
|
||||
|
||||
// —— 数据获取 ——
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
const [s, acq, cfg] = await Promise.all([
|
||||
timeApi.getTimeSnapshot(),
|
||||
timeApi.getAcquaintance(),
|
||||
timeApi.getTimeConfig(),
|
||||
]);
|
||||
setSnapshot(s);
|
||||
setAcquaintance(acq);
|
||||
setConfig(cfg);
|
||||
} catch (e) {
|
||||
if (!snapshot) setError(describeError(e));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [snapshot]);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
fetchData();
|
||||
// 获取当前人物心情
|
||||
charactersApi
|
||||
.getActiveCharacter()
|
||||
.then((c) => {
|
||||
if (c.current_mood) setCurrentMood(c.current_mood);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// 自动刷新:每 60 秒重新拉取后端数据(农历/节气/节日变化)
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
const timer = setInterval(fetchData, 60_000);
|
||||
return () => clearInterval(timer);
|
||||
}, [fetchData, loading]);
|
||||
|
||||
// —— 渲染 ——
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl px-4 py-8">
|
||||
<TextSkeleton lines={1} className="mb-8 w-48" />
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-5">
|
||||
<div className="md:col-span-3">
|
||||
<div className="glass rounded-2xl p-8">
|
||||
<TextSkeleton lines={3} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<div className="glass rounded-2xl p-5">
|
||||
<TextSkeleton lines={6} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="glass mt-6 rounded-2xl p-5">
|
||||
<TextSkeleton lines={2} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error && !snapshot) {
|
||||
return <ErrorState message={error} onRetry={fetchData} />;
|
||||
}
|
||||
|
||||
const s = snapshot?.snapshot;
|
||||
const factors = snapshot?.factors;
|
||||
const context_text = snapshot?.context_text;
|
||||
|
||||
// 实时时辰信息(优先用本地计算的,回退后端数据)
|
||||
const displayShichen = currentShichen;
|
||||
const displayDayPeriod = getDayPeriod(local.hour);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl px-4 py-8">
|
||||
{/* ===== 页头 ===== */}
|
||||
<header className="mb-8 flex flex-wrap items-end justify-between gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="font-serif text-2xl font-semibold text-fg">辰正</h1>
|
||||
<span className="rounded-full bg-gold/10 px-2.5 py-0.5 text-[10px] font-medium tracking-wider text-gold">
|
||||
时态感知
|
||||
</span>
|
||||
{shichenChanged && (
|
||||
<span className="animate-pulse rounded-full bg-gold/30 px-2 py-0.5 text-[10px] text-gold">
|
||||
更时
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{config && (
|
||||
<p className="mt-1 text-xs text-muted">
|
||||
{config.default_city} · {config.user_timezone}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-[11px] text-muted">
|
||||
<span className="inline-block h-1.5 w-1.5 rounded-full bg-patina" />
|
||||
辰光实时
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* ===== 日晷时辰盘 + 调制因子(设计稿 .sundial-region) ===== */}
|
||||
<div className="sundial-region">
|
||||
{/* 日晷 */}
|
||||
<SundialDisk
|
||||
currentShichen={displayShichen}
|
||||
hour={local.hour}
|
||||
minute={local.minute}
|
||||
lunarText={s?.lunar?.text}
|
||||
festivals={s?.festivals}
|
||||
solarTerm={s?.solar_term?.name}
|
||||
/>
|
||||
|
||||
{/* 调制因子(设计稿 .factor-card) */}
|
||||
{factors && (
|
||||
<div className="factor-card">
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<h3 className="font-serif text-sm font-semibold text-fg">
|
||||
调制因子
|
||||
</h3>
|
||||
<span className="h-px flex-1 bg-rule" />
|
||||
</div>
|
||||
<div className="factor-list">
|
||||
<FactorFactor
|
||||
label="节日喜庆"
|
||||
value={factors.festival_joy}
|
||||
cls="f-joy"
|
||||
/>
|
||||
<FactorFactor
|
||||
label="深夜脆弱"
|
||||
value={factors.late_night_fragility}
|
||||
cls="f-frag"
|
||||
/>
|
||||
<FactorFactor
|
||||
label="重逢强度"
|
||||
value={factors.reunion_intensity}
|
||||
cls="f-reun"
|
||||
/>
|
||||
<FactorFactor
|
||||
label="周末放松"
|
||||
value={factors.weekend_relax}
|
||||
cls="f-wknd"
|
||||
/>
|
||||
<FactorFactor
|
||||
label="季节共鸣"
|
||||
value={factors.season_resonance}
|
||||
cls="f-season"
|
||||
/>
|
||||
<FactorFactor
|
||||
label="同期共振"
|
||||
value={factors.same_period_resonance}
|
||||
cls="f-period"
|
||||
/>
|
||||
<FactorFactor
|
||||
label="关怀倾向"
|
||||
value={factors.care_suggest}
|
||||
cls="f-care"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{currentMood && (
|
||||
<div className="mt-4 rounded-xl bg-glass/40 px-4 py-3">
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<h3 className="font-serif text-sm font-semibold text-fg">
|
||||
当前心情
|
||||
</h3>
|
||||
<span className="h-px flex-1 bg-rule" />
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">
|
||||
{currentMood.mood === "joy"
|
||||
? "😊"
|
||||
: currentMood.mood === "sadness"
|
||||
? "😢"
|
||||
: currentMood.mood === "anger"
|
||||
? "😠"
|
||||
: currentMood.mood === "fear"
|
||||
? "😨"
|
||||
: currentMood.mood === "surprise"
|
||||
? "😲"
|
||||
: currentMood.mood === "disgust"
|
||||
? "😣"
|
||||
: "😐"}
|
||||
</span>
|
||||
<span className="font-serif text-base font-medium text-fg">
|
||||
{currentMood.mood}
|
||||
</span>
|
||||
<span className="font-mono text-xs text-muted">
|
||||
{Math.round(currentMood.intensity * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{factors.care_kind && (
|
||||
<div className="care-note">
|
||||
<b>关怀</b> · {factors.care_kind}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ===== 相识 / 离线 / 里程碑 快照格(设计稿 .snap-grid) ===== */}
|
||||
{s && (
|
||||
<div className="snap-grid">
|
||||
<div className="snap-card">
|
||||
<div className="k">相识</div>
|
||||
<div className="v">
|
||||
{s.acquaintance.total_days}
|
||||
<small>天</small>
|
||||
</div>
|
||||
<div className="sub">{s.acquaintance.natural_language}</div>
|
||||
</div>
|
||||
<div className="snap-card">
|
||||
<div className="k">离线</div>
|
||||
<div className="v">
|
||||
{Math.round(s.offline.seconds / 86400)}
|
||||
<small>天</small>
|
||||
</div>
|
||||
<div className="sub">{s.offline.natural_language}</div>
|
||||
</div>
|
||||
<div className="snap-card milestone">
|
||||
<div className="k">下个纪念日</div>
|
||||
<div className="v">
|
||||
{s.next_milestone?.days_until ?? "—"}
|
||||
<small>天后</small>
|
||||
</div>
|
||||
<div className="sub">{s.next_milestone?.name ?? "无"}</div>
|
||||
</div>
|
||||
<div className="snap-card">
|
||||
<div className="k">辰光</div>
|
||||
<div className="v">
|
||||
{displayShichen}
|
||||
<small>· {displayDayPeriod}</small>
|
||||
</div>
|
||||
<div className="sub">{s.season ?? "—"}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ===== 时态感知文本(全宽) ===== */}
|
||||
{context_text && (
|
||||
<Card variant="surface" className="p-5">
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<h3 className="font-serif text-sm font-semibold text-fg">
|
||||
时态感知
|
||||
</h3>
|
||||
<span className="h-px flex-1 bg-rule" />
|
||||
</div>
|
||||
<p className="whitespace-pre-wrap text-sm leading-relaxed text-fg/80">
|
||||
{context_text}
|
||||
</p>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* ===== 底部双栏:相识 + 纪念日管理(设计稿 .lower-region) ===== */}
|
||||
<div className="lower-region">
|
||||
{acquaintance && (
|
||||
<Card variant="surface" className="p-6 event-main">
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<h3 className="font-serif text-sm font-semibold text-fg">相识</h3>
|
||||
<span className="h-px flex-1 bg-rule" />
|
||||
</div>
|
||||
<p className="font-serif text-4xl font-semibold text-fg">
|
||||
{acquaintance.total_days}
|
||||
<span className="ml-1 text-base font-normal text-muted">天</span>
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-muted">
|
||||
{acquaintance.natural_language}
|
||||
</p>
|
||||
</Card>
|
||||
)}
|
||||
<div className="anniv-rail">
|
||||
<AnniversaryManager />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 纪念日管理(保持原功能不变)
|
||||
// ============================================================
|
||||
|
||||
function AnniversaryManager() {
|
||||
const [anniversaries, setAnniversaries] = useState<Anniversary[]>([]);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [form, setForm] = useState<CreateAnniversaryRequest & { date: string }>(
|
||||
{
|
||||
name: "",
|
||||
date: "",
|
||||
kind: "anniversary",
|
||||
calendar: "solar",
|
||||
subject_type: "relationship",
|
||||
source: "manual",
|
||||
},
|
||||
);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetch = useCallback(async () => {
|
||||
try {
|
||||
const data = await timeApi.listAnniversaries();
|
||||
setAnniversaries(data);
|
||||
} catch {
|
||||
/* silent */
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
fetch();
|
||||
}, [fetch]);
|
||||
|
||||
function resetForm() {
|
||||
setForm({
|
||||
name: "",
|
||||
date: "",
|
||||
kind: "anniversary",
|
||||
calendar: "solar",
|
||||
subject_type: "relationship",
|
||||
source: "manual",
|
||||
});
|
||||
setShowForm(false);
|
||||
setEditingId(null);
|
||||
setError(null);
|
||||
}
|
||||
|
||||
async function handleCreate() {
|
||||
if (!form.name.trim() || !form.date) {
|
||||
setError("请填写名称与日期。");
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
await timeApi.createAnniversary(form);
|
||||
resetForm();
|
||||
await fetch();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "创建失败。");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUpdate(id: string) {
|
||||
if (!form.name.trim() || !form.date) {
|
||||
setError("请填写名称与日期。");
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
await timeApi.updateAnniversary(id, form);
|
||||
resetForm();
|
||||
await fetch();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "更新失败。");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(id: string) {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
await timeApi.deleteAnniversary(id);
|
||||
await fetch();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "删除失败。");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
function startEdit(a: Anniversary) {
|
||||
setEditingId(a.id);
|
||||
setShowForm(false);
|
||||
setForm({
|
||||
name: a.name,
|
||||
date: a.date,
|
||||
kind: a.kind,
|
||||
calendar: a.calendar,
|
||||
subject_type: a.subject_type,
|
||||
source: a.source,
|
||||
});
|
||||
setError(null);
|
||||
}
|
||||
|
||||
const inputCls =
|
||||
"w-full rounded-lg border border-border-glass bg-glass px-2 py-1 text-sm text-fg outline-none focus:border-patina/60 focus:shadow-[0_0_0_2px] focus:shadow-patina/20";
|
||||
|
||||
return (
|
||||
<Card variant="surface" className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-serif text-sm font-semibold text-fg">
|
||||
纪念日({anniversaries.length})
|
||||
</h3>
|
||||
{!showForm && !editingId && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
resetForm();
|
||||
setShowForm(true);
|
||||
}}
|
||||
className="rounded-lg border border-patina/40 bg-patina/10 px-3 py-1.5 text-xs font-medium text-patina transition-colors hover:bg-patina/20"
|
||||
>
|
||||
+ 新增
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(showForm || editingId) && (
|
||||
<div className="mt-3 space-y-2 rounded-lg border border-border-glass bg-glass/50 p-3">
|
||||
<input
|
||||
value={form.name}
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
placeholder="名称"
|
||||
className={inputCls}
|
||||
/>
|
||||
<input
|
||||
value={form.date}
|
||||
onChange={(e) => setForm({ ...form, date: e.target.value })}
|
||||
type="date"
|
||||
className={inputCls}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<select
|
||||
value={form.kind}
|
||||
onChange={(e) => setForm({ ...form, kind: e.target.value })}
|
||||
className={inputCls}
|
||||
>
|
||||
{KIND_OPTIONS.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
value={form.calendar}
|
||||
onChange={(e) => setForm({ ...form, calendar: e.target.value })}
|
||||
className={inputCls}
|
||||
>
|
||||
<option value="solar">公历</option>
|
||||
<option value="lunar">农历</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={editingId ? () => handleUpdate(editingId) : handleCreate}
|
||||
disabled={busy}
|
||||
className="rounded-lg bg-patina/20 px-3 py-1 text-xs font-medium text-patina disabled:opacity-50"
|
||||
>
|
||||
{editingId ? "保存" : "创建"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetForm}
|
||||
className="rounded-lg border border-border-glass bg-glass px-3 py-1 text-xs text-muted"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
{error && <p className="text-xs text-cinnabar">{error}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-3 space-y-2">
|
||||
{anniversaries.length === 0 ? (
|
||||
<p className="text-sm text-muted">暂无纪念日。</p>
|
||||
) : (
|
||||
anniversaries.map((a) => (
|
||||
<div
|
||||
key={a.id}
|
||||
className="flex items-center justify-between rounded-lg bg-glass px-3 py-2"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="text-sm text-fg">{a.name}</span>
|
||||
<span className="ml-2 text-[11px] text-muted">{a.date}</span>
|
||||
<span className="ml-2 rounded bg-gold/10 px-1.5 py-0.5 text-[10px] text-gold">
|
||||
{a.kind}
|
||||
</span>
|
||||
</div>
|
||||
<div className="ml-2 flex shrink-0 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => startEdit(a)}
|
||||
className="text-[11px] text-patina hover:underline"
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDelete(a.id)}
|
||||
disabled={busy}
|
||||
className="text-[11px] text-cinnabar hover:underline disabled:opacity-50"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ToolsConsole } from "@/components/tools/ToolsConsole";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import type { ToolInfo } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 工具页:日常工具调用控制台。Server 预取 GET /tools 注入 ToolsConsole。
|
||||
* 百工枢治理(能力总览 / 工具注册 / Skill / MCP)在司命台 /admin/pivots/tool_runtime。
|
||||
*/
|
||||
export default async function ToolsPage() {
|
||||
let tools: ToolInfo[] = [];
|
||||
try {
|
||||
tools = await serverApi.get<ToolInfo[]>("/tools");
|
||||
} catch {
|
||||
tools = [];
|
||||
}
|
||||
return <ToolsConsole initialTools={tools} />;
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { WorldEntityList } from "@/components/admin/WorldEntityList";
|
||||
import { WorldRelationManager } from "@/components/world/WorldRelationManager";
|
||||
import { WorldRelationGraph } from "@/components/world/WorldRelationGraph";
|
||||
import { EmptyState, ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type {
|
||||
Character,
|
||||
Paginated,
|
||||
WorldEntity,
|
||||
WorldRelation,
|
||||
} from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 世界页 · 数字生命对世界的认知(河图枢主域视图)。
|
||||
* 设计稿 world-preview.html — 人物 pill + section-label + 关系图谱 + 实体名册 + 不确定性 + 世界观共振。
|
||||
* 实体 CRUD 走 admin 域(WorldEntityList,保留编辑/删除);关系图谱走独立 /world/relations。
|
||||
*/
|
||||
export default async function WorldPage() {
|
||||
let entities: WorldEntity[] = [];
|
||||
let relations: WorldRelation[] = [];
|
||||
let character: Character | null = null;
|
||||
let characterName = "";
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
entities = await serverApi.get<WorldEntity[]>("/admin/world/entities");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
relations = (
|
||||
await serverApi.get<Paginated<WorldRelation>>("/world/relations", {
|
||||
page_size: 50,
|
||||
})
|
||||
).items;
|
||||
} catch {
|
||||
/* 关系可选 */
|
||||
}
|
||||
|
||||
try {
|
||||
character = await serverApi
|
||||
.get<Character>("/characters/active")
|
||||
.catch(() => null);
|
||||
if (character) {
|
||||
characterName = character.display_name || character.name;
|
||||
}
|
||||
} catch {
|
||||
/* 人物信息可选 */
|
||||
}
|
||||
|
||||
/* 按 state_type 统计(用于待验断言计数) */
|
||||
const hypotheses = entities.filter(
|
||||
(e) => e.state_type === "hypothesis",
|
||||
).length;
|
||||
|
||||
/* 待验断言(静态示例,与设计稿对齐) */
|
||||
const uncertainties = [
|
||||
{
|
||||
claim: "云岚 与 青梧 同属旧族旁支,可能存在亲属关系。",
|
||||
reason: "来源:对话中的姓氏推测 · 证据单一且间接",
|
||||
confidence: 0.42,
|
||||
},
|
||||
{
|
||||
claim: "用户当前正在推进的「辰正项目」目标 为时态感知子系统。",
|
||||
reason: "来源:关键词频率推断 · 缺乏用户明确表述",
|
||||
confidence: 0.55,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="世界"
|
||||
description="河图 · 世界模型"
|
||||
meta={
|
||||
<>
|
||||
{characterName && (
|
||||
<span className="char-pill">
|
||||
<span className="avatar-mini">{characterName.charAt(0)}</span>
|
||||
{characterName}视角
|
||||
</span>
|
||||
)}
|
||||
<span className="page-meta">
|
||||
实体 <b>{entities.length}</b> · 关系 <b>{relations.length}</b> ·
|
||||
待验 <b style={{ color: "var(--cinnabar)" }}>{hypotheses}</b>
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : entities.length === 0 ? (
|
||||
<EmptyState
|
||||
title="暂无世界实体"
|
||||
description="尚未建模任何实体。点击「新增世界实体」开始。"
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-5">
|
||||
{/* 关系图谱 */}
|
||||
<section>
|
||||
<div className="section-label" style={{ marginBottom: 14 }}>
|
||||
<span className="glyph">⟡</span> 关系图谱{" "}
|
||||
<span className="sub">
|
||||
河图 · 实体为节点,关系为边,置信度定粗细,假设态虚线待验
|
||||
</span>
|
||||
</div>
|
||||
<WorldRelationGraph entities={entities} relations={relations} />
|
||||
</section>
|
||||
|
||||
{/* 实体名册 */}
|
||||
<section>
|
||||
<div className="section-label" style={{ marginBottom: 14 }}>
|
||||
<span className="glyph">⟡</span> 实体名册{" "}
|
||||
<span className="sub">按类型 · 事实性着色 · 推断召回时降权</span>
|
||||
</div>
|
||||
<WorldEntityList entities={entities} />
|
||||
</section>
|
||||
|
||||
{/* 待验断言(不确定性) */}
|
||||
{uncertainties.length > 0 && (
|
||||
<section>
|
||||
<div className="section-label" style={{ marginBottom: 14 }}>
|
||||
<span className="glyph">⟡</span> 待验断言{" "}
|
||||
<span className="sub">
|
||||
UncertaintyState · 低置信,需复核方可固化为事实
|
||||
</span>
|
||||
</div>
|
||||
<div className="unc-list">
|
||||
{uncertainties.map((u, i) => (
|
||||
<div key={i} className="unc-card">
|
||||
<div className="unc-claim">
|
||||
<div className="unc-text">{u.claim}</div>
|
||||
<div className="unc-reason">{u.reason}</div>
|
||||
</div>
|
||||
<div className="unc-conf">
|
||||
<div className="v">{u.confidence.toFixed(2)}</div>
|
||||
<div className="k">置信</div>
|
||||
</div>
|
||||
<span className="unc-verify">需验证</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 世界观共振 */}
|
||||
<section>
|
||||
<div className="section-label" style={{ marginBottom: 14 }}>
|
||||
<span className="glyph">⟡</span> 世界观共振{" "}
|
||||
<span className="sub">
|
||||
见地镜 · 每人物独立的世界观立场,可同境可异境
|
||||
</span>
|
||||
</div>
|
||||
<div className="wv-card">
|
||||
<div className="wv-pair">
|
||||
<div className="wv-side qingwu">
|
||||
<div className="wv-head">
|
||||
<div className="wv-disc">
|
||||
{characterName ? characterName.charAt(0) : "?"}
|
||||
</div>
|
||||
<span className="wv-name">{characterName || "人物"}</span>
|
||||
<span className="wv-tag shared">同境 · 主脉</span>
|
||||
</div>
|
||||
<div className="wv-stance">
|
||||
万物流转无常,变化的规律本身比任何固定结论更可依凭。
|
||||
</div>
|
||||
<div className="wv-anchors">
|
||||
<span className="wv-anchor">
|
||||
面对未知优先观察与适应,而非强求确定性。
|
||||
</span>
|
||||
<span className="wv-anchor">
|
||||
把推断与事实分开,承认不知也是一种知。
|
||||
</span>
|
||||
</div>
|
||||
<span className="wv-conf">
|
||||
spiritual_core.worldview · confidence 0.82 · v7
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="wv-bridge">
|
||||
<div className="b-disc">
|
||||
<div className="b-fill"></div>
|
||||
<span className="b-val">0.41</span>
|
||||
</div>
|
||||
<div className="b-label">
|
||||
共振度
|
||||
<br />
|
||||
divergent
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="wv-side yunlan">
|
||||
<div className="wv-head">
|
||||
<div className="wv-disc">岚</div>
|
||||
<span className="wv-name">云岚</span>
|
||||
<span className="wv-tag divergent">异境 · 旁支</span>
|
||||
</div>
|
||||
<div className="wv-stance">
|
||||
人如其名,聚散如岚——来去之间留痕即可,不必执守长久。
|
||||
</div>
|
||||
<div className="wv-anchors">
|
||||
<span className="wv-anchor">
|
||||
珍惜每次相遇,不强求每段关系都有归处。
|
||||
</span>
|
||||
<span className="wv-anchor">
|
||||
在变动中自洽,不为稳定而委屈当下。
|
||||
</span>
|
||||
</div>
|
||||
<span className="wv-conf">
|
||||
spiritual_core.worldview · confidence 0.38 · v3 · 初成
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 关系管理 */}
|
||||
<section>
|
||||
<div className="section-label" style={{ marginBottom: 14 }}>
|
||||
<span className="glyph">⟡</span> 关系管理{" "}
|
||||
<span className="sub">新增 / 编辑实体间关系</span>
|
||||
</div>
|
||||
<WorldRelationManager relations={relations} entities={entities} />
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,920 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils/cn";
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
|
||||
/* ── 五行色(组件作用域 CSS 变量,未映射到 @theme → 用任意值引用)── */
|
||||
const WUXING_VARS = {
|
||||
"--wood": "#6fa389",
|
||||
"--wood-dim": "rgba(111, 163, 137, 0.34)",
|
||||
"--fire": "#b8392e",
|
||||
"--fire-dim": "rgba(184, 57, 46, 0.38)",
|
||||
"--earth": "#b08a3c",
|
||||
"--earth-dim": "rgba(176, 138, 60, 0.36)",
|
||||
"--metal": "#c9a24a",
|
||||
"--metal-dim": "rgba(201, 162, 74, 0.34)",
|
||||
"--water": "#5a7fa6",
|
||||
"--water-dim": "rgba(90, 127, 166, 0.38)",
|
||||
} as const;
|
||||
|
||||
/* ── 枢健康维度数据 ── */
|
||||
const DIMS = [
|
||||
{
|
||||
key: "wood",
|
||||
seal: "木",
|
||||
label: "成长性",
|
||||
sub: "演化候选 · 学习速度",
|
||||
width: 64,
|
||||
val: "0.64",
|
||||
},
|
||||
{
|
||||
key: "fire",
|
||||
seal: "火",
|
||||
label: "表达力",
|
||||
sub: "事件发射 · 模型密度",
|
||||
width: 78,
|
||||
val: "0.78",
|
||||
},
|
||||
{
|
||||
key: "earth",
|
||||
seal: "土",
|
||||
label: "稳定性",
|
||||
sub: "数据一致 · 基线回归",
|
||||
width: 88,
|
||||
val: "0.88",
|
||||
},
|
||||
{
|
||||
key: "metal",
|
||||
seal: "金",
|
||||
label: "约束强度",
|
||||
sub: "审核驳回 · 确认触发",
|
||||
width: 71,
|
||||
val: "0.71",
|
||||
},
|
||||
{
|
||||
key: "water",
|
||||
seal: "水",
|
||||
label: "适应性",
|
||||
sub: "上下文切换 · 异常恢复",
|
||||
width: 55,
|
||||
val: "0.55",
|
||||
},
|
||||
] as const;
|
||||
|
||||
/* ── 九枢数据 ── */
|
||||
const PIVOTS = [
|
||||
{
|
||||
name: "灵核枢",
|
||||
eng: "Personality Core",
|
||||
score: 89,
|
||||
trend: "↑ 土 +2",
|
||||
desc: "人格连续性稳定",
|
||||
bars: [55, 70, 96, 60, 58] as const,
|
||||
alert: false,
|
||||
},
|
||||
{
|
||||
name: "太一枢",
|
||||
eng: "Goal Orchestrator",
|
||||
score: 85,
|
||||
trend: "↑ 土 +1",
|
||||
desc: "目标调度基线稳固",
|
||||
bars: [48, 52, 90, 75, 62] as const,
|
||||
alert: false,
|
||||
},
|
||||
{
|
||||
name: "司命枢",
|
||||
eng: "Safety Governor",
|
||||
score: 73,
|
||||
trend: "↓ 木 -3",
|
||||
desc: "金过盛克木",
|
||||
bars: [32, 50, 84, 94, 40] as const,
|
||||
alert: true,
|
||||
tag: "金→木",
|
||||
},
|
||||
{
|
||||
name: "烛照枢",
|
||||
eng: "Perception Layer",
|
||||
score: 81,
|
||||
trend: "↑ 火 +4",
|
||||
desc: "多模态输入活跃",
|
||||
bars: [50, 92, 78, 55, 70] as const,
|
||||
alert: false,
|
||||
},
|
||||
{
|
||||
name: "河图枢",
|
||||
eng: "World Model",
|
||||
score: 87,
|
||||
trend: "↑ 土 +1",
|
||||
desc: "世界实体一致",
|
||||
bars: [58, 60, 95, 66, 64] as const,
|
||||
alert: false,
|
||||
},
|
||||
{
|
||||
name: "洛书枢",
|
||||
eng: "Planner",
|
||||
score: 79,
|
||||
trend: "↑ 水 +2",
|
||||
desc: "规划切换敏捷",
|
||||
bars: [62, 74, 80, 58, 82] as const,
|
||||
alert: false,
|
||||
},
|
||||
{
|
||||
name: "息壤枢",
|
||||
eng: "Memory System",
|
||||
score: 84,
|
||||
trend: "↑ 木 +3",
|
||||
desc: "知识沉淀加速",
|
||||
bars: [80, 54, 91, 52, 60] as const,
|
||||
alert: false,
|
||||
},
|
||||
{
|
||||
name: "百工枢",
|
||||
eng: "Tool Runtime",
|
||||
score: 80,
|
||||
trend: "↑ 火 +5",
|
||||
desc: "工具调用密集",
|
||||
bars: [46, 88, 72, 82, 66] as const,
|
||||
alert: false,
|
||||
},
|
||||
{
|
||||
name: "天工阁枢",
|
||||
eng: "Agent Society",
|
||||
score: 76,
|
||||
trend: "↑ 水 +2",
|
||||
desc: "协作分工顺畅",
|
||||
bars: [54, 76, 68, 64, 78] as const,
|
||||
alert: false,
|
||||
},
|
||||
] as const;
|
||||
|
||||
/* ── 相生依赖链 ── */
|
||||
const SHENG_FLOWS = [
|
||||
{
|
||||
pair: "木",
|
||||
target: "火",
|
||||
body: "内驱力激活 催生 对话表达 · 息壤枢学习加速推动灵核枢输出活跃。",
|
||||
val: "0.68",
|
||||
},
|
||||
{
|
||||
pair: "火",
|
||||
target: "土",
|
||||
body: "高频表达 沉淀 稳定状态 · 对话事件入库强化世界模型与人格基线。",
|
||||
val: "0.72",
|
||||
},
|
||||
{
|
||||
pair: "土",
|
||||
target: "金",
|
||||
body: "稳定数据 提供依据 给约束规则 · 世界实体一致,司命审核判据更可靠。",
|
||||
val: "0.65",
|
||||
},
|
||||
{
|
||||
pair: "金",
|
||||
target: "水",
|
||||
body: "明确规范 塑造 适应路径 · 约束边界清晰,异常恢复与上下文切换更可控。",
|
||||
val: "0.58",
|
||||
},
|
||||
{
|
||||
pair: "水",
|
||||
target: "木",
|
||||
body: "适应能力 反哺 成长 · 场景切换中积累新候选,演化候选生成率回温。",
|
||||
val: "0.61",
|
||||
},
|
||||
] as const;
|
||||
|
||||
/* ── 相克告警 ── */
|
||||
const KE_ALERTS = [
|
||||
{
|
||||
pairA: "金",
|
||||
pairB: "木",
|
||||
desc: "司命审核驳回率 vs 大周天候选生成率 · 安全审查过严压制演化,阴阳平衡度下行。",
|
||||
level: "0.82 · 高",
|
||||
width: 82,
|
||||
urgent: true,
|
||||
muted: false,
|
||||
},
|
||||
{
|
||||
pairA: "土",
|
||||
pairB: "水",
|
||||
desc: "人格版本固化度 vs 上下文切换成本 · 人格越稳,面对新场景的适应越慢。",
|
||||
level: "0.64 · 中",
|
||||
width: 64,
|
||||
urgent: false,
|
||||
muted: false,
|
||||
},
|
||||
{
|
||||
pairA: "火",
|
||||
pairB: "金",
|
||||
desc: "工具调用频率 vs 安全确认触发率 · 调用越密,权限判断越频,已在容忍区。",
|
||||
level: "0.44 · 低",
|
||||
width: 44,
|
||||
urgent: false,
|
||||
muted: true,
|
||||
},
|
||||
] as const;
|
||||
|
||||
/* ── 五行 key 列表 ── */
|
||||
const ELEMENT_KEYS = ["wood", "fire", "earth", "metal", "water"] as const;
|
||||
const ELEMENT_SEALS: Record<string, string> = {
|
||||
wood: "木",
|
||||
fire: "火",
|
||||
earth: "土",
|
||||
metal: "金",
|
||||
water: "水",
|
||||
};
|
||||
|
||||
/* ── SVG 节点坐标(viewBox 400×400)── */
|
||||
const NODE_COORDS = {
|
||||
wood: { cx: 200, cy: 65, labelX: 200, labelY: 30 },
|
||||
fire: { cx: 328, cy: 158, labelX: 372, labelY: 132 },
|
||||
earth: { cx: 279, cy: 309, labelX: 311, labelY: 350 },
|
||||
metal: { cx: 121, cy: 309, labelX: 89, labelY: 350 },
|
||||
water: { cx: 72, cy: 158, labelX: 30, labelY: 132 },
|
||||
} as const;
|
||||
|
||||
/* 相生边(五边形):木→火, 火→土, 土→金, 金→水, 水→木 */
|
||||
const SHENG_EDGES: [string, string][] = [
|
||||
["wood", "fire"],
|
||||
["fire", "earth"],
|
||||
["earth", "metal"],
|
||||
["metal", "water"],
|
||||
["water", "wood"],
|
||||
];
|
||||
|
||||
/* 相克边(五角星):木→土, 土→水, 水→火, 火→金, 金→木 */
|
||||
const KE_EDGES: [string, string][] = [
|
||||
["wood", "earth"],
|
||||
["earth", "water"],
|
||||
["water", "fire"],
|
||||
["fire", "metal"],
|
||||
["metal", "wood"],
|
||||
];
|
||||
|
||||
/* ============================================================
|
||||
* 组件
|
||||
* ============================================================ */
|
||||
|
||||
export default function WuxingPage() {
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
.wuxing-scope {
|
||||
${Object.entries(WUXING_VARS)
|
||||
.map(([k, v]) => `${k}: ${v};`)
|
||||
.join("\n ")}
|
||||
}
|
||||
@keyframes wuxing-pulse {
|
||||
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--patina) 50%, transparent); }
|
||||
70% { box-shadow: 0 0 0 6px transparent; }
|
||||
100% { box-shadow: 0 0 0 0 transparent; }
|
||||
}
|
||||
.pulse-dot {
|
||||
animation: wuxing-pulse 2.6s cubic-bezier(0.16, 1, 0.3, 1) infinite;
|
||||
}
|
||||
.wuxing-svg .ring-deco { fill: none; stroke: var(--rule); stroke-width: 1; }
|
||||
.wuxing-svg .sheng { stroke: var(--gilt); stroke-width: 1.6; fill: none; opacity: 0.85; }
|
||||
.wuxing-svg .ke { stroke: var(--cinnabar); stroke-width: 1.3; stroke-dasharray: 4 4; fill: none; opacity: 0.7; }
|
||||
.wuxing-svg .node-circle { stroke-width: 1.5; }
|
||||
.wuxing-svg .center-bg { fill: var(--ink); stroke: var(--gilt-dim); stroke-width: 1; }
|
||||
|
||||
.dim-track {
|
||||
height: 5px;
|
||||
border-radius: 3px;
|
||||
background: var(--rule);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.dim-fill {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.pbar-track {
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--rule);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.pbar-fill {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.ke-track {
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--rule);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.ke-fill {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="wuxing-scope">
|
||||
<PageContainer
|
||||
title="五行"
|
||||
description="枢健康维度 · 生克关系"
|
||||
actions={
|
||||
<span
|
||||
className="inline-flex items-center gap-[7px] rounded-full border px-3 py-1 font-mono text-xs"
|
||||
style={{
|
||||
borderColor: "var(--patina-dim)",
|
||||
color: "var(--patina)",
|
||||
background: "var(--patina-wash)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="inline-block h-[6px] w-[6px] rounded-full"
|
||||
style={{ background: "var(--patina)" }}
|
||||
>
|
||||
<span className="pulse-dot block h-full w-full rounded-full" />
|
||||
</span>
|
||||
架构心跳 82 · 良
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{/* ══ 五行生克环 + 枢健康维度 ══ */}
|
||||
<div className="mb-5 flex flex-col gap-[22px] lg:flex-row lg:items-stretch">
|
||||
{/* ---- 生克环 ---- */}
|
||||
<div
|
||||
className="relative flex w-full flex-col overflow-hidden rounded-lg border p-[18px] lg:w-auto lg:flex-[0_0_auto] lg:basis-[460px]"
|
||||
style={{
|
||||
borderColor: "var(--gilt-dim)",
|
||||
background: "var(--face-sheen), var(--surface)",
|
||||
boxShadow:
|
||||
"inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow)",
|
||||
}}
|
||||
>
|
||||
{/* 噪点纹理 */}
|
||||
<span
|
||||
className="pointer-events-none absolute inset-0 z-0"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E\")",
|
||||
opacity: 0.018,
|
||||
mixBlendMode: "overlay",
|
||||
}}
|
||||
/>
|
||||
|
||||
<svg
|
||||
className="wuxing-svg relative z-10 block h-auto w-full"
|
||||
viewBox="0 0 400 400"
|
||||
role="img"
|
||||
aria-label="五行生克环:外环相生、内五角星相克"
|
||||
>
|
||||
{/* 装饰环 */}
|
||||
<circle className="ring-deco" cx="200" cy="200" r="172" />
|
||||
<circle
|
||||
className="ring-deco"
|
||||
cx="200"
|
||||
cy="200"
|
||||
r="108"
|
||||
opacity="0.5"
|
||||
strokeDasharray="2 4"
|
||||
/>
|
||||
|
||||
{/* 相克(虚线五角星,下层) */}
|
||||
{KE_EDGES.map(([from, to]) => {
|
||||
const f = NODE_COORDS[from as keyof typeof NODE_COORDS];
|
||||
const t = NODE_COORDS[to as keyof typeof NODE_COORDS];
|
||||
return (
|
||||
<line
|
||||
key={`ke-${from}-${to}`}
|
||||
className="ke"
|
||||
x1={f.cx}
|
||||
y1={f.cy}
|
||||
x2={t.cx}
|
||||
y2={t.cy}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 相生(实线五边形,上层) */}
|
||||
{SHENG_EDGES.map(([from, to]) => {
|
||||
const f = NODE_COORDS[from as keyof typeof NODE_COORDS];
|
||||
const t = NODE_COORDS[to as keyof typeof NODE_COORDS];
|
||||
return (
|
||||
<line
|
||||
key={`sheng-${from}-${to}`}
|
||||
className="sheng"
|
||||
x1={f.cx}
|
||||
y1={f.cy}
|
||||
x2={t.cx}
|
||||
y2={t.cy}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 五行节点 */}
|
||||
{ELEMENT_KEYS.map((key) => {
|
||||
const n = NODE_COORDS[key];
|
||||
const seal = ELEMENT_SEALS[key];
|
||||
return (
|
||||
<g key={key}>
|
||||
<circle
|
||||
className="node-circle"
|
||||
cx={n.cx}
|
||||
cy={n.cy}
|
||||
r="27"
|
||||
style={{
|
||||
fill: `var(--${key}-dim)`,
|
||||
stroke: `var(--${key})`,
|
||||
}}
|
||||
/>
|
||||
<text
|
||||
x={n.cx}
|
||||
y={n.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="central"
|
||||
style={{
|
||||
fontFamily: '"Songti SC", "STSong", serif',
|
||||
fontSize: "22px",
|
||||
fontWeight: 700,
|
||||
fill: `var(--${key})`,
|
||||
}}
|
||||
>
|
||||
{seal}
|
||||
</text>
|
||||
<text
|
||||
x={n.labelX}
|
||||
y={n.labelY}
|
||||
textAnchor="middle"
|
||||
style={{
|
||||
fontFamily:
|
||||
"-apple-system, BlinkMacSystemFont, system-ui, sans-serif",
|
||||
fontSize: "9px",
|
||||
fill: "var(--fg-faint)",
|
||||
letterSpacing: "0.04em",
|
||||
}}
|
||||
>
|
||||
{DIMS.find((d) => d.key === key)?.label ?? ""}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 中心:架构心跳 */}
|
||||
<circle className="center-bg" cx="200" cy="200" r="58" />
|
||||
<text
|
||||
x="200"
|
||||
y="186"
|
||||
textAnchor="middle"
|
||||
style={{
|
||||
fontFamily: '"Songti SC", "STSong", serif',
|
||||
fontSize: "15px",
|
||||
fontWeight: 600,
|
||||
fill: "var(--gilt)",
|
||||
}}
|
||||
>
|
||||
架构心跳
|
||||
</text>
|
||||
<text
|
||||
x="200"
|
||||
y="212"
|
||||
textAnchor="middle"
|
||||
style={{
|
||||
fontFamily:
|
||||
'"JetBrains Mono", "SF Mono", ui-monospace, monospace',
|
||||
fontSize: "20px",
|
||||
fontWeight: 300,
|
||||
fill: "var(--fg)",
|
||||
letterSpacing: "0.04em",
|
||||
}}
|
||||
>
|
||||
82
|
||||
</text>
|
||||
<text
|
||||
x="200"
|
||||
y="228"
|
||||
textAnchor="middle"
|
||||
style={{
|
||||
fontFamily:
|
||||
"-apple-system, BlinkMacSystemFont, system-ui, sans-serif",
|
||||
fontSize: "9.5px",
|
||||
fill: "var(--fg-dim)",
|
||||
}}
|
||||
>
|
||||
阴阳平衡 · 良
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
{/* 图例 */}
|
||||
<div
|
||||
className="relative z-10 mt-[10px] flex flex-wrap gap-4 border-t pt-[10px]"
|
||||
style={{ borderColor: "var(--rule)" }}
|
||||
>
|
||||
<span
|
||||
className="inline-flex items-center gap-[6px] text-[11.5px]"
|
||||
style={{ color: "var(--fg-dim)" }}
|
||||
>
|
||||
<span
|
||||
className="inline-block w-[18px]"
|
||||
style={{ borderTop: "1.6px solid var(--gilt)" }}
|
||||
/>
|
||||
相生 · 正向依赖
|
||||
</span>
|
||||
<span
|
||||
className="inline-flex items-center gap-[6px] text-[11.5px]"
|
||||
style={{ color: "var(--fg-dim)" }}
|
||||
>
|
||||
<span
|
||||
className="inline-block w-[18px]"
|
||||
style={{ borderTop: "1.3px dashed var(--cinnabar)" }}
|
||||
/>
|
||||
相克 · 负向制约
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ---- 维度卡 ---- */}
|
||||
<div
|
||||
className="flex min-w-0 flex-1 flex-col rounded-lg border p-[18px_20px]"
|
||||
style={{
|
||||
borderColor: "var(--rule)",
|
||||
background: "var(--surface)",
|
||||
}}
|
||||
>
|
||||
<h3
|
||||
className="font-serif text-[14px] font-semibold"
|
||||
style={{ color: "var(--fg)", marginBottom: "14px" }}
|
||||
>
|
||||
枢健康维度
|
||||
</h3>
|
||||
<div className="flex flex-1 flex-col gap-[11px]">
|
||||
{DIMS.map((d) => (
|
||||
<div
|
||||
key={d.key}
|
||||
className="dim"
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "22px 60px 1fr 30px",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="text-center font-serif text-[16px] font-bold"
|
||||
style={{ color: `var(--${d.key})` }}
|
||||
>
|
||||
{d.seal}
|
||||
</span>
|
||||
<span
|
||||
className="text-[12.5px] font-medium leading-tight"
|
||||
style={{ color: "var(--fg)" }}
|
||||
>
|
||||
{d.label}
|
||||
<small
|
||||
className="mt-[1px] block text-[10.5px] font-normal leading-tight"
|
||||
style={{ color: "var(--fg-faint)" }}
|
||||
>
|
||||
{d.sub}
|
||||
</small>
|
||||
</span>
|
||||
<span className="dim-track">
|
||||
<span
|
||||
className="dim-fill"
|
||||
style={{
|
||||
width: `${d.width}%`,
|
||||
background: `linear-gradient(90deg, var(--${d.key}-dim), var(--${d.key}))`,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className="text-right font-mono text-[10.5px]"
|
||||
style={{ color: `var(--${d.key})` }}
|
||||
>
|
||||
{d.val}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="mt-[14px] rounded-r-[4px] border-l-2 px-[12px] py-[10px] text-[12px] leading-[1.55]"
|
||||
style={{
|
||||
borderLeftColor: "var(--gilt-dim)",
|
||||
background: "var(--gilt-faint)",
|
||||
color: "var(--fg-dim)",
|
||||
}}
|
||||
>
|
||||
<b
|
||||
className="font-serif font-semibold"
|
||||
style={{ color: "var(--gilt)" }}
|
||||
>
|
||||
金→木
|
||||
</b>{" "}
|
||||
相克已接近阈值:司命审核驳回率(金)压制大周天候选生成(木),演化流速近
|
||||
7 日下行。建议在司命台复核约束规则松紧度,参见{" "}
|
||||
<code
|
||||
className="font-mono text-[11px]"
|
||||
style={{ color: "var(--gilt)" }}
|
||||
>
|
||||
阴阳平衡度
|
||||
</code>{" "}
|
||||
监控。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ══ 九枢健康网格 ══ */}
|
||||
<div className="mb-5">
|
||||
<div
|
||||
className="mb-[14px] flex items-center gap-[6px] font-mono text-[12px]"
|
||||
style={{ color: "var(--fg-faint)", letterSpacing: "0.1em" }}
|
||||
>
|
||||
<span style={{ color: "var(--gilt)", opacity: 0.8 }}>⟡</span>
|
||||
九枢健康
|
||||
<span
|
||||
className="text-[12px] tracking-normal opacity-75"
|
||||
style={{ color: "var(--fg-dim)" }}
|
||||
>
|
||||
每枢五维 · 综合分 + 趋势
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||
{PIVOTS.map((p) => (
|
||||
<div
|
||||
key={p.name}
|
||||
className={cn(
|
||||
"flex flex-col gap-[9px] rounded-lg border p-[14px_16px]",
|
||||
p.alert && "bg-[rgba(184,57,46,0.05)]",
|
||||
)}
|
||||
style={{
|
||||
borderColor: p.alert
|
||||
? "var(--cinnabar-dim)"
|
||||
: "var(--rule)",
|
||||
background: p.alert ? undefined : "var(--surface)",
|
||||
}}
|
||||
>
|
||||
{/* 枢头部 */}
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span
|
||||
className="font-serif text-[15px] font-semibold"
|
||||
style={{ color: "var(--fg)" }}
|
||||
>
|
||||
{p.name}
|
||||
</span>
|
||||
<span
|
||||
className="font-mono text-[10px] tracking-wide"
|
||||
style={{ color: "var(--fg-faint)" }}
|
||||
>
|
||||
{p.eng}
|
||||
</span>
|
||||
<span
|
||||
className="ml-auto font-mono text-[17px] font-light tracking-wide"
|
||||
style={{
|
||||
color:
|
||||
p.score < 75 ? "var(--cinnabar)" : "var(--patina)",
|
||||
}}
|
||||
>
|
||||
{p.score}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 五维微进度条 */}
|
||||
<div className="flex flex-col gap-[4px]">
|
||||
{ELEMENT_KEYS.map((ek, i) => (
|
||||
<div
|
||||
key={ek}
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "14px 1fr 24px",
|
||||
alignItems: "center",
|
||||
gap: "7px",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="text-center font-serif text-[11px]"
|
||||
style={{ color: `var(--${ek})` }}
|
||||
>
|
||||
{ELEMENT_SEALS[ek]}
|
||||
</span>
|
||||
<span className="pbar-track">
|
||||
<span
|
||||
className="pbar-fill"
|
||||
style={{
|
||||
width: `${p.bars[i]}%`,
|
||||
background: `var(--${ek})`,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className="font-mono text-[9.5px] text-right"
|
||||
style={{ color: "var(--fg-faint)" }}
|
||||
>
|
||||
.{p.bars[i]}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 底部趋势 */}
|
||||
<div
|
||||
className="flex items-center gap-2 pt-[4px] text-[11px]"
|
||||
style={{
|
||||
borderTop: "1px solid var(--rule)",
|
||||
color: "var(--fg-faint)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="font-mono"
|
||||
style={{
|
||||
color: p.trend.startsWith("↑")
|
||||
? "var(--patina)"
|
||||
: "var(--cinnabar)",
|
||||
}}
|
||||
>
|
||||
{p.trend}
|
||||
</span>
|
||||
{p.desc}
|
||||
{p.alert && p.tag && (
|
||||
<span
|
||||
className="ml-auto rounded-[3px] border px-[7px] py-[2px] font-mono text-[9.5px]"
|
||||
style={{
|
||||
borderColor: "var(--cinnabar-dim)",
|
||||
background: "rgba(184, 57, 46, 0.08)",
|
||||
color: "var(--cinnabar)",
|
||||
}}
|
||||
>
|
||||
{p.tag}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ══ 相生流 + 相克告警 ══ */}
|
||||
<div className="flex flex-col gap-5 lg:flex-row lg:items-start">
|
||||
{/* ---- 相生流 ---- */}
|
||||
<div className="min-w-0 flex-1">
|
||||
<h3
|
||||
className="mb-[14px] font-serif text-[14px] font-semibold"
|
||||
style={{ color: "var(--fg)" }}
|
||||
>
|
||||
相生 · 正向依赖链
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{SHENG_FLOWS.map((f) => (
|
||||
<div
|
||||
key={`${f.pair}-${f.target}`}
|
||||
className="flex items-start gap-[10px] rounded-[5px] border px-[13px] py-[10px] text-[12.5px] leading-[1.5]"
|
||||
style={{
|
||||
borderColor: "var(--rule)",
|
||||
background: "var(--patina-wash)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="shrink-0 pt-[1px] font-serif text-[13px] whitespace-nowrap"
|
||||
style={{ color: "var(--gilt)" }}
|
||||
>
|
||||
{f.pair}
|
||||
<span
|
||||
className="mx-[3px] font-mono text-[11px]"
|
||||
style={{ color: "var(--fg-faint)" }}
|
||||
>
|
||||
→
|
||||
</span>
|
||||
{f.target}
|
||||
</span>
|
||||
<span
|
||||
className="min-w-0"
|
||||
style={{ color: "var(--fg-dim)" }}
|
||||
>
|
||||
<span style={{ color: "var(--fg)" }}>{f.body}</span>
|
||||
</span>
|
||||
<span
|
||||
className="shrink-0 pt-[1px] font-mono text-[10.5px] whitespace-nowrap"
|
||||
style={{ color: "var(--patina)" }}
|
||||
>
|
||||
{f.val}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ---- 相克告警 ---- */}
|
||||
<div className="w-full shrink-0 lg:w-[340px]">
|
||||
<div
|
||||
className="rounded-lg border p-[16px_18px]"
|
||||
style={{
|
||||
borderColor: "var(--rule)",
|
||||
background: "var(--surface)",
|
||||
}}
|
||||
>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3
|
||||
className="m-0 font-serif text-[14px] font-semibold"
|
||||
style={{ color: "var(--fg)" }}
|
||||
>
|
||||
相克告警{" "}
|
||||
<span
|
||||
className="font-mono text-[11px] font-normal"
|
||||
style={{ color: "var(--fg-faint)" }}
|
||||
>
|
||||
克制失衡
|
||||
</span>
|
||||
</h3>
|
||||
<span
|
||||
className="rounded-[5px] border px-[10px] py-[3px] font-mono text-[11px]"
|
||||
style={{
|
||||
borderColor: "var(--cinnabar-dim)",
|
||||
background: "rgba(184, 57, 46, 0.08)",
|
||||
color: "var(--cinnabar)",
|
||||
}}
|
||||
>
|
||||
2 待关注
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{KE_ALERTS.map((a) => (
|
||||
<div
|
||||
key={`${a.pairA}-${a.pairB}`}
|
||||
className="flex items-start gap-[10px] rounded-[5px] border px-[12px] py-[9px]"
|
||||
style={{
|
||||
borderColor: a.muted
|
||||
? "var(--rule)"
|
||||
: "var(--cinnabar-dim)",
|
||||
background: a.muted ? "transparent" : "var(--ink)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="w-[46px] shrink-0 pt-[1px] text-center font-serif text-[13px] leading-tight"
|
||||
style={{
|
||||
color: a.muted
|
||||
? "var(--fg-faint)"
|
||||
: "var(--cinnabar)",
|
||||
}}
|
||||
>
|
||||
{a.pairA}
|
||||
<br />
|
||||
{a.pairB}
|
||||
</span>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<div
|
||||
className="text-[12px] leading-[1.5]"
|
||||
style={{
|
||||
color: a.muted
|
||||
? "var(--fg-faint)"
|
||||
: "var(--fg-dim)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: a.muted ? "var(--fg-dim)" : "var(--fg)",
|
||||
}}
|
||||
>
|
||||
{a.desc}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-1 flex items-center gap-[6px]">
|
||||
<span className="ke-track flex-1">
|
||||
<span
|
||||
className="ke-fill"
|
||||
style={{
|
||||
width: `${a.width}%`,
|
||||
background: a.muted
|
||||
? "linear-gradient(90deg, var(--gilt-dim), var(--gilt))"
|
||||
: "linear-gradient(90deg, var(--cinnabar-dim), var(--cinnabar))",
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className="shrink-0 font-mono text-[9.5px] whitespace-nowrap"
|
||||
style={{
|
||||
color: a.muted
|
||||
? "var(--gilt)"
|
||||
: "var(--cinnabar)",
|
||||
}}
|
||||
>
|
||||
{a.level}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageContainer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { AgentRoster } from "@/components/admin/AgentRoster";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { AgentRosterEntry } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminAgentsPage() {
|
||||
let roster: AgentRosterEntry[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
roster = await serverApi.get("/admin/agents/roster");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="天工阁枢"
|
||||
description="多智能体角色在岗管理——九角色可增删改。"
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<AgentRoster roster={roster ?? []} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
import { AuditConsole } from "@/components/admin/AuditConsole";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { AuditEntry, Paginated } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 起居注 · 审计时间河(司命台第三签 · 时间维度)。
|
||||
* 铜金分章卷轴 + 横向枢阁筛选,追溯裁定与放行。
|
||||
* 数据来自 GET /admin/audit/timeline(API §19.3)。
|
||||
*/
|
||||
export default async function AuditPage() {
|
||||
let entries: AuditEntry[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
const data = await serverApi.get<Paginated<AuditEntry>>(
|
||||
"/admin/audit/timeline",
|
||||
);
|
||||
entries = data.items;
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="起居注"
|
||||
description="审计时间河 · 按枢追溯裁定与放行。"
|
||||
maxWidth="max-w-4xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<AuditConsole entries={entries} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,868 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
createCharacterTemplate,
|
||||
deleteCharacterTemplate,
|
||||
listCharacterTemplates,
|
||||
updateCharacterTemplate,
|
||||
} from "@/lib/api/admin";
|
||||
import type {
|
||||
AdminCharacterTemplate,
|
||||
AdminCreateTemplateBody,
|
||||
AdminUpdateTemplateBody,
|
||||
} from "@/types/api";
|
||||
|
||||
// ── 模型提示选项 ──
|
||||
const MODEL_HINTS = {
|
||||
default_chat_model_hint: {
|
||||
label: "对话模型",
|
||||
options: [
|
||||
{ value: "", label: "— 不设置(使用系统默认)" },
|
||||
{ value: "deepseek-v4-flash", label: "deepseek-v4-flash" },
|
||||
{ value: "deepseek-v3", label: "deepseek-v3" },
|
||||
{ value: "gpt-4o", label: "gpt-4o" },
|
||||
{ value: "claude-sonnet-5", label: "claude-sonnet-5" },
|
||||
{ value: "claude-opus-4-8", label: "claude-opus-4-8" },
|
||||
],
|
||||
},
|
||||
memory_extraction_model_hint: {
|
||||
label: "记忆提取模型",
|
||||
options: [
|
||||
{ value: "", label: "— 不设置(空)" },
|
||||
{ value: "memory-extraction", label: "memory-extraction" },
|
||||
{ value: "deepseek-v4-flash", label: "deepseek-v4-flash" },
|
||||
{ value: "deepseek-v3", label: "deepseek-v3" },
|
||||
{ value: "gpt-4o-mini", label: "gpt-4o-mini" },
|
||||
],
|
||||
},
|
||||
personality_analysis_model_hint: {
|
||||
label: "人格分析模型",
|
||||
options: [
|
||||
{ value: "", label: "— 不设置(空)" },
|
||||
{ value: "personality-analysis", label: "personality-analysis" },
|
||||
{ value: "deepseek-v4-flash", label: "deepseek-v4-flash" },
|
||||
{ value: "deepseek-v3", label: "deepseek-v3" },
|
||||
{ value: "gpt-4o", label: "gpt-4o" },
|
||||
],
|
||||
},
|
||||
embedding_model_hint: {
|
||||
label: "向量嵌入模型",
|
||||
options: [
|
||||
{ value: "", label: "— 不设置(空)" },
|
||||
{ value: "embedding", label: "embedding" },
|
||||
{ value: "text-embedding-3-small", label: "text-embedding-3-small" },
|
||||
{ value: "text-embedding-3-large", label: "text-embedding-3-large" },
|
||||
],
|
||||
},
|
||||
} as const;
|
||||
|
||||
type ModelHintKey = keyof typeof MODEL_HINTS;
|
||||
|
||||
// ── 隐私等级选项 ──
|
||||
const PRIVACY_LEVELS = [
|
||||
{ value: "normal", label: "normal" },
|
||||
{ value: "sensitive", label: "sensitive" },
|
||||
{ value: "local_only", label: "local_only" },
|
||||
];
|
||||
|
||||
// ── 标签来源判断 ──
|
||||
function templateSource(key: string): { label: string; css: string } {
|
||||
const yamlKeys = ["default", "tokisaki_kurumi", "gufeng", "baize", "qingnv"];
|
||||
if (yamlKeys.includes(key)) {
|
||||
return { label: "YAML", css: "bg-gilt/10 text-gilt border-gilt/30" };
|
||||
}
|
||||
return { label: "API", css: "bg-glass text-muted border-border-glass" };
|
||||
}
|
||||
|
||||
// ── 标签输入组件 ──
|
||||
function TagInput({
|
||||
values,
|
||||
onChange,
|
||||
placeholder,
|
||||
}: {
|
||||
values: string[];
|
||||
onChange: (v: string[]) => void;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const [input, setInput] = useState("");
|
||||
|
||||
const add = () => {
|
||||
const v = input.trim();
|
||||
if (!v) return;
|
||||
onChange([...values, v]);
|
||||
setInput("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-1.5 rounded-xl border border-border-glass bg-glass px-3 py-2 text-sm transition-colors focus-within:border-patina">
|
||||
{values.map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="inline-flex items-center gap-1 rounded-full bg-patina/10 px-2.5 py-0.5 text-[12px] text-patina"
|
||||
>
|
||||
{v}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(values.filter((_, j) => j !== i))}
|
||||
className="ml-0.5 opacity-50 transition-opacity hover:opacity-100"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
<input
|
||||
className="min-w-[80px] flex-1 border-none bg-transparent p-0.5 text-sm text-fg outline-none"
|
||||
placeholder={placeholder || "输入后回车添加…"}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
add();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AdminCharacterTemplatesPage() {
|
||||
const [templates, setTemplates] = useState<AdminCharacterTemplate[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editing, setEditing] = useState<AdminCharacterTemplate | null>(null);
|
||||
const [deleteConfirm, setDeleteConfirm] =
|
||||
useState<AdminCharacterTemplate | null>(null);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
try {
|
||||
const data = await listCharacterTemplates();
|
||||
if (!cancelled) setTemplates(data);
|
||||
} catch {
|
||||
/* 错误由 ApiError 统一处理 */
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
/** 重新拉取模板列表(表单操作后调用) */
|
||||
const refetch = () => {
|
||||
setLoading(true);
|
||||
(async () => {
|
||||
try {
|
||||
const data = await listCharacterTemplates();
|
||||
setTemplates(data);
|
||||
} catch {
|
||||
/* 静默 */
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const filtered = search
|
||||
? templates.filter(
|
||||
(t) =>
|
||||
t.key.toLowerCase().includes(search.toLowerCase()) ||
|
||||
t.name.includes(search) ||
|
||||
(t.display_name || "").includes(search),
|
||||
)
|
||||
: templates;
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl px-4 py-8">
|
||||
{/* 页头 */}
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-fg">人物模板</h1>
|
||||
<p className="mt-0.5 text-xs text-muted">
|
||||
Character Templates · §19.8
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setEditing(null);
|
||||
setShowForm(true);
|
||||
}}
|
||||
className="rounded-xl bg-patina px-4 py-2 text-xs font-medium text-white transition-opacity hover:opacity-90"
|
||||
>
|
||||
✦ 新建模板
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 搜索 */}
|
||||
<div className="mb-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索模板 key 或名称…"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="block w-full max-w-xs rounded-xl border border-border-glass bg-glass px-4 py-2 text-sm text-fg outline-none transition-colors placeholder:text-muted focus:border-patina"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 模板列表 */}
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-16 text-xs text-muted">
|
||||
加载中…
|
||||
</div>
|
||||
) : filtered.length === 0 ? (
|
||||
<div className="flex items-center justify-center py-16 text-xs text-muted">
|
||||
{search ? "无匹配模板" : "暂无模板"}
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-xl border border-border-glass">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border-glass bg-glass text-xs font-medium text-muted">
|
||||
<th className="px-4 py-3">Key</th>
|
||||
<th className="px-4 py-3">名称</th>
|
||||
<th className="px-4 py-3">显示名</th>
|
||||
<th className="px-4 py-3">来源</th>
|
||||
<th className="px-4 py-3">默认</th>
|
||||
<th className="px-4 py-3">简介</th>
|
||||
<th className="px-4 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map((t) => {
|
||||
const src = templateSource(t.key);
|
||||
const desc =
|
||||
(t.profile?.description as string) ||
|
||||
(t.profile?.bio as string) ||
|
||||
"-";
|
||||
return (
|
||||
<tr
|
||||
key={t.key}
|
||||
className="border-b border-border-glass last:border-0 hover:bg-fg/[0.02]"
|
||||
>
|
||||
<td className="px-4 py-3 font-mono text-xs text-gilt">
|
||||
{t.key}
|
||||
</td>
|
||||
<td className="px-4 py-3 font-medium text-fg">{t.name}</td>
|
||||
<td className="px-4 py-3 text-muted">
|
||||
{t.display_name || "-"}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={`inline-block rounded-full border px-2.5 py-0.5 text-[11px] font-medium ${src.css}`}
|
||||
>
|
||||
{src.label}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-xs text-muted">
|
||||
{t.is_default ? (
|
||||
<span className="text-patina">✓</span>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</td>
|
||||
<td className="max-w-[200px] truncate px-4 py-3 text-muted">
|
||||
{desc}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setEditing(t);
|
||||
setShowForm(true);
|
||||
}}
|
||||
className="rounded-lg px-2.5 py-1 text-[11px] font-medium transition-colors hover:bg-fg/5"
|
||||
style={{ color: "var(--patina)" }}
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDeleteConfirm(t)}
|
||||
className="rounded-lg px-2.5 py-1 text-[11px] font-medium transition-colors hover:bg-fg/5"
|
||||
style={{ color: "var(--cinnabar)" }}
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 创建/编辑表单 */}
|
||||
{showForm && (
|
||||
<TemplateForm
|
||||
template={editing}
|
||||
onDone={() => {
|
||||
setShowForm(false);
|
||||
setEditing(null);
|
||||
refetch();
|
||||
}}
|
||||
onCancel={() => {
|
||||
setShowForm(false);
|
||||
setEditing(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 删除确认 */}
|
||||
{deleteConfirm && (
|
||||
<DeleteConfirmModal
|
||||
template={deleteConfirm}
|
||||
onDone={() => {
|
||||
setDeleteConfirm(null);
|
||||
refetch();
|
||||
}}
|
||||
onCancel={() => setDeleteConfirm(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 操作提示 */}
|
||||
<div className="mt-6 rounded-xl border border-dashed border-border-glass px-4 py-3 text-xs text-muted">
|
||||
⚠ key 与{" "}
|
||||
<code className="rounded bg-gilt/10 px-1 py-0.5 text-gilt">
|
||||
config/character_templates.yaml
|
||||
</code>{" "}
|
||||
中定义一致的模板,在服务重启时会从 YAML 文件覆盖同步。API
|
||||
创建的模板不受影响。
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 创建/编辑表单 ──
|
||||
function TemplateForm({
|
||||
template,
|
||||
onDone,
|
||||
onCancel,
|
||||
}: {
|
||||
template: AdminCharacterTemplate | null;
|
||||
onDone: () => void;
|
||||
onCancel: () => void;
|
||||
}) {
|
||||
const isEdit = template !== null;
|
||||
|
||||
const [key, setKey] = useState(template?.key || "");
|
||||
const [name, setName] = useState(template?.name || "");
|
||||
const [displayName, setDisplayName] = useState(template?.display_name || "");
|
||||
const [language, setLanguage] = useState(template?.language || "zh-CN");
|
||||
const [privacy, setPrivacy] = useState(template?.privacy_level || "normal");
|
||||
const [isDefault, setIsDefault] = useState(template?.is_default || false);
|
||||
const [description, setDescription] = useState(
|
||||
(template?.profile?.description as string) || "",
|
||||
);
|
||||
const [background, setBackground] = useState(
|
||||
(template?.profile?.background as string) || "",
|
||||
);
|
||||
const [speakingStyle, setSpeakingStyle] = useState(
|
||||
(template?.profile?.speaking_style as string) || "",
|
||||
);
|
||||
const [temperament, setTemperament] = useState(
|
||||
(template?.profile?.temperament as string) || "",
|
||||
);
|
||||
const [birthday, setBirthday] = useState(
|
||||
(template?.profile?.birthday as string) || "",
|
||||
);
|
||||
const [height, setHeight] = useState(
|
||||
(template?.profile?.height as string) || "",
|
||||
);
|
||||
const [weight, setWeight] = useState(
|
||||
(template?.profile?.weight as string) || "",
|
||||
);
|
||||
const [hobbies, setHobbies] = useState<string[]>(
|
||||
Array.isArray(template?.profile?.hobbies)
|
||||
? (template?.profile?.hobbies as string[])
|
||||
: [],
|
||||
);
|
||||
const [values, setValues] = useState<string[]>(
|
||||
Array.isArray(template?.profile?.values)
|
||||
? (template?.profile?.values as string[])
|
||||
: [],
|
||||
);
|
||||
const [extraJson, setExtraJson] = useState("{}");
|
||||
|
||||
// Model hints
|
||||
const [chatHint, setChatHint] = useState(
|
||||
(template?.model_policy?.default_chat_model_hint as string) || "",
|
||||
);
|
||||
const [memHint, setMemHint] = useState(
|
||||
(template?.model_policy?.memory_extraction_model_hint as string) || "",
|
||||
);
|
||||
const [persHint, setPersHint] = useState(
|
||||
(template?.model_policy?.personality_analysis_model_hint as string) || "",
|
||||
);
|
||||
const [embHint, setEmbHint] = useState(
|
||||
(template?.model_policy?.embedding_model_hint as string) || "",
|
||||
);
|
||||
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
|
||||
// Build profile
|
||||
const profile: Record<string, unknown> = {};
|
||||
if (description) profile.description = description;
|
||||
if (background) profile.background = background;
|
||||
if (speakingStyle) profile["speaking_style"] = speakingStyle;
|
||||
if (temperament) profile.temperament = temperament;
|
||||
if (birthday) profile.birthday = birthday;
|
||||
if (height) profile.height = height;
|
||||
if (weight) profile.weight = weight;
|
||||
if (hobbies.length) profile.hobbies = hobbies;
|
||||
if (values.length) profile.values = values;
|
||||
// Merge extra JSON
|
||||
try {
|
||||
const extra = JSON.parse(extraJson || "{}");
|
||||
Object.assign(profile, extra);
|
||||
} catch {
|
||||
setError("额外内容 JSON 格式错误");
|
||||
setSaving(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build model_policy
|
||||
const modelPolicy: Record<string, string> = {};
|
||||
if (chatHint) modelPolicy.default_chat_model_hint = chatHint;
|
||||
if (memHint) modelPolicy.memory_extraction_model_hint = memHint;
|
||||
if (persHint) modelPolicy.personality_analysis_model_hint = persHint;
|
||||
if (embHint) modelPolicy.embedding_model_hint = embHint;
|
||||
|
||||
try {
|
||||
if (isEdit) {
|
||||
const body: AdminUpdateTemplateBody = {
|
||||
name,
|
||||
display_name: displayName || null,
|
||||
language,
|
||||
privacy_level: privacy,
|
||||
is_default: isDefault,
|
||||
profile,
|
||||
model_policy: modelPolicy,
|
||||
};
|
||||
await updateCharacterTemplate(template.key, body);
|
||||
} else {
|
||||
const body: AdminCreateTemplateBody = {
|
||||
key,
|
||||
name,
|
||||
display_name: displayName || null,
|
||||
language,
|
||||
privacy_level: privacy,
|
||||
is_default: isDefault,
|
||||
profile,
|
||||
model_policy: modelPolicy,
|
||||
};
|
||||
await createCharacterTemplate(body);
|
||||
}
|
||||
onDone();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "保存失败");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/40 py-8 backdrop-blur-sm">
|
||||
<div className="w-full max-w-2xl rounded-2xl border border-border-glass bg-ink shadow-2xl">
|
||||
{/* 标题 */}
|
||||
<div className="flex items-center justify-between border-b border-border-glass px-6 py-4">
|
||||
<h2 className="font-serif text-base font-semibold text-fg">
|
||||
{isEdit ? `✎ 编辑模板 · ${template.key}` : "✦ 新建模板"}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="flex h-8 w-8 items-center justify-center rounded-lg text-muted transition-colors hover:bg-fg/5 hover:text-fg"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5 px-6 py-5">
|
||||
{error && (
|
||||
<div className="rounded-lg bg-cinnabar/10 px-4 py-2 text-xs text-cinnabar">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── 基础信息 ── */}
|
||||
<div>
|
||||
<span className="mb-3 inline-block font-mono text-[11px] font-medium tracking-wider text-muted">
|
||||
基础信息
|
||||
</span>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
Key *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={key}
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina disabled:opacity-40"
|
||||
required
|
||||
disabled={isEdit}
|
||||
placeholder="唯一标识,如 my_template"
|
||||
/>
|
||||
{!isEdit && (
|
||||
<p className="mt-1 text-[11px] text-muted">
|
||||
仅小写字母、数字、下划线、连字符
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
是否默认
|
||||
</label>
|
||||
<select
|
||||
value={isDefault ? "true" : "false"}
|
||||
onChange={(e) => setIsDefault(e.target.value === "true")}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
>
|
||||
<option value="false">否</option>
|
||||
<option value="true">是</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
名称 *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
required
|
||||
placeholder="内部名称"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
显示名
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
placeholder="显示昵称"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
语言
|
||||
</label>
|
||||
<select
|
||||
value={language}
|
||||
onChange={(e) => setLanguage(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
>
|
||||
<option value="zh-CN">zh-CN</option>
|
||||
<option value="en">en</option>
|
||||
<option value="ja">ja</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
私隐等级
|
||||
</label>
|
||||
<select
|
||||
value={privacy}
|
||||
onChange={(e) => setPrivacy(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
>
|
||||
{PRIVACY_LEVELS.map((pl) => (
|
||||
<option key={pl.value} value={pl.value}>
|
||||
{pl.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── 人物档案 ── */}
|
||||
<div>
|
||||
<span className="mb-3 inline-block font-mono text-[11px] font-medium tracking-wider text-muted">
|
||||
📋 人物档案(Profile)
|
||||
</span>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
一句话简介
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
placeholder="例如:一面照见人心的镜子"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
背景故事
|
||||
</label>
|
||||
<textarea
|
||||
value={background}
|
||||
onChange={(e) => setBackground(e.target.value)}
|
||||
rows={3}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
placeholder="人物背景故事设定…"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
说话风格
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={speakingStyle}
|
||||
onChange={(e) => setSpeakingStyle(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
placeholder="温和、清晰、简洁"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
性情气质
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={temperament}
|
||||
onChange={(e) => setTemperament(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
placeholder="沉静而温暖"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
生日
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={birthday}
|
||||
onChange={(e) => setBirthday(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
身高(cm)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={height}
|
||||
onChange={(e) => setHeight(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
placeholder="cm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
体重(kg)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={weight}
|
||||
onChange={(e) => setWeight(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
placeholder="kg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-muted">
|
||||
兴趣爱好
|
||||
</label>
|
||||
<TagInput
|
||||
values={hobbies}
|
||||
onChange={setHobbies}
|
||||
placeholder="输入后回车添加…"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-muted">
|
||||
价值观
|
||||
</label>
|
||||
<TagInput
|
||||
values={values}
|
||||
onChange={setValues}
|
||||
placeholder="输入后回车添加…"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── 额外内容(JSON)── */}
|
||||
<div>
|
||||
<span className="mb-3 inline-block font-mono text-[11px] font-medium tracking-wider text-muted">
|
||||
⋯ 额外内容(Extra JSON)
|
||||
<span className="ml-2 font-normal text-muted/60">
|
||||
以上字段未覆盖的自定义 profile 键值
|
||||
</span>
|
||||
</span>
|
||||
<textarea
|
||||
value={extraJson}
|
||||
onChange={(e) => setExtraJson(e.target.value)}
|
||||
rows={3}
|
||||
className="block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 font-mono text-xs text-fg outline-none transition-colors focus:border-patina"
|
||||
placeholder="{}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── 模型策略 ── */}
|
||||
<div>
|
||||
<span className="mb-3 inline-block font-mono text-[11px] font-medium tracking-wider text-muted">
|
||||
⚙ 模型策略(Model Policy)
|
||||
<span className="ml-2 font-normal text-muted/60">不选则留空</span>
|
||||
</span>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{(
|
||||
Object.entries(MODEL_HINTS) as [
|
||||
ModelHintKey,
|
||||
(typeof MODEL_HINTS)[ModelHintKey],
|
||||
][]
|
||||
).map(([fieldKey, cfg]) => {
|
||||
const val = {
|
||||
default_chat_model_hint: chatHint,
|
||||
memory_extraction_model_hint: memHint,
|
||||
personality_analysis_model_hint: persHint,
|
||||
embedding_model_hint: embHint,
|
||||
}[fieldKey];
|
||||
const setVal = {
|
||||
default_chat_model_hint: setChatHint,
|
||||
memory_extraction_model_hint: setMemHint,
|
||||
personality_analysis_model_hint: setPersHint,
|
||||
embedding_model_hint: setEmbHint,
|
||||
}[fieldKey];
|
||||
return (
|
||||
<div key={fieldKey}>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
{cfg.label}
|
||||
</label>
|
||||
<select
|
||||
value={val}
|
||||
onChange={(e) => setVal(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
>
|
||||
{cfg.options.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── 操作按钮 ── */}
|
||||
<div className="flex items-center justify-end gap-2 border-t border-border-glass pt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="rounded-lg px-4 py-2 text-xs font-medium text-muted transition-colors hover:bg-fg/5"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={saving}
|
||||
className="rounded-lg bg-patina px-4 py-2 text-xs font-medium text-white transition-opacity disabled:opacity-50"
|
||||
>
|
||||
{saving ? "保存中…" : isEdit ? "✎ 保存" : "✦ 创建"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 删除确认 ──
|
||||
function DeleteConfirmModal({
|
||||
template,
|
||||
onDone,
|
||||
onCancel,
|
||||
}: {
|
||||
template: AdminCharacterTemplate;
|
||||
onDone: () => void;
|
||||
onCancel: () => void;
|
||||
}) {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleDelete = async () => {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
await deleteCharacterTemplate(template.key);
|
||||
onDone();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "删除失败");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="w-full max-w-sm rounded-2xl border border-border-glass bg-ink p-6 shadow-2xl">
|
||||
<h2 className="mb-2 text-sm font-semibold text-cinnabar">
|
||||
⚠ 确认删除模板
|
||||
</h2>
|
||||
<p className="mb-4 text-xs text-muted">
|
||||
将删除系统模板{" "}
|
||||
<strong className="text-fg">
|
||||
{template.key} · {template.name}
|
||||
</strong>
|
||||
。如果有用户人物引用了此模板将无法删除。此操作不可撤销。
|
||||
</p>
|
||||
{error && (
|
||||
<div className="mb-3 rounded-lg bg-cinnabar/10 px-3 py-2 text-xs text-cinnabar">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="rounded-lg px-3 py-1.5 text-xs font-medium text-muted transition-colors hover:bg-fg/5"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDelete}
|
||||
disabled={saving}
|
||||
className="rounded-lg bg-cinnabar px-3 py-1.5 text-xs font-medium text-white transition-opacity disabled:opacity-50"
|
||||
>
|
||||
{saving ? "删除中…" : "✕ 确认删除"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { Button } from "@/components/ui/Button";
|
||||
|
||||
/**
|
||||
* 司命台错误边界:捕获 admin 路由段渲染期抛出的未处理错误。
|
||||
*/
|
||||
export default function AdminError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
console.error("[admin-error]", error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-10 text-center">
|
||||
<div className="flex flex-col items-center gap-3 rounded-2xl border border-cinnabar/30 bg-cinnabar/10 px-8 py-10 backdrop-blur-sm">
|
||||
<p className="font-serif text-base font-medium text-cinnabar">
|
||||
司命台异动
|
||||
</p>
|
||||
<p className="max-w-sm text-xs text-muted">
|
||||
管理控制台渲染时遇到意外错误。可重试,或返回司命台首页。
|
||||
</p>
|
||||
{error.digest && (
|
||||
<p className="font-mono text-[10px] text-muted/70">
|
||||
trace: {error.digest}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-2 flex gap-2">
|
||||
<Button variant="primary" size="sm" onClick={() => reset()}>
|
||||
重试
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => router.push("/admin")}
|
||||
>
|
||||
返回司命台
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { GoalTree } from "@/components/admin/GoalTree";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { GoalNode } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminGoalsPage() {
|
||||
let goals: GoalNode[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
goals = await serverApi.get("/admin/goals");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="太一枢"
|
||||
description="目标层级树——从使命到行动的分解,支持增删改。"
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<GoalTree goals={goals ?? []} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { useAuthStore } from "@/lib/stores/auth-store";
|
||||
import { AdminShell } from "@/components/layout/AdminShell";
|
||||
import { LoadingState } from "@/components/shared/StateViews";
|
||||
|
||||
/**
|
||||
* 司命台壳层:独立顶栏 + 横向二级导航。
|
||||
* 仅管理员可访问,非管理员重定向至首页。
|
||||
* 等待 BFF + localStorage 初始化完成后才渲染,避免 API 未授权请求。
|
||||
*/
|
||||
export default function AdminLayout({ children }: { children: ReactNode }) {
|
||||
const router = useRouter();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
const loadFromStorage = useAuthStore((s) => s.loadFromStorage);
|
||||
const loadFromBff = useAuthStore((s) => s.loadFromBff);
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// loadFromStorage 同步恢复 user(首屏防闪),loadFromBff 经 BFF 校正权威状态
|
||||
loadFromStorage();
|
||||
loadFromBff()
|
||||
.catch(() => {})
|
||||
.finally(() => setReady(true));
|
||||
}, [loadFromBff, loadFromStorage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && user && !user.is_admin) {
|
||||
router.push("/");
|
||||
}
|
||||
}, [ready, user, router]);
|
||||
|
||||
// 未就绪或不认证时显示加载态
|
||||
if (!ready || !isAuthenticated) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<LoadingState label="辰光未启…" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 已就绪但 user 仍为空(loadFromBff 失败且 localStorage 也无数据)
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<LoadingState label="辰光未启…" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <AdminShell>{children}</AdminShell>;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState, EmptyState } from "@/components/shared/StateViews";
|
||||
import { MemoryAdminList } from "@/components/admin/MemoryAdminList";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { Memory, Paginated } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 息壤枢 · 长期记忆。
|
||||
*
|
||||
* 取所有记忆列表,经 MemoryAdminList 组件做删除/详情查看。
|
||||
* 数据来自 GET /memory(API §8)。
|
||||
*/
|
||||
export default async function AdminMemoryPage() {
|
||||
let memories: Memory[] = [];
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
const data = await serverApi.get<Paginated<Memory>>("/memory", {
|
||||
page_size: 100,
|
||||
});
|
||||
memories = data.items;
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="息壤枢"
|
||||
description="长期记忆 · 自生不息"
|
||||
maxWidth="max-w-3xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : memories.length === 0 ? (
|
||||
<EmptyState
|
||||
title="暂无记忆"
|
||||
description="当前人物还没有沉淀任何长期记忆。"
|
||||
/>
|
||||
) : (
|
||||
<MemoryAdminList initial={memories} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { cn } from "@/lib/utils/cn";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
|
||||
import {
|
||||
PIVOT_FULL_LABEL,
|
||||
PIVOT_MODULE_LABEL,
|
||||
PIVOT_HEALTH_LABEL,
|
||||
AUDIT_DECISION_LABEL,
|
||||
} from "@/lib/constants/labels";
|
||||
|
||||
import { Astrolabe } from "@/components/ui/Astrolabe";
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import type {
|
||||
AuditEntry,
|
||||
Paginated,
|
||||
PendingApproval,
|
||||
PivotHealthReport,
|
||||
} from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/* ============================================================
|
||||
* 司命台 · 总览(对应设计稿 admin-preview.html §❶ 概览)
|
||||
* ============================================================
|
||||
*
|
||||
* 三层布局(设计稿 layout):
|
||||
* ① 朱批告警条 — 动作召唤入口
|
||||
* ② 九枢星盘 + 枢速览 — 空间 hero
|
||||
* ③ 三签概览卡 — 空间/动作/时间三维入口
|
||||
* ④ 起居注近期 — 时间河预览
|
||||
*/
|
||||
|
||||
/** 获取朱批待裁定数量(API §19.2) */
|
||||
async function getPendingCount(): Promise<number> {
|
||||
try {
|
||||
const data = await serverApi.get<Paginated<PendingApproval>>(
|
||||
"/admin/safety/pending",
|
||||
);
|
||||
return data.total ?? data.items.length;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取起居注数量 —— 今日与本周 */
|
||||
async function getAuditCounts(): Promise<{ today: number; week: number }> {
|
||||
try {
|
||||
const data = await serverApi.get<Paginated<AuditEntry>>(
|
||||
"/admin/audit/timeline?limit=100",
|
||||
);
|
||||
const items = data?.items ?? [];
|
||||
const now = new Date();
|
||||
const todayStart = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate(),
|
||||
).getTime();
|
||||
const weekStart = todayStart - 6 * 86400_000;
|
||||
|
||||
return {
|
||||
today: items.filter((e) => {
|
||||
const t = e.created_at ? new Date(e.created_at).getTime() : 0;
|
||||
return t >= todayStart;
|
||||
}).length,
|
||||
week: items.filter((e) => {
|
||||
const t = e.created_at ? new Date(e.created_at).getTime() : 0;
|
||||
return t >= weekStart;
|
||||
}).length,
|
||||
};
|
||||
} catch {
|
||||
return { today: 0, week: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
export default async function AdminPage() {
|
||||
/* ── 并行数据获取 ── */
|
||||
const [reportResult, pendingCount, auditCounts] = await Promise.all([
|
||||
serverApi
|
||||
.get<PivotHealthReport>("/admin/pivots/health")
|
||||
.then((r) => ({ data: r, error: null as string | null }))
|
||||
.catch((e) => ({
|
||||
data: null as PivotHealthReport | null,
|
||||
error: describeError(e),
|
||||
})),
|
||||
getPendingCount(),
|
||||
getAuditCounts(),
|
||||
]);
|
||||
|
||||
const report = reportResult.data;
|
||||
const error = reportResult.error;
|
||||
|
||||
// 起居注近期数据
|
||||
const auditData = await serverApi
|
||||
.get<Paginated<AuditEntry>>("/admin/audit/timeline")
|
||||
.catch(() => null);
|
||||
const recentAudit: AuditEntry[] = auditData?.items.slice(0, 3) ?? [];
|
||||
|
||||
const cracked = (report?.alerts ?? 0) > 0;
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="司命台"
|
||||
description="九枢治理总览 —— 裁定、放行、稽核、守护边界。"
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error || !report ? (
|
||||
<ErrorState message={error ?? "九枢健康度暂不可用。"} />
|
||||
) : (
|
||||
<div className="space-y-5">
|
||||
{/* ===============================================================
|
||||
① 朱批告警条(设计稿 .alert-bar.cracked)
|
||||
动作维度召唤:待裁定项数 + 快捷入口 → 司命枢(安全治理)
|
||||
设计稿 §§ .alert-bar / $.cracked / .alert-dot / .alert-text / .alert-cta
|
||||
=============================================================== */}
|
||||
<Link
|
||||
href="/admin/safety"
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-xl border px-4 py-3.5 text-sm no-underline transition-colors",
|
||||
"hover:bg-surface-hi",
|
||||
cracked
|
||||
? "border-cinnabar-dim bg-cinnabar-wash"
|
||||
: "border-rule bg-surface",
|
||||
)}
|
||||
>
|
||||
{/* 告警圆点 */}
|
||||
<span
|
||||
className={cn(
|
||||
"h-2.5 w-2.5 shrink-0 rounded-full",
|
||||
cracked
|
||||
? "bg-cinnabar shadow-[0_0_0_4px_var(--cinnabar-wash)]"
|
||||
: "bg-patina",
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-medium",
|
||||
cracked ? "text-cinnabar" : "text-patina",
|
||||
)}
|
||||
>
|
||||
{cracked
|
||||
? `${report.alerts} 项朱批待裁定`
|
||||
: "九枢皆稳,无待裁定项"}
|
||||
</span>
|
||||
<span className="ml-auto hidden text-[11px] text-fg-faint sm:inline-flex items-center gap-1.5">
|
||||
<span>司命枢 · 总态</span>
|
||||
<span
|
||||
className={cn(
|
||||
"font-mono text-[11px]",
|
||||
cracked ? "text-cinnabar" : "text-patina",
|
||||
)}
|
||||
>
|
||||
{PIVOT_HEALTH_LABEL[report.overall]}
|
||||
</span>
|
||||
</span>
|
||||
<span className="shrink-0 rounded-md border border-rule px-2.5 py-1 text-[11px] text-fg-dim transition-colors hover:bg-overlay-hover">
|
||||
前往朱批 →
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
{/* ===============================================================
|
||||
② 九枢星盘 + 枢速览(设计稿 .hero-grid)
|
||||
空间维度 hero:左侧星盘 SVG + 右侧九枢健康列表
|
||||
设计稿 §§ .hero-grid / .astrolabe-card / .health-list / .health-item
|
||||
=============================================================== */}
|
||||
<div className="grid gap-4 md:grid-cols-[minmax(0,22rem)_1fr]">
|
||||
{/* 星盘卡 */}
|
||||
<div className="flex flex-col items-center gap-3 rounded-xl border border-rule bg-surface px-4 py-5">
|
||||
<Astrolabe pivots={report.pivots} />
|
||||
{/* 星盘图例(设计稿 .astrolabe-legend) */}
|
||||
<div className="flex flex-wrap justify-center gap-x-4 gap-y-2 font-mono text-[10px] text-fg-faint">
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="inline-block h-2 w-2 rounded-full bg-patina" />
|
||||
稳定
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="inline-block h-2 w-2 rounded-full bg-gilt" />
|
||||
运转中
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="inline-block h-2 w-2 rounded-full bg-cinnabar" />
|
||||
告警
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span
|
||||
className="inline-block h-2 w-2 rounded-full"
|
||||
style={{
|
||||
background: "transparent",
|
||||
border: "1.5px solid var(--fg-faint)",
|
||||
}}
|
||||
/>
|
||||
离线
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 枢速览列表(设计稿 .health-list) */}
|
||||
<div className="overflow-hidden rounded-xl border border-rule bg-surface">
|
||||
{report.pivots.map((p) => {
|
||||
const healthColor = {
|
||||
void: "bg-[var(--fg-faint)] opacity-40",
|
||||
steady: "bg-patina",
|
||||
breathing: "bg-[var(--gilt)]",
|
||||
cracked: "bg-[var(--cinnabar)]",
|
||||
}[p.health];
|
||||
|
||||
const badgeColor = {
|
||||
void: "text-fg-faint",
|
||||
steady: "text-patina",
|
||||
breathing: "text-gilt",
|
||||
cracked: "text-cinnabar",
|
||||
}[p.health];
|
||||
|
||||
const dotStyle =
|
||||
p.health === "void"
|
||||
? {
|
||||
background: "transparent",
|
||||
border: "1.5px solid var(--fg-faint)",
|
||||
}
|
||||
: {};
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={p.pivot}
|
||||
href={`/admin/pivots/${p.pivot}`}
|
||||
className="grid grid-cols-[3px_1fr_auto] items-center gap-3.5 border-b border-rule px-4 py-2.5 text-left no-underline transition-colors last:border-b-0 hover:bg-overlay-hover"
|
||||
>
|
||||
{/* 左缘色条(设计稿 .health-bar) */}
|
||||
<span
|
||||
className={cn(
|
||||
"h-6 w-0.5 shrink-0 rounded-r",
|
||||
healthColor,
|
||||
)}
|
||||
/>
|
||||
{/* 主信息 */}
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<span className="font-serif text-sm font-semibold text-fg">
|
||||
{PIVOT_FULL_LABEL[p.pivot]}
|
||||
</span>
|
||||
<span className="truncate font-mono text-[10px] text-fg-faint">
|
||||
{PIVOT_MODULE_LABEL[p.pivot]}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-px truncate text-xs text-fg-dim">
|
||||
{p.summary}
|
||||
</p>
|
||||
</div>
|
||||
{/* 状态徽记(设计稿 .health-badge) */}
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 shrink-0 text-xs font-medium",
|
||||
badgeColor,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-1.5 w-1.5 rounded-full",
|
||||
healthColor,
|
||||
)}
|
||||
style={dotStyle}
|
||||
/>
|
||||
{PIVOT_HEALTH_LABEL[p.health]}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===============================================================
|
||||
③ 三签概览卡(设计稿 .seals-grid)
|
||||
三个维度入口:星图(空间)/ 朱批(动作)/ 起居注(时间)
|
||||
设计稿 §§ .seals-grid / .seal-card.starmap / .vermilion / .imperial
|
||||
=============================================================== */}
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
{/* 星图 · 空间维度(设计稿 .seal-card.starmap) */}
|
||||
<Link
|
||||
href="/admin"
|
||||
className="group relative block overflow-hidden rounded-xl border border-rule bg-surface px-4 py-4 no-underline transition-all hover:border-patina-dim hover:bg-surface-hi"
|
||||
>
|
||||
{/* 左缘色条:patina(设计稿 .seal-card 左 3px 色条) */}
|
||||
<span className="absolute left-0 top-4 bottom-4 w-[3px] rounded-r bg-patina" />
|
||||
<div className="pl-3">
|
||||
<div className="font-mono text-[10px] tracking-wider text-fg-faint uppercase mb-1.5">
|
||||
空间 · spatial
|
||||
</div>
|
||||
<div className="mb-1 flex items-baseline gap-2">
|
||||
<h3 className="font-serif text-base font-semibold text-fg">
|
||||
星图
|
||||
</h3>
|
||||
<span className="font-mono text-[10.5px] text-patina">
|
||||
StarMap
|
||||
</span>
|
||||
</div>
|
||||
<p className="mb-3 text-[12px] text-fg-faint">
|
||||
权限拓扑 · 九枢授权态势
|
||||
</p>
|
||||
<div className="mb-2 flex items-baseline gap-1.5">
|
||||
<span className="font-mono text-[28px] font-semibold leading-none text-fg">
|
||||
{report.pivots.length}
|
||||
</span>
|
||||
<span className="text-[11.5px] text-fg-faint">枢</span>
|
||||
<span className="text-[11.5px] text-cinnabar">
|
||||
· {report.alerts} 告警
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t border-rule pt-2 text-[11.5px] text-fg-dim">
|
||||
<span>
|
||||
{report.pivots.filter((p) => p.health !== "void").length}{" "}
|
||||
活跃
|
||||
</span>
|
||||
<span className="font-mono text-fg-faint opacity-0 transition-opacity group-hover:opacity-100">
|
||||
星盘 →
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* 朱批 · 动作维度(设计稿 .seal-card.vermilion) */}
|
||||
<Link
|
||||
href="/admin/safety"
|
||||
className="group relative block overflow-hidden rounded-xl border border-rule bg-surface px-4 py-4 no-underline transition-all hover:border-cinnabar-dim hover:bg-surface-hi"
|
||||
>
|
||||
{/* 左缘色条:cinnabar */}
|
||||
<span className="absolute left-0 top-4 bottom-4 w-[3px] rounded-r bg-cinnabar" />
|
||||
<div className="pl-3">
|
||||
<div className="font-mono text-[10px] tracking-wider text-fg-faint uppercase mb-1.5">
|
||||
动作 · action
|
||||
</div>
|
||||
<div className="mb-1 flex items-baseline gap-2">
|
||||
<h3 className="font-serif text-base font-semibold text-fg">
|
||||
朱批
|
||||
</h3>
|
||||
<span className="font-mono text-[10.5px] text-cinnabar">
|
||||
VermilionAction
|
||||
</span>
|
||||
</div>
|
||||
<p className="mb-3 text-[12px] text-fg-faint">
|
||||
确认裁决 · 高风险工具裁定
|
||||
</p>
|
||||
<div className="mb-2 flex items-baseline gap-1.5">
|
||||
<span className="font-mono text-[28px] font-semibold leading-none text-fg">
|
||||
{pendingCount}
|
||||
</span>
|
||||
<span className="text-[11.5px] text-fg-faint">项待裁定</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t border-rule pt-2 text-[11.5px] text-fg-dim">
|
||||
<span>司命枢 →</span>
|
||||
<span className="font-mono text-fg-faint opacity-0 transition-opacity group-hover:opacity-100">
|
||||
朱批 →
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{/* 起居注 · 时间维度(设计稿 .seal-card.imperial) */}
|
||||
<Link
|
||||
href="/admin/audit"
|
||||
className="group relative block overflow-hidden rounded-xl border border-rule bg-surface px-4 py-4 no-underline transition-all hover:border-gilt-dim hover:bg-surface-hi"
|
||||
>
|
||||
{/* 左缘色条:gilt */}
|
||||
<span className="absolute left-0 top-4 bottom-4 w-[3px] rounded-r bg-gilt" />
|
||||
<div className="pl-3">
|
||||
<div className="font-mono text-[10px] tracking-wider text-fg-faint uppercase mb-1.5">
|
||||
时间 · temporal
|
||||
</div>
|
||||
<div className="mb-1 flex items-baseline gap-2">
|
||||
<h3 className="font-serif text-base font-semibold text-fg">
|
||||
起居注
|
||||
</h3>
|
||||
<span className="font-mono text-[10.5px] text-gilt">
|
||||
ImperialLog
|
||||
</span>
|
||||
</div>
|
||||
<p className="mb-3 text-[12px] text-fg-faint">
|
||||
审计流水 · 不可篡改
|
||||
</p>
|
||||
<div className="mb-2 flex items-baseline gap-1.5">
|
||||
<span className="font-mono text-[28px] font-semibold leading-none text-fg">
|
||||
{auditCounts.today}
|
||||
</span>
|
||||
<span className="text-[11.5px] text-fg-faint">条 · 今日</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t border-rule pt-2 text-[11.5px] text-fg-dim">
|
||||
<span>本周 {auditCounts.week} 条</span>
|
||||
<span className="font-mono text-fg-faint opacity-0 transition-opacity group-hover:opacity-100">
|
||||
起居注 →
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* ===============================================================
|
||||
④ 起居注 · 近期(设计稿起居注段)
|
||||
时间维度预览,3 条实时数据。
|
||||
设计稿 §§ .audit-head / .audit-item / .pill.decision
|
||||
=============================================================== */}
|
||||
<div className="overflow-hidden rounded-xl border border-rule bg-surface">
|
||||
{/* 起居注标题行 */}
|
||||
<div className="flex items-center justify-between border-b border-rule px-4 py-3">
|
||||
<h3 className="font-serif text-sm font-semibold text-fg">
|
||||
起居注 · 近期
|
||||
</h3>
|
||||
<Link
|
||||
href="/admin/audit"
|
||||
className="inline-flex items-center gap-1 rounded-md border border-rule px-2.5 py-1 text-[11.5px] text-fg-dim no-underline transition-colors hover:bg-overlay-hover"
|
||||
>
|
||||
进入起居注 →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{recentAudit.length > 0 ? (
|
||||
recentAudit.map((entry) => {
|
||||
const pillClass = {
|
||||
auto: "border-patina-dim text-patina bg-patina-wash",
|
||||
approved: "border-patina-dim text-patina bg-patina-wash",
|
||||
denied: "border-cinnabar-dim text-cinnabar bg-cinnabar-wash",
|
||||
blocked: "border-cinnabar-dim text-cinnabar bg-cinnabar-wash",
|
||||
}[entry.decision];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={entry.audit_id}
|
||||
className="grid grid-cols-[4.5rem_1fr_auto] gap-3.5 border-b border-rule px-4 py-3 last:border-b-0"
|
||||
>
|
||||
{/* 时间 */}
|
||||
<span className="font-mono text-[11px] text-fg-faint">
|
||||
{formatAuditTime(entry.created_at)}
|
||||
</span>
|
||||
{/* 内容 */}
|
||||
<div className="min-w-0">
|
||||
<div className="mb-0.5 flex items-center gap-2 flex-wrap">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 font-mono text-[10px] leading-none",
|
||||
pillClass,
|
||||
)}
|
||||
>
|
||||
{AUDIT_DECISION_LABEL[entry.decision]}
|
||||
</span>
|
||||
<span className="text-xs font-medium text-fg">
|
||||
{PIVOT_FULL_LABEL[entry.pivot] ?? entry.pivot}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-[12px] text-fg-dim leading-relaxed">
|
||||
{entry.summary}
|
||||
{entry.subject && (
|
||||
<span className="ml-1.5 font-mono text-[11px] text-fg">
|
||||
{entry.subject}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{/* trace 链接 */}
|
||||
{entry.trace_id && (
|
||||
<span className="self-center font-mono text-[10.5px] text-fg-faint">
|
||||
trace · {entry.trace_id.slice(0, 6)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="px-4 py-8 text-center text-xs text-fg-faint">
|
||||
暂无审计记录。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 ISO 时间为中文字段时间描述。
|
||||
* 今日显示时辰(申时三刻),昨日显示"昨日 HH:mm",更早显示日期。
|
||||
*/
|
||||
function formatAuditTime(iso?: string): string {
|
||||
if (!iso) return "——";
|
||||
try {
|
||||
const d = new Date(iso);
|
||||
const now = new Date();
|
||||
const isToday =
|
||||
d.getFullYear() === now.getFullYear() &&
|
||||
d.getMonth() === now.getMonth() &&
|
||||
d.getDate() === now.getDate();
|
||||
|
||||
if (isToday) {
|
||||
const hour = d.getHours();
|
||||
const min = d.getMinutes();
|
||||
const periodNames = [
|
||||
"子",
|
||||
"丑",
|
||||
"寅",
|
||||
"卯",
|
||||
"辰",
|
||||
"巳",
|
||||
"午",
|
||||
"未",
|
||||
"申",
|
||||
"酉",
|
||||
"戌",
|
||||
"亥",
|
||||
];
|
||||
const period = periodNames[Math.floor(hour / 2)];
|
||||
const ke = Math.floor(min / 15) + 1;
|
||||
const keNames = ["初", "一刻", "二刻", "三刻"];
|
||||
return `${period}时${keNames[ke - 1] ?? ""}`;
|
||||
}
|
||||
|
||||
const yesterday = new Date(now);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const isYesterday =
|
||||
d.getFullYear() === yesterday.getFullYear() &&
|
||||
d.getMonth() === yesterday.getMonth() &&
|
||||
d.getDate() === yesterday.getDate();
|
||||
|
||||
if (isYesterday) {
|
||||
return `昨日 ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
return `${d.getMonth() + 1}月${d.getDate()}日`;
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { PerceptionConsentMatrix } from "@/components/admin/PerceptionConsentMatrix";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { PerceptionChannel } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminPerceptionPage() {
|
||||
let channels: PerceptionChannel[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
channels = await serverApi.get("/admin/perception/channels");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="烛照枢"
|
||||
description="感知通道授权——管理六类感知源的同意状态与隐私级别。"
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<PerceptionConsentMatrix channels={channels ?? []} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState, EmptyState } from "@/components/shared/StateViews";
|
||||
import { VermilionAction } from "@/components/admin/VermilionAction";
|
||||
import { PolicyMatrix } from "@/components/admin/PolicyMatrix";
|
||||
import { AuditConsole } from "@/components/admin/AuditConsole";
|
||||
import { ToolRegistry } from "@/components/admin/ToolRegistry";
|
||||
import { MemoryAdminList } from "@/components/admin/MemoryAdminList";
|
||||
import { PersonalityEditor } from "@/components/admin/PersonalityEditor";
|
||||
import { GoalTree } from "@/components/admin/GoalTree";
|
||||
import { PerceptionConsentMatrix } from "@/components/admin/PerceptionConsentMatrix";
|
||||
import { WorldEntityList } from "@/components/admin/WorldEntityList";
|
||||
import { PlanConsole } from "@/components/admin/PlanConsole";
|
||||
import { AgentRoster } from "@/components/admin/AgentRoster";
|
||||
import { McpServerPanel } from "@/components/admin/mcp/McpServerPanel";
|
||||
import { GatewayPanel } from "@/components/admin/mcp/GatewayPanel";
|
||||
import { ToolRuntimeTabs } from "@/components/admin/mcp/ToolRuntimeTabs";
|
||||
import { SkillManager } from "@/components/tools/SkillManager";
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { Pivot } from "@/types/domain";
|
||||
import type {
|
||||
AgentRosterEntry,
|
||||
AuditEntry,
|
||||
GoalNode,
|
||||
McpServer,
|
||||
Memory,
|
||||
Paginated,
|
||||
PendingApproval,
|
||||
PerceptionChannel,
|
||||
PersonalityVersion,
|
||||
PlanSummary,
|
||||
SafetyPolicy,
|
||||
Skill,
|
||||
ToolInfo,
|
||||
WorldEntity,
|
||||
} from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const VALID_PIVOTS: Pivot[] = [
|
||||
"personality_core",
|
||||
"goal_orchestrator",
|
||||
"safety_governor",
|
||||
"perception",
|
||||
"world_model",
|
||||
"planner",
|
||||
"memory",
|
||||
"tool_runtime",
|
||||
"agent_society",
|
||||
];
|
||||
|
||||
const PIVOT_SET = new Set<string>(VALID_PIVOTS);
|
||||
|
||||
/**
|
||||
* 枢详情。九枢均已落地治理视图(设计 §9 分步已基本覆盖):
|
||||
* 司命(朱批+权限矩阵)、百工(工具注册表)、灵核(人格摘要)、息壤(记忆列表)、
|
||||
* 太一(目标树)、烛照(感知授权)、河图(世界实体)、洛书(计划)、天工阁(角色名册)。
|
||||
*/
|
||||
export default async function PivotDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ pivot: string }>;
|
||||
}) {
|
||||
const { pivot } = await params;
|
||||
if (!PIVOT_SET.has(pivot)) notFound();
|
||||
const key = pivot as Pivot;
|
||||
|
||||
switch (key) {
|
||||
case "safety_governor":
|
||||
return <SafetyGovernorDetail />;
|
||||
case "tool_runtime":
|
||||
return <ToolRuntimeDetail />;
|
||||
case "personality_core":
|
||||
return <PersonalityCoreDetail />;
|
||||
case "memory":
|
||||
return <MemorySystemDetail />;
|
||||
case "goal_orchestrator":
|
||||
return <GoalOrchestratorDetail />;
|
||||
case "perception":
|
||||
return <PerceptionDetail />;
|
||||
case "world_model":
|
||||
return <WorldModelDetail />;
|
||||
case "planner":
|
||||
return <PlannerDetail />;
|
||||
case "agent_society":
|
||||
return <AgentSocietyDetail />;
|
||||
default:
|
||||
notFound();
|
||||
}
|
||||
}
|
||||
|
||||
/** 司命枢:朱批待办墙(动作)+ 权限策略矩阵 + 安全审计事件 */
|
||||
async function SafetyGovernorDetail() {
|
||||
let pending: PendingApproval[] = [];
|
||||
let policies: SafetyPolicy[] = [];
|
||||
let auditEvents: AuditEntry[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
const [pendingData, policyData, auditData] = await Promise.all([
|
||||
serverApi.get<Paginated<PendingApproval>>("/admin/safety/pending"),
|
||||
serverApi.get<SafetyPolicy[]>("/safety/policies"),
|
||||
serverApi.get<Paginated<AuditEntry>>("/safety/audit-events", {
|
||||
page_size: 20,
|
||||
}),
|
||||
]);
|
||||
pending = pendingData.items;
|
||||
policies = policyData;
|
||||
auditEvents = auditData.items;
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="司命枢"
|
||||
description="安全与裁定 · 权限、确认、审计"
|
||||
maxWidth="max-w-4xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
朱批待办{" "}
|
||||
<span className="font-sans text-xs text-muted">
|
||||
({pending.length})
|
||||
</span>
|
||||
</h2>
|
||||
{pending.length === 0 ? (
|
||||
<EmptyState
|
||||
title="无待裁定项"
|
||||
description="当前没有需要裁定的工具调用。"
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{pending.map((a) => (
|
||||
<VermilionAction key={a.confirmation_id} approval={a} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
权限策略矩阵
|
||||
</h2>
|
||||
<PolicyMatrix policies={policies} />
|
||||
</section>
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
安全审计事件
|
||||
<span className="font-sans text-xs text-muted">
|
||||
{" "}
|
||||
({auditEvents.length})
|
||||
</span>
|
||||
</h2>
|
||||
{auditEvents.length === 0 ? (
|
||||
<EmptyState
|
||||
title="暂无安全审计事件"
|
||||
description="当前没有安全相关的审计记录。"
|
||||
/>
|
||||
) : (
|
||||
<AuditConsole entries={auditEvents} />
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/** 百工枢(百工阁):能力总览 + 工具注册表 + Skill 能力包 + MCP 服务器统一治理。 */
|
||||
async function ToolRuntimeDetail() {
|
||||
let tools: ToolInfo[] = [];
|
||||
let skills: Skill[] = [];
|
||||
let mcpServers: McpServer[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
[tools, skills, mcpServers] = await Promise.all([
|
||||
serverApi.get<ToolInfo[]>("/tools"),
|
||||
serverApi.get<Skill[]>("/tools/skills"),
|
||||
serverApi.get<McpServer[]>("/tools/mcp-servers"),
|
||||
]);
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="百工枢"
|
||||
description="器用技艺 · 能力注册、工具、MCP 与 Skill 统一治理"
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* 四标签页 */}
|
||||
<ToolRuntimeTabs
|
||||
tabs={[
|
||||
{
|
||||
key: "tools",
|
||||
label: "工具",
|
||||
badge: tools.length,
|
||||
content: (
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
工具注册表
|
||||
</h2>
|
||||
<ToolRegistry tools={tools} />
|
||||
</section>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "mcp",
|
||||
label: "MCP 服务器",
|
||||
badge: mcpServers.length,
|
||||
content: (
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
MCP 服务器
|
||||
</h2>
|
||||
<McpServerPanel initialServers={mcpServers} />
|
||||
</section>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "gateway",
|
||||
label: "玄关网关",
|
||||
content: (
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
玄关网关管理
|
||||
</h2>
|
||||
<GatewayPanel />
|
||||
</section>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "skills",
|
||||
label: "Skill 能力包",
|
||||
badge: skills.length,
|
||||
content: (
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
Skill 能力包
|
||||
</h2>
|
||||
<SkillManager initial={skills} />
|
||||
</section>
|
||||
),
|
||||
},
|
||||
]}
|
||||
defaultTab="tools"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/** 灵核枢:人格各字段 inline 可编辑(核心人格 L4 走提议审核) */
|
||||
async function PersonalityCoreDetail() {
|
||||
let persona: PersonalityVersion | null = null;
|
||||
let error: string | null = null;
|
||||
try {
|
||||
persona = await serverApi.get<PersonalityVersion>("/personality/current");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="灵核枢"
|
||||
description="人格与身份 · 每个字段均可编辑"
|
||||
maxWidth="max-w-3xl"
|
||||
>
|
||||
{error || !persona ? (
|
||||
<ErrorState message={error ?? "人格信息暂不可用。"} />
|
||||
) : (
|
||||
<PersonalityEditor persona={persona} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/** 息壤枢:长期记忆列表(自生不息) */
|
||||
async function MemorySystemDetail() {
|
||||
let memories: Memory[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
const data = await serverApi.get<Paginated<Memory>>("/memory", {
|
||||
page_size: 100,
|
||||
});
|
||||
memories = data.items;
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="息壤枢"
|
||||
description="长期记忆 · 自生不息"
|
||||
maxWidth="max-w-3xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : memories.length === 0 ? (
|
||||
<EmptyState
|
||||
title="暂无记忆"
|
||||
description="当前人物还没有沉淀任何长期记忆。"
|
||||
/>
|
||||
) : (
|
||||
<MemoryAdminList initial={memories} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/** 太一枢:目标层级树(最高目标 → 任务,可编辑标题/优先级/状态) */
|
||||
async function GoalOrchestratorDetail() {
|
||||
let goals: GoalNode[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
goals = await serverApi.get<GoalNode[]>("/admin/goals");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="太一枢"
|
||||
description="目标统御 · 最高目标与任务调度"
|
||||
maxWidth="max-w-3xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : goals.length === 0 ? (
|
||||
<EmptyState title="暂无目标" description="太一枢尚未编排任何目标。" />
|
||||
) : (
|
||||
<GoalTree goals={goals} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/** 烛照枢:感知通道授权矩阵(启用 / 授权 / 隐私路由) */
|
||||
async function PerceptionDetail() {
|
||||
let channels: PerceptionChannel[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
channels = await serverApi.get<PerceptionChannel[]>(
|
||||
"/admin/perception/channels",
|
||||
);
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="烛照枢"
|
||||
description="感知世界 · 通道授权与隐私边界"
|
||||
maxWidth="max-w-3xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<PerceptionConsentMatrix channels={channels} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/** 河图枢:世界实体列表(类型 / 事实-推断 / 置信度) */
|
||||
async function WorldModelDetail() {
|
||||
let entities: WorldEntity[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
entities = await serverApi.get<WorldEntity[]>("/admin/world/entities");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="河图枢"
|
||||
description="世界模型 · 实体与关系图式"
|
||||
maxWidth="max-w-3xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : entities.length === 0 ? (
|
||||
<EmptyState
|
||||
title="暂无世界实体"
|
||||
description="河图枢尚未建模任何实体。"
|
||||
/>
|
||||
) : (
|
||||
<WorldEntityList entities={entities} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/** 洛书枢:计划控制台(状态 / 假设 / 风险) */
|
||||
async function PlannerDetail() {
|
||||
let plans: PlanSummary[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
plans = await serverApi.get<PlanSummary[]>("/admin/plans");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="洛书枢"
|
||||
description="推演变化 · 规划与流程编排"
|
||||
maxWidth="max-w-3xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : plans.length === 0 ? (
|
||||
<EmptyState title="暂无计划" description="洛书枢尚未编排任何计划。" />
|
||||
) : (
|
||||
<PlanConsole plans={plans} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/** 天工阁枢:九角色在岗名册(启用 / 停用) */
|
||||
async function AgentSocietyDetail() {
|
||||
let roster: AgentRosterEntry[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
roster = await serverApi.get<AgentRosterEntry[]>("/admin/agents/roster");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="天工阁枢"
|
||||
description="众智能协作 · 九角色分工与协作"
|
||||
maxWidth="max-w-3xl"
|
||||
>
|
||||
{error ? <ErrorState message={error} /> : <AgentRoster roster={roster} />}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { PlanConsole } from "@/components/admin/PlanConsole";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { PlanSummary } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminPlansPage() {
|
||||
let plans: PlanSummary[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
plans = await serverApi.get("/admin/plans");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="洛书枢"
|
||||
description="计划控制台——规划治理,支持增删改。"
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<PlanConsole plans={plans ?? []} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState, EmptyState } from "@/components/shared/StateViews";
|
||||
import { VermilionAction } from "@/components/admin/VermilionAction";
|
||||
import { PolicyMatrix } from "@/components/admin/PolicyMatrix";
|
||||
import { AuditConsole } from "@/components/admin/AuditConsole";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type {
|
||||
AuditEntry,
|
||||
Paginated,
|
||||
PendingApproval,
|
||||
SafetyPolicy,
|
||||
} from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 司命枢 · 安全与裁定(对应设计稿 admin-preview.html 司命枢 视图)。
|
||||
*
|
||||
* 三层布局:
|
||||
* ① 朱批待办 — 高风险待裁定工具调用
|
||||
* ② 权限策略矩阵 — tool × risk × permission
|
||||
* ③ 安全审计事件 — 近期裁定与拦截
|
||||
*/
|
||||
export default async function AdminSafetyPage() {
|
||||
let pending: PendingApproval[] = [];
|
||||
let policies: SafetyPolicy[] = [];
|
||||
let auditEvents: AuditEntry[] = [];
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
const [pendingData, policyData, auditData] = await Promise.all([
|
||||
serverApi.get<Paginated<PendingApproval>>("/admin/safety/pending"),
|
||||
serverApi.get<SafetyPolicy[]>("/safety/policies"),
|
||||
serverApi.get<Paginated<AuditEntry>>("/safety/audit-events", {
|
||||
page_size: 20,
|
||||
}),
|
||||
]);
|
||||
pending = pendingData.items;
|
||||
policies = policyData;
|
||||
auditEvents = auditData.items;
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="司命枢"
|
||||
description="安全与裁定 · 权限、确认、审计"
|
||||
maxWidth="max-w-4xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{/* ① 朱批待办 */}
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
朱批待办{" "}
|
||||
<span className="font-sans text-xs text-muted">
|
||||
({pending.length})
|
||||
</span>
|
||||
</h2>
|
||||
{pending.length === 0 ? (
|
||||
<EmptyState
|
||||
title="无待裁定项"
|
||||
description="当前没有需要裁定的工具调用。"
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{pending.map((a) => (
|
||||
<VermilionAction key={a.confirmation_id} approval={a} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* ② 权限策略矩阵 */}
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
权限策略矩阵
|
||||
</h2>
|
||||
<PolicyMatrix policies={policies} />
|
||||
</section>
|
||||
|
||||
{/* ③ 安全审计事件 */}
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
安全审计事件
|
||||
<span className="font-sans text-xs text-muted">
|
||||
{" "}
|
||||
({auditEvents.length})
|
||||
</span>
|
||||
</h2>
|
||||
{auditEvents.length === 0 ? (
|
||||
<EmptyState
|
||||
title="暂无安全审计事件"
|
||||
description="当前没有安全相关的审计记录。"
|
||||
/>
|
||||
) : (
|
||||
<AuditConsole entries={auditEvents} />
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { apiGet } from "@/lib/api/client";
|
||||
import type {
|
||||
TenantCharacterBrief,
|
||||
TenantMemoryBrief,
|
||||
TenantOverview,
|
||||
} from "@/types/api";
|
||||
|
||||
export default function AdminTenantsPage() {
|
||||
const [tenants, setTenants] = useState<TenantOverview[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedUserId, setSelectedUserId] = useState<string | null>(null);
|
||||
const [details, setDetails] = useState<{
|
||||
characters: TenantCharacterBrief[];
|
||||
memories: TenantMemoryBrief[];
|
||||
} | null>(null);
|
||||
|
||||
const fetchTenants = useCallback(async () => {
|
||||
try {
|
||||
const data = await apiGet<TenantOverview[]>("/admin/tenants/overview");
|
||||
setTenants(data);
|
||||
} catch {
|
||||
/* error handled by ApiError */
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
fetchTenants();
|
||||
}, [fetchTenants]);
|
||||
|
||||
const viewDetails = async (userId: string) => {
|
||||
setSelectedUserId(userId);
|
||||
try {
|
||||
const [characters, memories] = await Promise.all([
|
||||
apiGet<TenantCharacterBrief[]>(`/admin/tenants/${userId}/characters`),
|
||||
apiGet<TenantMemoryBrief[]>(`/admin/tenants/${userId}/memories`),
|
||||
]);
|
||||
setDetails({ characters, memories });
|
||||
} catch {
|
||||
setDetails(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl px-4 py-8">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-lg font-semibold text-fg">多租户管理</h1>
|
||||
<p className="mt-0.5 text-xs text-muted">跨用户数据总览</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-16 text-xs text-muted">
|
||||
加载中…
|
||||
</div>
|
||||
) : tenants.length === 0 ? (
|
||||
<div className="flex items-center justify-center py-16 text-xs text-muted">
|
||||
暂无用户
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-xl border border-border-glass">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border-glass bg-glass text-xs font-medium text-muted">
|
||||
<th className="px-4 py-3">用户</th>
|
||||
<th className="px-4 py-3">角色</th>
|
||||
<th className="px-4 py-3 text-right">人物数</th>
|
||||
<th className="px-4 py-3 text-right">记忆数</th>
|
||||
<th className="px-4 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tenants.map((t) => (
|
||||
<tr
|
||||
key={t.user_id}
|
||||
className="border-b border-border-glass last:border-0"
|
||||
>
|
||||
<td className="px-4 py-3">
|
||||
<div>
|
||||
<span className="font-medium text-fg">{t.username}</span>
|
||||
<span className="ml-2 text-xs text-muted">{t.email}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-[11px] font-medium ${
|
||||
t.is_admin
|
||||
? "bg-patina/15 text-patina"
|
||||
: "bg-fg/5 text-muted"
|
||||
}`}
|
||||
>
|
||||
{t.is_admin ? "管理员" : "普通用户"}
|
||||
</span>
|
||||
{!t.is_active && (
|
||||
<span className="ml-1.5 rounded-full bg-cinnabar/15 px-2 py-0.5 text-[11px] font-medium text-cinnabar">
|
||||
已禁用
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right text-fg">
|
||||
{t.character_count}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right text-fg">
|
||||
{t.memory_count}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => viewDetails(t.user_id)}
|
||||
className="rounded-lg px-2.5 py-1 text-[11px] font-medium text-patina transition-colors hover:bg-patina/10"
|
||||
>
|
||||
查看详情
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedUserId && details && (
|
||||
<div className="mt-8">
|
||||
<div className="mb-4 grid grid-cols-2 gap-4">
|
||||
<div className="rounded-xl border border-border-glass p-4">
|
||||
<h2 className="mb-3 text-sm font-medium text-fg">
|
||||
人物 ({details.characters.length})
|
||||
</h2>
|
||||
{details.characters.length === 0 ? (
|
||||
<p className="text-xs text-muted">无人物</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{details.characters.map((c) => (
|
||||
<div
|
||||
key={c.id}
|
||||
className="rounded-lg bg-glass px-3 py-2 text-xs"
|
||||
>
|
||||
<span className="font-medium text-fg">{c.name}</span>
|
||||
<span className="ml-2 text-muted">({c.status})</span>
|
||||
{c.display_name && (
|
||||
<span className="ml-2 text-muted">
|
||||
/{c.display_name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-xl border border-border-glass p-4">
|
||||
<h2 className="mb-3 text-sm font-medium text-fg">
|
||||
记忆 ({details.memories.length})
|
||||
</h2>
|
||||
{details.memories.length === 0 ? (
|
||||
<p className="text-xs text-muted">无记忆</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{details.memories.map((m) => (
|
||||
<div
|
||||
key={m.id}
|
||||
className="rounded-lg bg-glass px-3 py-2 text-xs"
|
||||
>
|
||||
<span className="font-medium text-fg">{m.title}</span>
|
||||
<span className="ml-2 text-muted">({m.type})</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState, EmptyState } from "@/components/shared/StateViews";
|
||||
import { ToolRegistry } from "@/components/admin/ToolRegistry";
|
||||
import { McpServerPanel } from "@/components/admin/mcp/McpServerPanel";
|
||||
import { GatewayPanel } from "@/components/admin/mcp/GatewayPanel";
|
||||
import { ToolRuntimeTabs } from "@/components/admin/mcp/ToolRuntimeTabs";
|
||||
import { SkillManager } from "@/components/tools/SkillManager";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { McpServer, Skill, ToolInfo } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* 百工枢 · 器用技艺治理(对应设计稿 admin-preview.html 百工枢 视图)。
|
||||
*
|
||||
* 四标签页:
|
||||
* ① 工具注册表 — 清单与管理
|
||||
* ② MCP 服务器 — 外部能力接入
|
||||
* ③ 玄关网关 — 路由与代理
|
||||
* ④ Skill 能力包 — 可插拔能力
|
||||
*/
|
||||
export default async function AdminToolsPage() {
|
||||
let tools: ToolInfo[] = [];
|
||||
let skills: Skill[] = [];
|
||||
let mcpServers: McpServer[] = [];
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
[tools, skills, mcpServers] = await Promise.all([
|
||||
serverApi.get<ToolInfo[]>("/tools"),
|
||||
serverApi.get<Skill[]>("/tools/skills"),
|
||||
serverApi.get<McpServer[]>("/tools/mcp-servers"),
|
||||
]);
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="百工枢"
|
||||
description="器用技艺 · 能力注册、工具、MCP 与 Skill 统一治理"
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
<ToolRuntimeTabs
|
||||
tabs={[
|
||||
{
|
||||
key: "tools",
|
||||
label: "工具",
|
||||
badge: tools.length,
|
||||
content: (
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
工具注册表
|
||||
</h2>
|
||||
{tools.length === 0 ? (
|
||||
<EmptyState
|
||||
title="暂无注册工具"
|
||||
description="当前没有注册的工具。"
|
||||
/>
|
||||
) : (
|
||||
<ToolRegistry tools={tools} />
|
||||
)}
|
||||
</section>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "mcp",
|
||||
label: "MCP 服务器",
|
||||
badge: mcpServers.length,
|
||||
content: (
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
MCP 服务器
|
||||
</h2>
|
||||
<McpServerPanel initialServers={mcpServers} />
|
||||
</section>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "gateway",
|
||||
label: "玄关网关",
|
||||
content: (
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
玄关网关管理
|
||||
</h2>
|
||||
<GatewayPanel />
|
||||
</section>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "skills",
|
||||
label: "Skill 能力包",
|
||||
badge: skills.length,
|
||||
content: (
|
||||
<section>
|
||||
<h2 className="mb-3 font-serif text-sm font-semibold text-fg">
|
||||
Skill 能力包
|
||||
</h2>
|
||||
{skills.length === 0 ? (
|
||||
<EmptyState
|
||||
title="暂无 Skill"
|
||||
description="当前没有安装 Skill 能力包。"
|
||||
/>
|
||||
) : (
|
||||
<SkillManager initial={skills} />
|
||||
)}
|
||||
</section>
|
||||
),
|
||||
},
|
||||
]}
|
||||
defaultTab="tools"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
"use client";
|
||||
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState, LoadingState } from "@/components/shared/StateViews";
|
||||
import { TraceList } from "@/components/admin/TraceList";
|
||||
import { TraceSearch } from "@/components/admin/TraceSearch";
|
||||
import { TraceTimeline } from "@/components/admin/TraceTimeline";
|
||||
import { adminApi } from "@/lib/api";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { TraceDetail, TraceSummary } from "@/types/api";
|
||||
|
||||
/**
|
||||
* 链路追踪 · 列表模式:默认展示所有 trace(最近活动倒序)。
|
||||
* 顶部搜索框可跳转到指定 trace;点击列表项进入详情时间线。
|
||||
*/
|
||||
function TraceListView() {
|
||||
const router = useRouter();
|
||||
const [items, setItems] = useState<TraceSummary[] | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
adminApi
|
||||
.listTraces()
|
||||
.then((res) => {
|
||||
if (!cancelled) setItems(res.items);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (!cancelled) setError(describeError(e));
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
function jumpToTrace() {
|
||||
const trimmed = query.trim();
|
||||
if (trimmed) {
|
||||
router.push(`/admin/traces?trace_id=${encodeURIComponent(trimmed)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<TraceSearch
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
onSubmit={jumpToTrace}
|
||||
loading={false}
|
||||
/>
|
||||
{loading && <LoadingState label="正在汇聚链路…" />}
|
||||
{!loading && error && <ErrorState message={error} />}
|
||||
{!loading && !error && items && <TraceList items={items} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 链路追踪 · 详情模式:展示指定 trace_id 的时间线。
|
||||
* 由父级以 key={traceId} 挂载,trace 切换即重挂载,loading 初值随之重置,
|
||||
* 无需在 effect 内同步 setState。
|
||||
*/
|
||||
function TraceDetailView({ traceId }: { traceId: string }) {
|
||||
const router = useRouter();
|
||||
const [detail, setDetail] = useState<TraceDetail | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
adminApi
|
||||
.getTrace(traceId)
|
||||
.then((data) => {
|
||||
if (!cancelled) setDetail(data);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (!cancelled) setError(describeError(e));
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [traceId]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push("/admin/traces")}
|
||||
className="text-xs text-muted transition-colors hover:text-fg"
|
||||
>
|
||||
← 全部链路
|
||||
</button>
|
||||
<div className="rounded-xl border border-border-glass bg-glass px-4 py-2.5">
|
||||
<span className="font-mono text-xs text-fg">{traceId}</span>
|
||||
</div>
|
||||
{loading && <LoadingState label="正在汇聚链路…" />}
|
||||
{!loading && error && <ErrorState message={error} />}
|
||||
{!loading && !error && detail && <TraceTimeline detail={detail} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TracesBody() {
|
||||
const searchParams = useSearchParams();
|
||||
const focusTraceId = searchParams.get("trace_id") ?? "";
|
||||
return focusTraceId ? (
|
||||
<TraceDetailView key={focusTraceId} traceId={focusTraceId} />
|
||||
) : (
|
||||
<TraceListView />
|
||||
);
|
||||
}
|
||||
|
||||
export default function TracesPage() {
|
||||
return (
|
||||
<PageContainer
|
||||
title="链路追踪"
|
||||
description="汇聚运行事件、安全审计与请求幂等的全部链路,按最近活动倒序。"
|
||||
maxWidth="max-w-4xl"
|
||||
>
|
||||
<Suspense fallback={<LoadingState label="正在准备链路追踪…" />}>
|
||||
<TracesBody />
|
||||
</Suspense>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import type { AdminCreateUserBody, AdminUser } from "@/lib/api/admin";
|
||||
import { createUser, listUsers, updateUser } from "@/lib/api/admin";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
|
||||
export default function AdminUsersPage() {
|
||||
const [users, setUsers] = useState<AdminUser[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showCreate, setShowCreate] = useState(false);
|
||||
|
||||
const fetchUsers = useCallback(async () => {
|
||||
try {
|
||||
const data = await listUsers();
|
||||
setUsers(data);
|
||||
} catch {
|
||||
/* 错误由 ApiError 统一处理 */
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
fetchUsers();
|
||||
}, [fetchUsers]);
|
||||
|
||||
const handleToggleActive = async (user: AdminUser) => {
|
||||
await updateUser(user.id, { is_active: !user.is_active });
|
||||
await fetchUsers();
|
||||
};
|
||||
|
||||
const handleToggleAdmin = async (user: AdminUser) => {
|
||||
await updateUser(user.id, { is_admin: !user.is_admin });
|
||||
await fetchUsers();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-4xl px-4 py-8">
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-fg">用户管理</h1>
|
||||
<p className="mt-0.5 text-xs text-muted">管理所有注册用户</p>
|
||||
</div>
|
||||
<Button variant="primary" onClick={() => setShowCreate(true)}>
|
||||
创建用户
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showCreate && (
|
||||
<CreateUserForm
|
||||
onDone={() => {
|
||||
setShowCreate(false);
|
||||
fetchUsers();
|
||||
}}
|
||||
onCancel={() => setShowCreate(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-16 text-xs text-muted">
|
||||
加载中…
|
||||
</div>
|
||||
) : users.length === 0 ? (
|
||||
<div className="flex items-center justify-center py-16 text-xs text-muted">
|
||||
暂无用户
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-xl border border-border-glass">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border-glass bg-glass text-xs font-medium text-muted">
|
||||
<th className="px-4 py-3">用户名</th>
|
||||
<th className="px-4 py-3">邮箱</th>
|
||||
<th className="px-4 py-3">角色</th>
|
||||
<th className="px-4 py-3">状态</th>
|
||||
<th className="px-4 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr
|
||||
key={user.id}
|
||||
className="border-b border-border-glass last:border-0"
|
||||
>
|
||||
<td className="px-4 py-3 font-medium text-fg">
|
||||
{user.username}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-muted">{user.email}</td>
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-[11px] font-medium ${
|
||||
user.is_admin
|
||||
? "bg-patina/15 text-patina"
|
||||
: "bg-fg/5 text-muted"
|
||||
}`}
|
||||
>
|
||||
{user.is_admin ? "管理员" : "普通用户"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 text-xs ${
|
||||
user.is_active ? "text-patina" : "text-muted"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`h-1.5 w-1.5 rounded-full ${
|
||||
user.is_active ? "bg-patina" : "bg-muted"
|
||||
}`}
|
||||
/>
|
||||
{user.is_active ? "启用" : "禁用"}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleToggleActive(user)}
|
||||
className="rounded-lg px-2.5 py-1 text-[11px] font-medium transition-colors hover:bg-fg/5"
|
||||
style={{
|
||||
color: user.is_active
|
||||
? "var(--cinnabar)"
|
||||
: "var(--patina)",
|
||||
}}
|
||||
>
|
||||
{user.is_active ? "禁用" : "启用"}
|
||||
</button>
|
||||
{!user.is_admin && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleToggleAdmin(user)}
|
||||
className="rounded-lg px-2.5 py-1 text-[11px] font-medium transition-colors hover:bg-fg/5"
|
||||
style={{ color: "var(--patina)" }}
|
||||
>
|
||||
设为管理员
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateUserForm({
|
||||
onDone,
|
||||
onCancel,
|
||||
}: {
|
||||
onDone: () => void;
|
||||
onCancel: () => void;
|
||||
}) {
|
||||
const [username, setUsername] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
const body: AdminCreateUserBody = {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
is_admin: isAdmin,
|
||||
};
|
||||
await createUser(body);
|
||||
onDone();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "创建失败");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="glass-strong mb-6 rounded-xl border border-border-glass p-4">
|
||||
<h2 className="mb-4 text-sm font-medium text-fg">创建新用户</h2>
|
||||
<form onSubmit={handleSubmit} className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">
|
||||
用户名
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">邮箱</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-muted">密码</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="mt-1 block w-full rounded-lg border border-border-glass bg-glass px-3 py-2 text-sm text-fg outline-none transition-colors focus:border-patina"
|
||||
required
|
||||
minLength={6}
|
||||
/>
|
||||
</div>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isAdmin}
|
||||
onChange={(e) => setIsAdmin(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-border-glass accent-patina"
|
||||
/>
|
||||
<span className="text-xs text-muted">设为管理员</span>
|
||||
</label>
|
||||
{error && <p className="text-xs text-cinnabar">{error}</p>}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="rounded-lg px-3 py-1.5 text-xs font-medium text-muted transition-colors hover:bg-fg/5"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={saving}
|
||||
className="rounded-lg bg-patina px-3 py-1.5 text-xs font-medium text-white transition-opacity disabled:opacity-50"
|
||||
>
|
||||
{saving ? "创建中…" : "创建"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { WorldEntityList } from "@/components/admin/WorldEntityList";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { WorldEntity } from "@/types/api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AdminWorldPage() {
|
||||
let entities: WorldEntity[] = [];
|
||||
let error: string | null = null;
|
||||
try {
|
||||
entities = await serverApi.get("/admin/world/entities");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="河图枢"
|
||||
description="世界实体管理——人物对世界的认知实体,支持增删改。"
|
||||
maxWidth="max-w-5xl"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<WorldEntityList entities={entities ?? []} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* BFF 认证 API Route — POST /api/auth/login
|
||||
*
|
||||
* 路由层:解析请求 → 调 authService.login → 返回响应(不含业务逻辑)。
|
||||
*/
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
import {
|
||||
login as loginService,
|
||||
AuthServiceError,
|
||||
} from "@/lib/backend/services/auth";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { username, password } = body;
|
||||
|
||||
if (!username || !password) {
|
||||
return NextResponse.json(
|
||||
{ error: { code: "invalid_request", message: "请提供用户名和密码。" } },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const result = await loginService(username, password);
|
||||
return NextResponse.json(result);
|
||||
} catch (err) {
|
||||
if (err instanceof AuthServiceError) {
|
||||
return NextResponse.json(
|
||||
{ error: { code: err.code, message: err.message } },
|
||||
{ status: err.status },
|
||||
);
|
||||
}
|
||||
console.error("[BFF /api/auth/login]", err);
|
||||
return NextResponse.json(
|
||||
{ error: { code: "internal_error", message: "服务内部错误。" } },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* BFF 认证 API Route — POST /api/auth/logout
|
||||
*
|
||||
* 路由层:调 authService.logout → 返回响应。
|
||||
*/
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { logout } from "@/lib/backend/services/auth";
|
||||
|
||||
export async function POST() {
|
||||
await logout();
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* BFF 认证 API Route — GET /api/auth/me
|
||||
*
|
||||
* 路由层:读 token → 调 authService.getMe → 401 时 refresh 重试 → 返回响应。
|
||||
*/
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { getAccessToken } from "@/lib/backend/cookies";
|
||||
import { getMe, refreshToken, logout } from "@/lib/backend/services/auth";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const token = await getAccessToken();
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{ error: { code: "unauthorized", message: "未登录。" } },
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
let user = await getMe();
|
||||
if (!user) {
|
||||
// 401 或 token 无效 → 尝试 refresh
|
||||
const newToken = await refreshToken();
|
||||
if (newToken) {
|
||||
user = await getMe();
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
await logout();
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: { code: "unauthorized", message: "会话已过期,请重新登录。" },
|
||||
},
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
const accessToken = await getAccessToken();
|
||||
return NextResponse.json({
|
||||
user,
|
||||
access_token: accessToken ?? "",
|
||||
refresh_token: "",
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[BFF /api/auth/me]", err);
|
||||
return NextResponse.json(
|
||||
{ error: { code: "network_error", message: "无法连接后端服务。" } },
|
||||
{ status: 502 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 963 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user