mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor(docs/home): 重构首页导航和文档结构
- README 新增架构图(明暗主题 SVG),更新 Proxy 描述 - 首页导航栏将"文档"链接从底部按钮区移至顶部导航 - 删除独立的 body-rules-spec 文档 - GuideLayout 路由切换时自动滚动到顶部 - 架构图组件移除网格背景
This commit is contained in:
10
README.md
10
README.md
@@ -22,6 +22,14 @@
|
|||||||
|
|
||||||
Aether 是一个自托管的 AI API 网关,为团队和个人提供多租户管理、智能负载均衡、成本配额控制和健康监控能力。通过统一的 API 入口,可以无缝对接 Claude、OpenAI、Gemini 等主流 AI 服务及其 CLI 工具。
|
Aether 是一个自托管的 AI API 网关,为团队和个人提供多租户管理、智能负载均衡、成本配额控制和健康监控能力。通过统一的 API 入口,可以无缝对接 Claude、OpenAI、Gemini 等主流 AI 服务及其 CLI 工具。
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="docs/architecture/architecture-dark.svg">
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="docs/architecture/architecture-light.svg">
|
||||||
|
<img src="docs/architecture/architecture-light.svg" width="680" alt="Aether Architecture">
|
||||||
|
</picture>
|
||||||
|
</p>
|
||||||
|
|
||||||
页面预览: https://fawney19.github.io/Aether/
|
页面预览: https://fawney19.github.io/Aether/
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
@@ -75,7 +83,7 @@ cd frontend && npm install && npm run dev
|
|||||||
|
|
||||||
## Aether Proxy
|
## Aether Proxy
|
||||||
|
|
||||||
Aether Proxy 是配套的正向代理节点,部署在海外 VPS 上,为墙内的 Aether 实例中转 API 流量。支持 TUI 向导一键配置、systemd 服务管理、TLS 加密、DNS 缓存及连接池调优。
|
Aether Proxy 是配套的正向代理节点,部署在海外 VPS 上,为墙内的 Aether 实例中转 API 流量。或者部署在其他服务器为指定的提供商、账号、Key使用不同的节点访问。支持 TUI 向导一键配置、systemd 服务管理、TLS 加密、DNS 缓存及连接池调优。
|
||||||
|
|
||||||
- Docker Compose 部署或下载预编译二进制直接运行
|
- Docker Compose 部署或下载预编译二进制直接运行
|
||||||
- 通过 `aether-proxy setup` 完成交互式配置,自动注册为系统服务
|
- 通过 `aether-proxy setup` 完成交互式配置,自动注册为系统服务
|
||||||
|
|||||||
117
docs/architecture/architecture-dark.svg
Normal file
117
docs/architecture/architecture-dark.svg
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 420" width="760" height="420" font-family="'Inter', system-ui, -apple-system, sans-serif">
|
||||||
|
<defs>
|
||||||
|
<filter id="shadow-sm" x="-10%" y="-10%" width="120%" height="130%">
|
||||||
|
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-opacity="0.5"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="shadow-lg" x="-10%" y="-10%" width="120%" height="130%">
|
||||||
|
<feDropShadow dx="0" dy="8" stdDeviation="12" flood-opacity="0.7"/>
|
||||||
|
</filter>
|
||||||
|
<clipPath id="gateway-clip">
|
||||||
|
<path d="M 0 0 h 760 v 420 H 0 Z M 74 169 v 54 h 12 v -54 Z" clip-rule="evenodd"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Background -->
|
||||||
|
<rect width="760" height="420" rx="24" fill="#121212"/>
|
||||||
|
|
||||||
|
<!-- === Labels placeholder, drawn at top layer === -->
|
||||||
|
|
||||||
|
<!-- Source pills -->
|
||||||
|
<rect x="138" y="24" width="100" height="36" rx="18" fill="#1a1a1c" stroke="#2a2a2c" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="163" cy="42" r="3" fill="#cc7154"/>
|
||||||
|
<text x="195" y="46" font-size="11" font-weight="700" fill="#f3f4f6" text-anchor="middle">Claude</text>
|
||||||
|
<rect x="330" y="24" width="100" height="36" rx="18" fill="#1a1a1c" stroke="#2a2a2c" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="355" cy="42" r="3" fill="#cc7154"/>
|
||||||
|
<text x="387" y="46" font-size="11" font-weight="700" fill="#f3f4f6" text-anchor="middle">OpenAI</text>
|
||||||
|
<rect x="522" y="24" width="100" height="36" rx="18" fill="#1a1a1c" stroke="#2a2a2c" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="547" cy="42" r="3" fill="#cc7154"/>
|
||||||
|
<text x="579" y="46" font-size="11" font-weight="700" fill="#f3f4f6" text-anchor="middle">Gemini</text>
|
||||||
|
|
||||||
|
<!-- Output pills -->
|
||||||
|
<rect x="128" y="360" width="120" height="36" rx="18" fill="#1a1a1c" stroke="#2a2a2c" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="153" cy="378" r="3" fill="#cc7154"/>
|
||||||
|
<text x="195" y="382" font-size="10" font-weight="700" fill="#f3f4f6" text-anchor="middle">Claude 响应</text>
|
||||||
|
<rect x="320" y="360" width="120" height="36" rx="18" fill="#1a1a1c" stroke="#2a2a2c" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="345" cy="378" r="3" fill="#cc7154"/>
|
||||||
|
<text x="387" y="382" font-size="10" font-weight="700" fill="#f3f4f6" text-anchor="middle">OpenAI 响应</text>
|
||||||
|
<rect x="512" y="360" width="120" height="36" rx="18" fill="#1a1a1c" stroke="#2a2a2c" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="537" cy="378" r="3" fill="#cc7154"/>
|
||||||
|
<text x="579" y="382" font-size="10" font-weight="700" fill="#f3f4f6" text-anchor="middle">Gemini 响应</text>
|
||||||
|
|
||||||
|
<!-- === Layer 2: AETHER GATEWAY box === -->
|
||||||
|
<rect x="80" y="78" width="600" height="240" rx="24" fill="#1a1a1c" filter="url(#shadow-lg)"/>
|
||||||
|
<rect x="80" y="78" width="600" height="240" rx="24" fill="none" stroke="rgba(204,113,84,0.4)" clip-path="url(#gateway-clip)"/>
|
||||||
|
<g transform="translate(80, 176)" font-size="7" font-weight="900" fill="#cc7154" text-anchor="middle">
|
||||||
|
<text x="0" y="0">A</text><text x="0" y="9">E</text><text x="0" y="18">T</text>
|
||||||
|
<text x="0" y="27">H</text><text x="0" y="36">E</text><text x="0" y="45">R</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Dashed box -->
|
||||||
|
<rect x="180" y="98" width="400" height="112" rx="16" fill="none" stroke="rgba(204,113,84,0.4)" stroke-width="1.5" stroke-dasharray="6 4"/>
|
||||||
|
<rect x="200" y="108" width="360" height="26" rx="12" fill="none" stroke="#2a2a2c"/>
|
||||||
|
<text x="380" y="125" font-size="10" font-weight="600" fill="#f3f4f6" text-anchor="middle">统一模型规范 / 协议聚合</text>
|
||||||
|
<rect x="200" y="140" width="85" height="26" rx="13" fill="rgba(204,113,84,0.12)"/>
|
||||||
|
<text x="242" y="157" font-size="10" font-weight="600" fill="#f3f4f6" text-anchor="middle">多端鉴权</text>
|
||||||
|
<rect x="290" y="140" width="85" height="26" rx="13" fill="rgba(204,113,84,0.12)"/>
|
||||||
|
<text x="332" y="157" font-size="10" font-weight="600" fill="#f3f4f6" text-anchor="middle">配额管控</text>
|
||||||
|
<rect x="380" y="140" width="85" height="26" rx="13" fill="rgba(204,113,84,0.12)"/>
|
||||||
|
<text x="422" y="157" font-size="10" font-weight="600" fill="#f3f4f6" text-anchor="middle">全局并发</text>
|
||||||
|
<rect x="470" y="140" width="85" height="26" rx="13" fill="rgba(204,113,84,0.12)"/>
|
||||||
|
<text x="512" y="157" font-size="10" font-weight="600" fill="#f3f4f6" text-anchor="middle">缓存亲和</text>
|
||||||
|
<rect x="200" y="172" width="360" height="28" rx="14" fill="none" stroke="#2a2a2c"/>
|
||||||
|
<text x="380" y="190" font-size="10" font-weight="600" fill="#f3f4f6" text-anchor="middle">智能调度 / 故障转移</text>
|
||||||
|
|
||||||
|
<!-- Engine cards -->
|
||||||
|
<rect x="103" y="250" width="170" height="28" rx="14" fill="rgba(204,113,84,0.12)" stroke="#2a2a2c"/>
|
||||||
|
<circle cx="163" cy="264" r="3" fill="#cc7154"/>
|
||||||
|
<text x="192" y="268" font-size="10" font-weight="700" fill="#f3f4f6" text-anchor="middle">格式转换</text>
|
||||||
|
<rect x="295" y="250" width="170" height="28" rx="14" fill="rgba(204,113,84,0.12)" stroke="#2a2a2c"/>
|
||||||
|
<circle cx="355" cy="264" r="3" fill="#cc7154"/>
|
||||||
|
<text x="384" y="268" font-size="10" font-weight="700" fill="#f3f4f6" text-anchor="middle">反向代理</text>
|
||||||
|
<rect x="487" y="250" width="170" height="28" rx="14" fill="rgba(204,113,84,0.12)" stroke="#2a2a2c"/>
|
||||||
|
<circle cx="547" cy="264" r="3" fill="#cc7154"/>
|
||||||
|
<text x="576" y="268" font-size="10" font-weight="700" fill="#f3f4f6" text-anchor="middle">原生直通</text>
|
||||||
|
|
||||||
|
<!-- === Layer 3: Track lines === -->
|
||||||
|
<g stroke="rgba(255,255,255,0.08)" stroke-width="1.5" fill="none" stroke-linecap="round">
|
||||||
|
<path id="f-in-1" d="M 188 60 L 188 66 C 188 72, 380 72, 380 78 L 380 98"/>
|
||||||
|
<path id="f-in-2" d="M 380 60 L 380 98"/>
|
||||||
|
<path id="f-in-3" d="M 572 60 L 572 66 C 572 72, 380 72, 380 78 L 380 98"/>
|
||||||
|
<path id="f-sL" d="M 380 210 C 380 230, 188 230, 188 250"/>
|
||||||
|
<path id="f-sM" d="M 380 210 L 380 250"/>
|
||||||
|
<path id="f-sR" d="M 380 210 C 380 230, 572 230, 572 250"/>
|
||||||
|
<path id="f-oL" d="M 188 278 L 188 284 C 188 302, 380 302, 380 318"/>
|
||||||
|
<path id="f-oM" d="M 380 278 L 380 318"/>
|
||||||
|
<path id="f-oR" d="M 572 278 L 572 284 C 572 302, 380 302, 380 318"/>
|
||||||
|
<path id="f-oL2" d="M 380 318 C 380 338, 188 338, 188 360"/>
|
||||||
|
<path id="f-oM2" d="M 380 318 L 380 360"/>
|
||||||
|
<path id="f-oR2" d="M 380 318 C 380 338, 572 338, 572 360"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Animated dots -->
|
||||||
|
<g>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="2.2s" repeatCount="indefinite"><mpath href="#f-in-1"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="2.2s" repeatCount="indefinite"><mpath href="#f-in-3"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.4s" repeatCount="indefinite"><mpath href="#f-sL"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.4s" repeatCount="indefinite"><mpath href="#f-sR"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.4s" repeatCount="indefinite"><mpath href="#f-oL"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.4s" repeatCount="indefinite"><mpath href="#f-oR"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.2s" repeatCount="indefinite"><mpath href="#f-oL2"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.2s" repeatCount="indefinite"><mpath href="#f-oR2"/></animateMotion></circle>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- === Layer 4: Junction dots === -->
|
||||||
|
<circle cx="380" cy="78" r="4" fill="#cc7154" stroke="#1e1e1e"/>
|
||||||
|
<circle cx="380" cy="210" r="4" fill="#cc7154" stroke="#1e1e1e"/>
|
||||||
|
<circle cx="380" cy="318" r="4" fill="#cc7154" stroke="#1e1e1e"/>
|
||||||
|
|
||||||
|
<!-- === Layer 5: SOURCES / OUTPUTS labels === -->
|
||||||
|
<g>
|
||||||
|
<circle cx="355" cy="12" r="2" fill="#6b7280"/>
|
||||||
|
<text x="365" y="15" font-size="9" font-weight="800" letter-spacing="0.2em" fill="#6b7280">SOURCES</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="353" cy="346" r="2" fill="#6b7280"/>
|
||||||
|
<text x="363" y="349" font-size="9" font-weight="800" letter-spacing="0.2em" fill="#6b7280">OUTPUTS</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.7 KiB |
118
docs/architecture/architecture-light.svg
Normal file
118
docs/architecture/architecture-light.svg
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 420" width="760" height="420" font-family="'Inter', system-ui, -apple-system, sans-serif">
|
||||||
|
<defs>
|
||||||
|
<filter id="shadow-sm" x="-10%" y="-10%" width="120%" height="130%">
|
||||||
|
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-opacity="0.04"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="shadow-lg" x="-10%" y="-10%" width="120%" height="130%">
|
||||||
|
<feDropShadow dx="0" dy="6" stdDeviation="10" flood-opacity="0.06"/>
|
||||||
|
</filter>
|
||||||
|
<!-- Clip path: full area minus AETHER label zone -->
|
||||||
|
<clipPath id="gateway-clip">
|
||||||
|
<path d="M 0 0 h 760 v 420 H 0 Z M 74 169 v 54 h 12 v -54 Z" clip-rule="evenodd"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Background -->
|
||||||
|
<rect width="760" height="420" rx="24" fill="#fcfcfc"/>
|
||||||
|
|
||||||
|
<!-- === Labels placeholder, drawn at top layer === -->
|
||||||
|
|
||||||
|
<!-- Source pills -->
|
||||||
|
<rect x="138" y="24" width="100" height="36" rx="18" fill="#ffffff" stroke="#e8e8ec" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="163" cy="42" r="3" fill="#cc7154"/>
|
||||||
|
<text x="195" y="46" font-size="11" font-weight="700" fill="#1f2937" text-anchor="middle">Claude</text>
|
||||||
|
<rect x="330" y="24" width="100" height="36" rx="18" fill="#ffffff" stroke="#e8e8ec" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="355" cy="42" r="3" fill="#cc7154"/>
|
||||||
|
<text x="387" y="46" font-size="11" font-weight="700" fill="#1f2937" text-anchor="middle">OpenAI</text>
|
||||||
|
<rect x="522" y="24" width="100" height="36" rx="18" fill="#ffffff" stroke="#e8e8ec" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="547" cy="42" r="3" fill="#cc7154"/>
|
||||||
|
<text x="579" y="46" font-size="11" font-weight="700" fill="#1f2937" text-anchor="middle">Gemini</text>
|
||||||
|
|
||||||
|
<!-- Output pills -->
|
||||||
|
<rect x="128" y="360" width="120" height="36" rx="18" fill="#ffffff" stroke="#e8e8ec" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="153" cy="378" r="3" fill="#cc7154"/>
|
||||||
|
<text x="195" y="382" font-size="10" font-weight="700" fill="#1f2937" text-anchor="middle">Claude 响应</text>
|
||||||
|
<rect x="320" y="360" width="120" height="36" rx="18" fill="#ffffff" stroke="#e8e8ec" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="345" cy="378" r="3" fill="#cc7154"/>
|
||||||
|
<text x="387" y="382" font-size="10" font-weight="700" fill="#1f2937" text-anchor="middle">OpenAI 响应</text>
|
||||||
|
<rect x="512" y="360" width="120" height="36" rx="18" fill="#ffffff" stroke="#e8e8ec" filter="url(#shadow-sm)"/>
|
||||||
|
<circle cx="537" cy="378" r="3" fill="#cc7154"/>
|
||||||
|
<text x="579" y="382" font-size="10" font-weight="700" fill="#1f2937" text-anchor="middle">Gemini 响应</text>
|
||||||
|
|
||||||
|
<!-- === Layer 2: AETHER GATEWAY box (z-20) === -->
|
||||||
|
<rect x="80" y="78" width="600" height="240" rx="24" fill="#ffffff" filter="url(#shadow-lg)"/>
|
||||||
|
<rect x="80" y="78" width="600" height="240" rx="24" fill="none" stroke="rgba(204,113,84,0.25)" clip-path="url(#gateway-clip)"/>
|
||||||
|
<g transform="translate(80, 176)" font-size="7" font-weight="900" fill="#cc7154" text-anchor="middle">
|
||||||
|
<text x="0" y="0">A</text><text x="0" y="9">E</text><text x="0" y="18">T</text>
|
||||||
|
<text x="0" y="27">H</text><text x="0" y="36">E</text><text x="0" y="45">R</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Dashed box -->
|
||||||
|
<rect x="180" y="98" width="400" height="112" rx="16" fill="none" stroke="rgba(204,113,84,0.25)" stroke-width="1.5" stroke-dasharray="6 4"/>
|
||||||
|
<rect x="200" y="108" width="360" height="26" rx="12" fill="none" stroke="#e8e8ec"/>
|
||||||
|
<text x="380" y="125" font-size="10" font-weight="600" fill="#1f2937" text-anchor="middle">统一模型规范 / 协议聚合</text>
|
||||||
|
<rect x="200" y="140" width="85" height="26" rx="13" fill="rgba(204,113,84,0.06)"/>
|
||||||
|
<text x="242" y="157" font-size="10" font-weight="600" fill="#1f2937" text-anchor="middle">多端鉴权</text>
|
||||||
|
<rect x="290" y="140" width="85" height="26" rx="13" fill="rgba(204,113,84,0.06)"/>
|
||||||
|
<text x="332" y="157" font-size="10" font-weight="600" fill="#1f2937" text-anchor="middle">配额管控</text>
|
||||||
|
<rect x="380" y="140" width="85" height="26" rx="13" fill="rgba(204,113,84,0.06)"/>
|
||||||
|
<text x="422" y="157" font-size="10" font-weight="600" fill="#1f2937" text-anchor="middle">全局并发</text>
|
||||||
|
<rect x="470" y="140" width="85" height="26" rx="13" fill="rgba(204,113,84,0.06)"/>
|
||||||
|
<text x="512" y="157" font-size="10" font-weight="600" fill="#1f2937" text-anchor="middle">缓存亲和</text>
|
||||||
|
<rect x="200" y="172" width="360" height="28" rx="14" fill="none" stroke="#e8e8ec"/>
|
||||||
|
<text x="380" y="190" font-size="10" font-weight="600" fill="#1f2937" text-anchor="middle">智能调度 / 故障转移</text>
|
||||||
|
|
||||||
|
<!-- Engine cards -->
|
||||||
|
<rect x="103" y="250" width="170" height="28" rx="14" fill="rgba(204,113,84,0.06)" stroke="#e8e8ec"/>
|
||||||
|
<circle cx="163" cy="264" r="3" fill="#cc7154"/>
|
||||||
|
<text x="192" y="268" font-size="10" font-weight="700" fill="#1f2937" text-anchor="middle">格式转换</text>
|
||||||
|
<rect x="295" y="250" width="170" height="28" rx="14" fill="rgba(204,113,84,0.06)" stroke="#e8e8ec"/>
|
||||||
|
<circle cx="355" cy="264" r="3" fill="#cc7154"/>
|
||||||
|
<text x="384" y="268" font-size="10" font-weight="700" fill="#1f2937" text-anchor="middle">反向代理</text>
|
||||||
|
<rect x="487" y="250" width="170" height="28" rx="14" fill="rgba(204,113,84,0.06)" stroke="#e8e8ec"/>
|
||||||
|
<circle cx="547" cy="264" r="3" fill="#cc7154"/>
|
||||||
|
<text x="576" y="268" font-size="10" font-weight="700" fill="#1f2937" text-anchor="middle">原生直通</text>
|
||||||
|
|
||||||
|
<!-- === Layer 3: Track lines (z-30, above Gateway fill) === -->
|
||||||
|
<g stroke="#e5e7eb" stroke-width="1.5" fill="none" stroke-linecap="round">
|
||||||
|
<path id="f-in-1" d="M 188 60 L 188 66 C 188 72, 380 72, 380 78 L 380 98"/>
|
||||||
|
<path id="f-in-2" d="M 380 60 L 380 98"/>
|
||||||
|
<path id="f-in-3" d="M 572 60 L 572 66 C 572 72, 380 72, 380 78 L 380 98"/>
|
||||||
|
<path id="f-sL" d="M 380 210 C 380 230, 188 230, 188 250"/>
|
||||||
|
<path id="f-sM" d="M 380 210 L 380 250"/>
|
||||||
|
<path id="f-sR" d="M 380 210 C 380 230, 572 230, 572 250"/>
|
||||||
|
<path id="f-oL" d="M 188 278 L 188 284 C 188 302, 380 302, 380 318"/>
|
||||||
|
<path id="f-oM" d="M 380 278 L 380 318"/>
|
||||||
|
<path id="f-oR" d="M 572 278 L 572 284 C 572 302, 380 302, 380 318"/>
|
||||||
|
<path id="f-oL2" d="M 380 318 C 380 338, 188 338, 188 360"/>
|
||||||
|
<path id="f-oM2" d="M 380 318 L 380 360"/>
|
||||||
|
<path id="f-oR2" d="M 380 318 C 380 338, 572 338, 572 360"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Animated dots -->
|
||||||
|
<g>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="2.2s" repeatCount="indefinite"><mpath href="#f-in-1"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="2.2s" repeatCount="indefinite"><mpath href="#f-in-3"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.4s" repeatCount="indefinite"><mpath href="#f-sL"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.4s" repeatCount="indefinite"><mpath href="#f-sR"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.4s" repeatCount="indefinite"><mpath href="#f-oL"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.4s" repeatCount="indefinite"><mpath href="#f-oR"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.2s" repeatCount="indefinite"><mpath href="#f-oL2"/></animateMotion></circle>
|
||||||
|
<circle r="2.5" fill="#cc7154"><animateMotion dur="1.2s" repeatCount="indefinite"><mpath href="#f-oR2"/></animateMotion></circle>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- === Layer 4: Junction dots (z-40, topmost) === -->
|
||||||
|
<circle cx="380" cy="78" r="4" fill="#cc7154" stroke="#ffffff"/>
|
||||||
|
<circle cx="380" cy="210" r="4" fill="#cc7154" stroke="#ffffff"/>
|
||||||
|
<circle cx="380" cy="318" r="4" fill="#cc7154" stroke="#ffffff"/>
|
||||||
|
|
||||||
|
<!-- === Layer 5: SOURCES / OUTPUTS labels (topmost) === -->
|
||||||
|
<g>
|
||||||
|
<circle cx="355" cy="12" r="2" fill="#8c94a1"/>
|
||||||
|
<text x="365" y="15" font-size="9" font-weight="800" letter-spacing="0.2em" fill="#8c94a1">SOURCES</text>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<circle cx="353" cy="346" r="2" fill="#8c94a1"/>
|
||||||
|
<text x="363" y="349" font-size="9" font-weight="800" letter-spacing="0.2em" fill="#8c94a1">OUTPUTS</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.8 KiB |
@@ -1,262 +0,0 @@
|
|||||||
# Aether 请求体规则 (Body Rules) — AI 辅助指南
|
|
||||||
|
|
||||||
> 本文档面向 AI 助手。用户在「端点管理 → 请求规则 → + 请求体」中添加规则,AI 需要指导用户在 UI 表单的各个输入框中填写什么内容。
|
|
||||||
|
|
||||||
## 系统背景
|
|
||||||
|
|
||||||
Aether 是一个 AI API 网关,将用户请求转发给上游 Provider(OpenAI、Claude、Gemini 等)。请求体规则在转发前修改请求体 JSON,每个 Endpoint 可配置多条规则。
|
|
||||||
|
|
||||||
## 用户请求体长什么样
|
|
||||||
|
|
||||||
规则操作的对象是用户发给 Aether 的 JSON 请求体。根据 Endpoint 的 API 格式不同,结构也不同:
|
|
||||||
|
|
||||||
### OpenAI Chat 格式(`openai:chat`)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"model": "gpt-4",
|
|
||||||
"messages": [
|
|
||||||
{"role": "system", "content": "你是一个助手"},
|
|
||||||
{"role": "user", "content": "你好"}
|
|
||||||
],
|
|
||||||
"temperature": 0.7,
|
|
||||||
"max_tokens": 1000,
|
|
||||||
"stream": true,
|
|
||||||
"top_p": 1,
|
|
||||||
"frequency_penalty": 0,
|
|
||||||
"presence_penalty": 0,
|
|
||||||
"stop": ["\n"],
|
|
||||||
"tools": [...],
|
|
||||||
"tool_choice": "auto",
|
|
||||||
"response_format": {"type": "json_object"},
|
|
||||||
"metadata": {...}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Claude Chat 格式(`claude:chat`)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"model": "claude-sonnet-4-20250514",
|
|
||||||
"messages": [
|
|
||||||
{"role": "user", "content": "你好"}
|
|
||||||
],
|
|
||||||
"system": "你是一个助手",
|
|
||||||
"max_tokens": 1024,
|
|
||||||
"temperature": 0.7,
|
|
||||||
"stream": true,
|
|
||||||
"top_p": 1,
|
|
||||||
"top_k": 40,
|
|
||||||
"stop_sequences": ["###"],
|
|
||||||
"metadata": {"user_id": "xxx"}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Claude CLI 格式(`claude:cli`)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"model": "claude-sonnet-4-20250514",
|
|
||||||
"messages": [
|
|
||||||
{"role": "user", "content": "帮我重构这段代码"}
|
|
||||||
],
|
|
||||||
"system": "你是一个编程助手",
|
|
||||||
"max_tokens": 16384,
|
|
||||||
"stream": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gemini Chat 格式(`gemini:chat`)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"contents": [
|
|
||||||
{"role": "user", "parts": [{"text": "你好"}]}
|
|
||||||
],
|
|
||||||
"generationConfig": {
|
|
||||||
"temperature": 0.7,
|
|
||||||
"maxOutputTokens": 1024,
|
|
||||||
"topP": 0.9,
|
|
||||||
"topK": 40
|
|
||||||
},
|
|
||||||
"systemInstruction": {"parts": [{"text": "你是一个助手"}]},
|
|
||||||
"safetySettings": [...]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **注意:** `model` 和 `stream` 是受保护字段,任何规则都无法修改它们。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UI 表单说明
|
|
||||||
|
|
||||||
用户在端点管理对话框中点击「+ 请求体」添加规则。每条规则有一个下拉框选择操作类型,然后根据类型显示不同的输入框。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 覆写(set)
|
|
||||||
|
|
||||||
**UI 布局:** `[覆写 ▼]` `[字段路径]` `=` `[值]` `[✓]`
|
|
||||||
|
|
||||||
| 输入框 | 填什么 | 示例 |
|
|
||||||
|--------|--------|------|
|
|
||||||
| 字段路径 | 要设置的字段,用 `.` 分隔层级,`[N]` 访问数组 | `temperature` |
|
|
||||||
| 值 | **JSON 格式**的值。字符串要加引号,数字直接写 | `0.7` |
|
|
||||||
|
|
||||||
值输入框右边有验证图标:绿色勾=JSON合法,红色叉=格式错误。
|
|
||||||
|
|
||||||
**用户常见需求 → 怎么填:**
|
|
||||||
|
|
||||||
| 用户说 | 字段路径 | 值 |
|
|
||||||
|--------|---------|-----|
|
|
||||||
| "固定 temperature 为 0.3" | `temperature` | `0.3` |
|
|
||||||
| "限制最大输出 500 token" | `max_tokens` | `500` |
|
|
||||||
| "加一个 metadata 字段标记来源" | `metadata.source` | `"my-app"` |
|
|
||||||
| "设置 top_p 为 0.9" | `top_p` | `0.9` |
|
|
||||||
| "添加停止序列" | `stop` | `["\n", "###"]` |
|
|
||||||
| "设置响应格式为 JSON" | `response_format` | `{"type": "json_object"}` |
|
|
||||||
| "把第一条消息内容改掉" | `messages[0].content` | `"新的内容"` |
|
|
||||||
| "设置一个嵌套对象" | `metadata.tracking` | `{"id": "abc", "env": "prod"}` |
|
|
||||||
| "设置值为空" | `some_field` | `null` |
|
|
||||||
|
|
||||||
> **注意:** 字符串值必须加引号!`"hello"` 是字符串,`hello` 是无效 JSON。数字、布尔、null、数组、对象不需要额外引号。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 删除(drop)
|
|
||||||
|
|
||||||
**UI 布局:** `[删除 ▼]` `[要删除的字段路径]`
|
|
||||||
|
|
||||||
| 输入框 | 填什么 | 示例 |
|
|
||||||
|--------|--------|------|
|
|
||||||
| 字段路径 | 要删除的字段路径 | `user_info.ip_address` |
|
|
||||||
|
|
||||||
**用户常见需求 → 怎么填:**
|
|
||||||
|
|
||||||
| 用户说 | 字段路径 |
|
|
||||||
|--------|---------|
|
|
||||||
| "去掉 user 字段" | `user` |
|
|
||||||
| "删除 metadata 里的 internal_flag" | `metadata.internal_flag` |
|
|
||||||
| "移除第一条消息" | `messages[0]` |
|
|
||||||
| "去掉 frequency_penalty" | `frequency_penalty` |
|
|
||||||
|
|
||||||
> **注意:** 删除数组元素会导致后续索引前移。如需删除多个数组元素,从后往前删。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 重命名(rename)
|
|
||||||
|
|
||||||
**UI 布局:** `[重命名 ▼]` `[原路径]` `→` `[新路径]`
|
|
||||||
|
|
||||||
| 输入框 | 填什么 | 示例 |
|
|
||||||
|--------|--------|------|
|
|
||||||
| 原路径 | 源字段的路径 | `extra.trace_id` |
|
|
||||||
| 新路径 | 目标字段的路径(中间层级会自动创建) | `metadata.request_id` |
|
|
||||||
|
|
||||||
**用户常见需求 → 怎么填:**
|
|
||||||
|
|
||||||
| 用户说 | 原路径 | 新路径 |
|
|
||||||
|--------|--------|--------|
|
|
||||||
| "把 max_tokens 改名为 max_completion_tokens" | `max_tokens` | `max_completion_tokens` |
|
|
||||||
| "把 extra 里的 id 移到 metadata 下" | `extra.id` | `metadata.id` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 插入(insert)
|
|
||||||
|
|
||||||
**UI 布局:** `[插入 ▼]` `[数组路径]` `[位置]` `[值(JSON)]` `[✓]`
|
|
||||||
|
|
||||||
| 输入框 | 填什么 | 示例 |
|
|
||||||
|--------|--------|------|
|
|
||||||
| 数组路径 | 目标数组的路径(必须是已有的数组) | `messages` |
|
|
||||||
| 位置 | 插入位置的数字索引,**留空=追加到末尾** | `0`(开头)或留空(末尾) |
|
|
||||||
| 值 | JSON 格式的元素 | `{"role": "system", "content": "..."}` |
|
|
||||||
|
|
||||||
**用户常见需求 → 怎么填:**
|
|
||||||
|
|
||||||
| 用户说 | 数组路径 | 位置 | 值 |
|
|
||||||
|--------|---------|------|-----|
|
|
||||||
| "在开头加一条 system 消息" | `messages` | `0` | `{"role": "system", "content": "你是一个专业助手"}` |
|
|
||||||
| "在末尾追加一条消息" | `messages` | (留空) | `{"role": "user", "content": "请用中文回答"}` |
|
|
||||||
| "在第二条消息前插入" | `messages` | `1` | `{"role": "assistant", "content": "好的"}` |
|
|
||||||
|
|
||||||
> **位置说明:** `0`=最前面,`1`=第二个位置,`-1`=倒数第一个前面。留空=追加到最后。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 正则替换(regex_replace)
|
|
||||||
|
|
||||||
**UI 布局:** `[正则替换 ▼]` `[字段路径]` `[正则]` `→` `[替换为]` `[ims]` `[✓]`
|
|
||||||
|
|
||||||
| 输入框 | 填什么 | 示例 |
|
|
||||||
|--------|--------|------|
|
|
||||||
| 字段路径 | 目标字符串字段的路径 | `messages[-1].content` |
|
|
||||||
| 正则 | 正则表达式(**不需要**加 `/` 包裹) | `1[3-9]\d{9}` |
|
|
||||||
| 替换为 | 替换成什么,留空=删除匹配内容 | `[手机号已隐藏]` |
|
|
||||||
| ims | 可选标志,留空=默认 | `i` |
|
|
||||||
|
|
||||||
> **重要:** 正则输入框里直接写正则语法即可,**不需要** JSON 转义。`\d` 就写 `\d`,不用写 `\\d`。JSON 转义是保存时系统自动处理的。
|
|
||||||
|
|
||||||
**flags 含义:**
|
|
||||||
|
|
||||||
| 字母 | 作用 | 什么时候用 |
|
|
||||||
|------|------|-----------|
|
|
||||||
| `i` | 忽略大小写 | 匹配 `hello`/`Hello`/`HELLO` |
|
|
||||||
| `m` | 多行模式 | `^`/`$` 匹配每行而非整个字符串 |
|
|
||||||
| `s` | dotall | `.` 能匹配换行符 |
|
|
||||||
|
|
||||||
大多数情况留空即可。
|
|
||||||
|
|
||||||
**用户常见需求 → 怎么填:**
|
|
||||||
|
|
||||||
| 用户说 | 字段路径 | 正则 | 替换为 | flags |
|
|
||||||
|--------|---------|------|--------|-------|
|
|
||||||
| "隐藏最后一条消息里的手机号" | `messages[-1].content` | `1[3-9]\d{9}` | `[手机号已隐藏]` | |
|
|
||||||
| "隐藏邮箱地址" | `messages[-1].content` | `[\w.+-]+@[\w.-]+\.\w{2,}` | `[邮箱已隐藏]` | `i` |
|
|
||||||
| "去掉 HTML 标签" | `messages[-1].content` | `<[^>]+>` | (留空) | |
|
|
||||||
| "把所有的 foo 替换成 bar" | `messages[-1].content` | `\bfoo\b` | `bar` | |
|
|
||||||
| "删掉 Markdown 加粗标记" | `messages[-1].content` | `\*\*([^*]+)\*\*` | `\1` | |
|
|
||||||
| "隐藏身份证号" | `messages[-1].content` | `\d{17}[\dXx]` | `[身份证已隐藏]` | |
|
|
||||||
| "隐藏 IP 地址" | `messages[-1].content` | `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}` | `[IP已隐藏]` | |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 路径语法速查
|
|
||||||
|
|
||||||
| 写法 | 含义 |
|
|
||||||
|------|------|
|
|
||||||
| `temperature` | 顶层字段 |
|
|
||||||
| `metadata.source` | 嵌套字段 |
|
|
||||||
| `messages[0]` | 数组第一个元素 |
|
|
||||||
| `messages[-1]` | 数组最后一个元素 |
|
|
||||||
| `messages[0].content` | 第一条消息的 content |
|
|
||||||
| `messages[-1].content` | 最后一条消息的 content |
|
|
||||||
| `data[0].items[2]` | 嵌套数组访问 |
|
|
||||||
| `config\.v1.enabled` | key 名字里有点号时用 `\.` 转义 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 多条规则示例
|
|
||||||
|
|
||||||
用户说:"帮我配置规则:在开头插入一条 system 消息说'用中文回答',固定 temperature 为 0.3,把用户消息里的手机号脱敏"
|
|
||||||
|
|
||||||
应指导用户添加 3 条规则:
|
|
||||||
|
|
||||||
| # | 操作 | 字段 1 | 字段 2 | 字段 3 | 字段 4 |
|
|
||||||
|---|------|--------|--------|--------|--------|
|
|
||||||
| 1 | 插入 | 路径: `messages` | 位置: `0` | 值: `{"role": "system", "content": "请用中文回答所有问题"}` | |
|
|
||||||
| 2 | 覆写 | 路径: `temperature` | 值: `0.3` | | |
|
|
||||||
| 3 | 正则替换 | 路径: `messages[-1].content` | 正则: `1[3-9]\d{9}` | 替换为: `[手机号]` | flags: (留空) |
|
|
||||||
|
|
||||||
规则按从上到下的顺序执行。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **`model` 和 `stream` 不可修改** — 这两个字段由系统管理,写了规则也会被跳过
|
|
||||||
2. **值必须是合法 JSON** — 覆写/插入的值输入框要求 JSON 格式:字符串加引号 `"text"`,数字直接写 `123`,布尔 `true`/`false`
|
|
||||||
3. **路径必须指向正确类型** — 插入的路径必须是数组,正则替换的路径必须是字符串
|
|
||||||
4. **路径不存在时的行为** — 覆写会自动创建中间层级(dict),其他操作遇到不存在的路径会静默跳过
|
|
||||||
5. **正则在 UI 里直接写** — 不需要 JSON 转义,`\d` 就是 `\d`
|
|
||||||
6. **正则保存时校验** — 无效的正则表达式会在保存时报错,不会静默通过
|
|
||||||
@@ -111,11 +111,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="flex items-center gap-2 mx-16 lg:mx-28">
|
<nav class="flex items-center gap-2 mx-8 lg:mx-16">
|
||||||
<button
|
<button
|
||||||
v-for="(section, index) in sections"
|
v-for="(section, index) in sections.slice(0, -1)"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="group relative px-3 py-2 text-sm font-medium transition"
|
class="group relative px-3 py-2 text-sm font-medium transition whitespace-nowrap"
|
||||||
:class="currentSection === index
|
:class="currentSection === index
|
||||||
? 'text-[#cc785c] dark:text-[#d4a27f]'
|
? 'text-[#cc785c] dark:text-[#d4a27f]'
|
||||||
: 'text-[#666663] dark:text-muted-foreground hover:text-[#191919] dark:hover:text-white'"
|
: 'text-[#666663] dark:text-muted-foreground hover:text-[#191919] dark:hover:text-white'"
|
||||||
@@ -127,6 +127,25 @@
|
|||||||
:class="currentSection === index ? 'bg-[#cc785c] dark:bg-[#d4a27f] scale-x-100' : 'bg-transparent scale-x-0'"
|
:class="currentSection === index ? 'bg-[#cc785c] dark:bg-[#d4a27f] scale-x-100' : 'bg-transparent scale-x-0'"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
<RouterLink
|
||||||
|
to="/guide"
|
||||||
|
class="group relative px-3 py-2 text-sm font-medium transition whitespace-nowrap text-[#666663] dark:text-muted-foreground hover:text-[#191919] dark:hover:text-white"
|
||||||
|
>
|
||||||
|
文档
|
||||||
|
</RouterLink>
|
||||||
|
<button
|
||||||
|
class="group relative px-3 py-2 text-sm font-medium transition whitespace-nowrap"
|
||||||
|
:class="currentSection === SECTIONS.FEATURES
|
||||||
|
? 'text-[#cc785c] dark:text-[#d4a27f]'
|
||||||
|
: 'text-[#666663] dark:text-muted-foreground hover:text-[#191919] dark:hover:text-white'"
|
||||||
|
@click="scrollToSection(SECTIONS.FEATURES)"
|
||||||
|
>
|
||||||
|
{{ sections[SECTIONS.FEATURES].name }}
|
||||||
|
<div
|
||||||
|
class="absolute bottom-0 left-0 right-0 h-0.5 rounded-full transition-all duration-300"
|
||||||
|
:class="currentSection === SECTIONS.FEATURES ? 'bg-[#cc785c] dark:bg-[#d4a27f] scale-x-100' : 'bg-transparent scale-x-0'"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Login/Dashboard Button -->
|
<!-- Login/Dashboard Button -->
|
||||||
@@ -408,13 +427,6 @@
|
|||||||
class="mt-6 md:mt-12 transition-all duration-700 flex items-center justify-center gap-4 relative z-30"
|
class="mt-6 md:mt-12 transition-all duration-700 flex items-center justify-center gap-4 relative z-30"
|
||||||
:style="getButtonsStyle(SECTIONS.FEATURES)"
|
:style="getButtonsStyle(SECTIONS.FEATURES)"
|
||||||
>
|
>
|
||||||
<RouterLink
|
|
||||||
to="/guide"
|
|
||||||
class="inline-flex items-center justify-center gap-2 rounded-xl bg-transparent border-2 border-[#cc785c] px-6 py-3 text-base font-semibold text-[#cc785c] dark:text-[#d4a27f] dark:border-[#d4a27f] transition hover:bg-[#cc785c]/10 dark:hover:bg-[#d4a27f]/10 hover:scale-105 w-[160px]"
|
|
||||||
>
|
|
||||||
<BookOpen class="h-5 w-5" />
|
|
||||||
配置教程
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink
|
<RouterLink
|
||||||
v-if="authStore.isAuthenticated"
|
v-if="authStore.isAuthenticated"
|
||||||
:to="dashboardPath"
|
:to="dashboardPath"
|
||||||
@@ -444,7 +456,6 @@
|
|||||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||||
import { RouterLink } from 'vue-router'
|
import { RouterLink } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
BookOpen,
|
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Code2,
|
Code2,
|
||||||
Moon,
|
Moon,
|
||||||
|
|||||||
@@ -409,6 +409,10 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
mobileMenuOpen.value = false
|
mobileMenuOpen.value = false
|
||||||
activeHash.value = ''
|
activeHash.value = ''
|
||||||
|
const container = getScrollContainer()
|
||||||
|
if (container) {
|
||||||
|
container.scrollTo({ top: 0 })
|
||||||
|
}
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setupIntersectionObserver()
|
setupIntersectionObserver()
|
||||||
const firstSection = document.querySelector('article section[id]')
|
const firstSection = document.querySelector('article section[id]')
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ const colors = computed(() => {
|
|||||||
const brand = '#cc7154'
|
const brand = '#cc7154'
|
||||||
return {
|
return {
|
||||||
bg: isDark.value ? '#121212' : '#fcfcfc',
|
bg: isDark.value ? '#121212' : '#fcfcfc',
|
||||||
grid: isDark.value ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.03)',
|
|
||||||
cardBg: isDark.value ? '#1a1a1c' : '#ffffff',
|
cardBg: isDark.value ? '#1a1a1c' : '#ffffff',
|
||||||
cardBorder: isDark.value ? '#2a2a2c' : '#e8e8ec',
|
cardBorder: isDark.value ? '#2a2a2c' : '#e8e8ec',
|
||||||
track: isDark.value ? 'rgba(255,255,255,0.08)' : '#e5e7eb',
|
track: isDark.value ? 'rgba(255,255,255,0.08)' : '#e5e7eb',
|
||||||
@@ -98,8 +97,6 @@ const shadows = computed(() => {
|
|||||||
:style="{
|
:style="{
|
||||||
backgroundColor: colors.bg,
|
backgroundColor: colors.bg,
|
||||||
borderColor: colors.cardBorder,
|
borderColor: colors.cardBorder,
|
||||||
backgroundImage: `radial-gradient(${colors.grid} 1px, transparent 1px)`,
|
|
||||||
backgroundSize: '20px 20px',
|
|
||||||
transform: `scale(${scale})`,
|
transform: `scale(${scale})`,
|
||||||
transformOrigin: 'top left',
|
transformOrigin: 'top left',
|
||||||
marginLeft: `${offsetLeft}px`,
|
marginLeft: `${offsetLeft}px`,
|
||||||
|
|||||||
Reference in New Issue
Block a user