Expand routing docs and TOC headings

This commit is contained in:
musistudio 2026-07-01 19:16:21 +08:00
parent f6c112148d
commit 054c4fcd94
5 changed files with 239 additions and 93 deletions

View file

@ -51,7 +51,7 @@ const isSectionDoc = !docProp || docProp === sectionDoc;
const doc = docProp ?? sectionDoc;
const { Content, frontmatter } = doc;
const allHeadings = doc.getHeadings();
const headings = allHeadings.filter((heading) => heading.depth === 2);
const tocHeadings = allHeadings.filter((heading) => heading.depth >= 2 && heading.depth <= 5);
const isExplicitHref = (value: string) =>
value.startsWith("#") ||
value.startsWith("/") ||
@ -155,9 +155,10 @@ const sidebarTree = sidebarNavItems.map((navItem, index) => {
})),
};
});
const tocItems = headings.map((heading) => ({
const tocItems = tocHeadings.map((heading) => ({
label: heading.text,
href: `#${heading.slug}`,
depth: heading.depth,
}));
const pageMarkdown = doc.rawContent();
---

View file

@ -5,32 +5,15 @@ eyebrow: Detailed Configuration
lead: Choose the model for a request, then automatically retry or switch to fallback models when the request fails.
---
## How Routing Works
## Built-In Routing
CCR first decides which model the request should use, then forwards the request upstream. The current implementation has two layers:
### Claude Code
1. **Request preprocessing**: when the built-in Claude Code route matches, CCR processes the Claude Code Agent / Task / Workflow tool descriptions, removes the first billing system text block from Claude Code subagent requests, and extracts any `<CCR-SUBAGENT-MODEL>...</CCR-SUBAGENT-MODEL>` model tag.
2. **Model decision**: if a custom router script returns a model, that model wins. Otherwise CCR tries, in order, a Claude Code subagent tag, a known `provider/model` inline selector, built-in agent routes, UI routing rules, and finally the default route.
The built-in Claude Code route detects requests from Claude Code and routes main requests to the Claude Code Agent Config model or the default route model.
The core shape of a rule is **Condition + Request action**. The condition decides whether the rule matches; the request action changes request fields. The most common action is setting `request.body.model` to a provider model or Fusion model.
Claude Code **main requests** use the Claude Code Agent Config model by default; if it is unset, they use the Routing page default model. CCR also automatically removes the first `x-anthropic-billing-header` system message injected by Claude Code so that billing helper messages do not affect later routing decisions. Claude Code Subagent, Task, and Workflow-created agents can still choose different models through the tag mechanism below.
## Built-In Claude Code Routing
The Routing page shows built-in **Claude Code** and **Codex** routes. Built-in routes are not normal routing rules: they cannot be moved, edited, or deleted. You can only enable or disable them. The info icon next to the name explains what each built-in route does.
The built-in Claude Code route detects requests from Claude Code and routes main requests to the Claude Code Agent Config model or the default route model:
| Item | Behavior |
| --- | --- |
| Match condition | Request header `user-agent` contains `claude` |
| Required setup | An enabled Claude Code config exists in **Agent Config** |
| Target model | The Claude Code config model first; if unset, the Routing page default model |
| Request action | Set `request.body.model` to the target model |
| Log reason | Main requests usually show `builtin:claude-code` |
This built-in route handles the default model for Claude Code **main requests**. Claude Code Subagent, Task, and Workflow-created agents can still choose different models through the tag mechanism below.
## Claude Code Subagent / Workflow Auto-Routing
#### Subagent / Workflow Auto-Routing
Claude Code Agent / Task / Workflow can spawn additional model requests. CCR uses tag injection to let those spawned requests choose a more appropriate CCR model:
@ -48,7 +31,7 @@ The full flow is:
Subagent / Workflow auto-routing therefore does not use headers such as `x-claude-code-agent-id` as the model selector. Those headers can help with observation, but the actual model selection comes from the prompt tag.
### Pairing It With The Models Page
##### Pairing It With The Models Page
The **Description** field on the Models page is both the enablement switch and the selection guide for this mechanism. If no model has a Description, CCR does not inject Agent / Task / Workflow routing instructions, so it does not write an empty model list into tool descriptions.
@ -70,6 +53,90 @@ Write descriptions around tasks instead of only naming the provider. For example
After saving, CCR formats those descriptions as “Configured CCR gateway models” in the injected Claude Code instructions. When Claude Code picks a model, request logs should show `builtin:claude-code-subagent`, and the tagged model becomes the final `resolved model`.
### Codex
The built-in Codex route adapts Codex's `apply_patch` file-editing tool for third-party or non-GPT models. The goal is for those models to edit files through the patch tool instead of generating commands or scripts such as `cat >`, `sed -i`, `python`, or `node`.
Technically, this is a tool protocol bridge. Native Codex `apply_patch` is a custom/freeform tool whose input is raw patch text, while many OpenAI-compatible third-party models handle ordinary function tools more reliably. CCR rewrites `apply_patch` into an upstream-visible `virtual_apply_patch` function tool and injects the full `apply_patch.lark` grammar into the tool description, requiring the model to put the patch in the `patch` field.
When the model returns `virtual_apply_patch`, CCR rewrites it back to Codex's expected shape: `custom_tool_call` with `name = apply_patch` and `input = raw patch text`. CCR does not edit files directly; Codex still executes the resulting patch. This adaptation follows the built-in **Codex** route and has no separate switch. GPT-named models keep using Codex's native freeform `apply_patch` path.
## Custom Routing
Custom routes are configured in the Routing page rule list. The top **Search routing rules** field filters by rule name, condition, request action, and related row text; the top-right **Add** button opens the **Add Routing Rule** dialog. The table shows each rule under **Name**, **Condition**, **Request action**, **Status**, and **Action**.
Custom rules match in list order, and the first enabled matching rule rewrites the request. Use the move up and move down buttons to adjust priority. Use the edit button to open **Edit Routing Rule**, and the delete button to open a confirmation dialog. Turning off the **Status** toggle keeps the rule in the list but removes it from matching.
### Add Or Edit A Rule
The dialog fields map directly to the saved rule:
| UI field | How to fill it | Saved meaning |
| --- | --- | --- |
| **Name** | Enter a recognizable rule name. This field is required. | Shown in the **Name** column and included in search. |
| **Condition** | Choose `request.header` or `request.body`, then fill in field, operator, and value. | Builds `condition.left`, `condition.operator`, and `condition.right`. |
| **Rewrite request parameters** | Keep at least one rewrite row. Each row chooses an operation, target key, and required value fields. | Builds `rewrites`, applied when the rule matches. |
| **Enabled** | Turn the rule on or off. | Controls `enabled`; disabled rules do not match. |
| **On failure** | Configure fallback behavior for this rule. | Overrides **Default on failure** when this rule matches. |
The **Add** or **Save** button is enabled only when the form is valid: name, condition field, and condition value are required; every rewrite row must have a key. **Delete** only requires a key. **Replace in array** requires both **Match value** and **Value**. Other operations require **Value**.
### Condition
The **Condition** area has four controls: source, field, operator, and value.
| Source | Field examples | Matched path |
| --- | --- | --- |
| `request.header` | `user-agent`, `x-api-key`, `x-client-name` | `request.header.user-agent` |
| `request.body` | `model`, `messages`, `messages.0.role`, `tools` | `request.body.model` |
Header names are case-insensitive. Body fields use dot-path lookup, and numeric segments address array indexes; for example, `messages.0.role` reads the first message role. For nested arrays such as `messages` or `tools`, `contains deep` is usually more robust than a fixed index.
The value field is parsed as a common literal when possible: `true`, `false`, `null`, numbers, JSON objects, and JSON arrays compare as their corresponding types. Other input is treated as a string. To force a value to stay string-like, wrap it as `"123"` or `'123'`.
| Operator | Use |
| --- | --- |
| `==` / `!=` | Compare actual and expected values. Numbers compare numerically; other values compare by comparable text. |
| `>` / `>=` / `<` / `<=` | Compare numerically when both sides are numbers; otherwise compare text order. |
| `starts with` | Check whether the actual value starts with the input value. Useful for model-prefix routing. |
| `contains` | Check substring containment for strings; for arrays, check direct array elements. |
| `contains deep` | Recursively checks objects and arrays. Useful for searching `messages` and `tools`. |
| `not contains` | The inverse of `contains`. |
### Rewrite Request Parameters
The **Rewrite request parameters** area starts with one `request.body.model` row. This is the common model-routing path: choose **Set**, use key `request.body.model`, and set the value to a target `provider/model` or Fusion model.
Click **Add parameter** to add more rewrite rows. The trash button removes a row, but the last row cannot be removed. When the rule matches, CCR applies the rewrite rows in order.
| Operation | Required fields | Behavior |
| --- | --- | --- |
| **Set** | key, value | Sets a request field, such as `request.body.model = provider/model` or `request.body.temperature = 0.2`. |
| **Delete** | key | Deletes a request field. Deleting `request.header.x-test` removes that header; deleting `request.body.foo` removes that body field. |
| **Append to array** | key, value | Appends the value to the target array. If the target is not an array, CCR starts from an empty array. |
| **Prepend to array** | key, value | Prepends the value to the target array. |
| **Remove from array** | key, value | Removes array elements equal to the value. |
| **Replace in array** | key, match value, value | Replaces array elements matching **Match value** with the new value. |
Rewrite values are also parsed as literals, so `0.2` becomes a number, `true` becomes a boolean, and `{"type":"web_search"}` becomes an object. Only `request.body.model` receives additional CCR model-selector normalization.
### On Failure
The dialog **On failure** control is the same control used by the page-level **Default on failure** setting. Choose **Off** to avoid fallback. Choose **Retry** to reveal **Retries**. Choose **Fallback targets** to reveal the **Fallback target** input and **Add** button. Added targets appear as tags with move up, move down, and remove buttons for ordering the fallback chain.
When a rule matches, its **On failure** setting is used. Requests that do not match a rule continue to use the page-level default.
### Examples
| Goal | Condition source | Field | Operator | Value | Rewrite request parameters |
| --- | --- | --- | --- | --- | --- |
| Route by client header | `request.header` | `x-client-name` | `==` | `claude-code` | **Set** `request.body.model = provider/model` |
| Route by original model prefix | `request.body` | `model` | `starts with` | `claude-` | **Set** `request.body.model = provider/model` |
| Route message content to a vision model | `request.body` | `messages` | `contains deep` | `image` | **Set** `request.body.model = vision-provider/model` |
| Remove a debug header | `request.header` | `x-debug-route` | `==` | `1` | **Delete** `request.header.x-debug-route` |
After saving, the rule appears in the list. Use request logs, especially `request model`, `resolved provider`, `resolved model`, and route reason, to verify that it matched.
## What Fallback Does
Fallback is the failure strategy after a model or upstream request fails. It does not pick the first model; it decides whether CCR should keep trying after the current target fails.
@ -117,24 +184,6 @@ When adding or editing a routing rule, configure **On failure** for that rule.
Rule-level Fallback is useful for high-risk or expensive targets. For example, route image tasks to a Fusion vision model first, then fall back to another multimodal model; or route complex tasks to a strong model first, then fall back to a stable model.
### Conditional Routing
The current UI primarily creates conditional rules. Conditions can read request headers or the request body:
| Source | Example |
| --- | --- |
| `request.header` | `x-client-name == claude-code` |
| `request.body` | `model starts-with claude-` |
| `request.body` | `messages contains-deep image` |
After a match, the request action can set, delete, or modify request fields. The most common action is:
```text
set request.body.model = provider/model
```
The model can also be a Fusion model, so routing can send selected requests to vision, search, or tool-augmented models.
## Verification
After saving, send a request and inspect Logs:

View file

@ -5,32 +5,15 @@ eyebrow: 详细配置
lead: 设置请求如何选择模型,并在失败时通过 Fallback 自动重试或切换到备用模型。
---
## 路由如何生效
## 内置路由
CCR 的路由会先决定本次请求使用哪个模型,然后再把请求交给上游。当前实现可以分成两层:
### Claude Code
1. **请求预处理**Claude Code 内置路由命中时CCR 会先处理 Claude Code 的 Agent / Task / Workflow 工具说明、移除 Claude Code subagent 请求里第一条 billing system 文本块,并提取 `<CCR-SUBAGENT-MODEL>...</CCR-SUBAGENT-MODEL>` 模型标签。
2. **模型决策**:如果配置了自定义路由脚本且脚本返回模型,这个模型优先。否则 CCR 会按顺序尝试 Claude Code subagent 标签、已知的 `供应商/模型` 直连模型、内置 Agent 路由、界面路由规则和默认路由。
Claude Code 内置路由的作用是识别 Claude Code 发来的请求,并把主请求路由到 Claude Code Agent 配置或默认路由中的模型。
路由规则的核心是 **条件 + 请求动作**。条件判断请求是否命中,请求动作修改请求字段。最常用的动作是把 `request.body.model` 设置为目标模型或 Fusion 模型。
Claude Code **主请求** 默认使用 Claude Code Agent 配置中的模型如果未设置则使用路由页的默认模型。CCR 也会自动删除 Claude Code 注入的第一条 `x-anthropic-billing-header` system 消息避免这类计费辅助消息影响后续路由判断。Claude Code 创建的 Subagent、Task 或 Workflow 内部 Agent 可以继续用下面的标签机制自动选择不同模型。
## Claude Code 内置路由
路由页面会显示内置的 **Claude Code****Codex** 路由。内置路由不是普通规则,不能上移、下移、编辑或删除,只能启用或关闭。名称旁的信息图标会说明该内置路由的功能。
Claude Code 内置路由的作用是识别 Claude Code 发来的请求,并把主请求路由到 Claude Code Agent 配置或默认路由中的模型:
| 项目 | 行为 |
| --- | --- |
| 命中条件 | 请求 Header 的 `user-agent` 包含 `claude` |
| 启用前提 | **Agent配置** 中存在已启用的 Claude Code 配置 |
| 目标模型 | 优先使用 Claude Code 配置里的模型;如果未设置,使用路由页的默认模型 |
| 请求动作 | 设置 `request.body.model` 为目标模型 |
| 日志原因 | 普通主请求通常显示为 `builtin:claude-code` |
这个内置路由解决的是 Claude Code **主请求** 的默认模型选择。Claude Code 创建的 Subagent、Task 或 Workflow 内部 Agent 可以继续用下面的标签机制自动选择不同模型。
## Claude Code Subagent / Workflow 自动路由
#### Subagent / Workflow 自动路由
Claude Code 的 Agent / Task / Workflow 可以派生新的模型请求。CCR 使用标签注入来让这些派生请求选择更合适的 CCR 模型:
@ -48,7 +31,7 @@ Claude Code 的 Agent / Task / Workflow 可以派生新的模型请求。CCR 使
因此Subagent / Workflow 的自动路由不是靠 `x-claude-code-agent-id` 之类的 Header 决定模型,而是靠 prompt 标签。Header 只能作为观测线索,真正的模型选择来自标签。
### 与模型页配合
##### 与模型页配合
模型页里的 **Description** 是这套机制的开关和选择依据。没有任何模型 Description 时CCR 不会注入 Agent / Task / Workflow 路由提示词,避免把空模型列表写进工具说明。
@ -70,6 +53,90 @@ Description 建议写成任务导向,而不是只写模型厂商名。例如
保存后CCR 会把这些 Description 组织成 “Configured CCR gateway models” 注入给 Claude Code。Claude Code 选择模型后CCR 会在派生请求上看到 `builtin:claude-code-subagent`,并把标签里的模型作为最终 `resolved model`
### Codex
Codex 内置路由会为第三方或非 GPT 模型适配 Codex 的 `apply_patch` 文件编辑工具。目标是让这些模型通过 patch 工具完成文件修改,而不是生成 `cat >``sed -i``python``node` 等命令或脚本来编辑文件。
技术原理是做一次工具协议桥接Codex 原生的 `apply_patch` 是 custom/freeform 工具,入参是原始 patch 文本;很多 OpenAI-compatible 三方模型更擅长普通 function tool。CCR 会在上游请求中把 `apply_patch` 转成 `virtual_apply_patch` function tool并在工具说明里注入完整的 `apply_patch.lark` 语法,要求模型把 patch 写入 `patch` 字段。
模型返回 `virtual_apply_patch`CCR 会把它转换回 Codex 期望的 `custom_tool_call``name = apply_patch``input = 原始 patch 文本`。CCR 不直接修改文件,真正执行 patch 的仍然是 Codex 客户端。这个适配跟随 **Codex** 内置路由启用或关闭没有单独开关GPT 命名模型继续使用 Codex 原生 freeform `apply_patch` 路径。
## 自定义路由
自定义路由在路由页的规则列表中配置。页面顶部的 **搜索路由规则** 可以按名称、条件、请求动作等文本过滤列表;右上角 **添加** 按钮打开 **添加路由规则** 弹窗。规则表格按 **名称**、**条件**、**请求动作**、**状态**、**操作** 展示每条规则。
自定义规则按列表顺序匹配,第一条命中的启用规则会改写请求。表格右侧的上移、下移按钮用来调整优先级,编辑按钮打开 **编辑路由规则**,删除按钮会先弹出确认框。**状态** 列的开关关闭后,规则保留在列表里,但不会参与匹配。
### 添加或编辑规则
弹窗里的字段和保存后的配置一一对应:
| UI 字段 | 填写方式 | 保存后的含义 |
| --- | --- | --- |
| **名称** | 填一个便于识别的规则名。该字段不能为空。 | 显示在列表 **名称** 列,也参与搜索。 |
| **条件** | 选择 `request.header``request.body`,填写字段名、操作符和值。 | 生成 `condition.left``condition.operator``condition.right`。 |
| **改写请求参数** | 至少保留一行 rewrite。每行选择操作、目标 key 和需要的值。 | 生成 `rewrites`,规则命中后按行改写请求。 |
| **启用** | 打开或关闭规则。 | 控制 `enabled`,关闭时不会匹配。 |
| **失败时** | 配置这条规则自己的 Fallback。 | 规则命中后覆盖页面顶部的 **默认失败处理**。 |
**添加** 或 **保存** 按钮只有在表单有效时才可点击:名称、条件字段、条件值都必须填写;每条 rewrite 都必须有 key。`删除` 操作只需要 key`替换数组元素` 需要同时填写 **匹配值****值**;其他操作需要填写 **值**
### 条件
**条件** 区域有四个输入:来源、字段、操作符和值。
| 来源 | 字段示例 | 实际匹配路径 |
| --- | --- | --- |
| `request.header` | `user-agent``x-api-key``x-client-name` | `request.header.user-agent` |
| `request.body` | `model``messages``messages.0.role``tools` | `request.body.model` |
Header 名不区分大小写。Body 字段按点号路径读取,数字片段表示数组下标;例如 `messages.0.role` 读取第一条 message 的 role。对于 messages、tools 这类嵌套数组,通常用 `contains deep` 比固定下标更稳。
值输入框会按常见字面量解析:`true``false``null`、数字、JSON 对象或数组会按对应类型比较;其他内容按字符串处理。需要强制作为字符串时,可以写成 `"123"``'123'`
| 操作符 | 用法 |
| --- | --- |
| `==` / `!=` | 比较实际值和输入值。数字会按数字比较,其他值按可比较文本比较。 |
| `>` / `>=` / `<` / `<=` | 两边都是数字时按数字比较,否则按文本顺序比较。 |
| `starts with` | 判断实际值是否以输入值开头,适合模型前缀分流。 |
| `contains` | 对字符串做包含判断;对数组只检查数组元素。 |
| `contains deep` | 递归检查对象和数组,适合在 `messages``tools` 中查找内容。 |
| `not contains` | `contains` 的反向判断。 |
### 改写请求参数
**改写请求参数** 区域默认给出一行 `request.body.model`。这也是最常用的模型路由写法:选择 **设置**key 填 `request.body.model`,值填目标 `供应商/模型` 或 Fusion 模型。
点击 **添加参数** 可以追加多行 rewrite垃圾桶按钮删除当前行最后一行不能删除。规则命中后CCR 会按列表顺序应用这些 rewrite。
| 操作 | 需要填写 | 行为 |
| --- | --- | --- |
| **设置** | key、值 | 设置请求里的字段,例如 `request.body.model = provider/model``request.body.temperature = 0.2`。 |
| **删除** | key | 删除请求字段。删除 `request.header.x-test` 会移除对应 Header删除 `request.body.foo` 会移除 body 字段。 |
| **追加到数组** | key、值 | 把值追加到目标数组末尾。目标不是数组时按空数组开始。 |
| **插入到数组开头** | key、值 | 把值插到目标数组开头。 |
| **从数组移除** | key、值 | 从目标数组中移除等于该值的元素。 |
| **替换数组元素** | key、匹配值、值 | 把数组中匹配 **匹配值** 的元素替换为新值。 |
Rewrite 的值也会按字面量解析,所以 `0.2` 会变成数字,`true` 会变成布尔值,`{"type":"web_search"}` 会变成对象。只有 `request.body.model` 的值会额外按 CCR 的模型选择器格式规范化。
### 失败时
弹窗底部的 **失败时** 和页面顶部的 **默认失败处理** 是同一套控件。选择 **关闭** 时不降级;选择 **继续重试** 时会出现 **重试次数**;选择 **失败降级目标** 时会出现 **失败降级目标** 输入框和 **添加** 按钮。添加后的目标会以标签形式显示,标签上的上移、下移、移除按钮用于调整降级顺序。
规则命中时会使用这条规则自己的 **失败时** 设置;没有命中的请求才继续使用页面顶部的默认设置。
### 配置示例
| 目标 | 条件来源 | 字段 | 操作符 | 值 | 改写请求参数 |
| --- | --- | --- | --- | --- | --- |
| 按客户端 Header 分流 | `request.header` | `x-client-name` | `==` | `claude-code` | **设置** `request.body.model = 供应商/模型` |
| 按原始模型前缀分流 | `request.body` | `model` | `starts with` | `claude-` | **设置** `request.body.model = 供应商/模型` |
| 按消息内容分流到视觉模型 | `request.body` | `messages` | `contains deep` | `image` | **设置** `request.body.model = 视觉供应商/模型` |
| 删除调试 Header | `request.header` | `x-debug-route` | `==` | `1` | **删除** `request.header.x-debug-route` |
保存后,规则会出现在列表中。请求日志里的 `request model``resolved provider``resolved model` 和路由原因可以用来确认规则是否命中。
## Fallback 是什么
Fallback 是请求失败后的降级策略。它不负责第一次选模型,而是在当前模型或上游失败时决定是否继续尝试。
@ -117,24 +184,6 @@ Fallback 是请求失败后的降级策略。它不负责第一次选模型,
规则级 Fallback 适合高风险或高成本模型。例如:图片任务先走 Fusion 视觉模型,失败后切到另一个多模态模型;复杂任务先走强模型,失败后切到稳定模型。
### 条件路由
当前界面新增规则时主要使用条件路由。条件可以读取请求 Header 或请求 Body
| 来源 | 示例 |
| --- | --- |
| `request.header` | `x-client-name == claude-code` |
| `request.body` | `model starts-with claude-` |
| `request.body` | `messages contains-deep image` |
命中后,请求动作可以设置、删除或修改请求字段。最常用的是:
```text
set request.body.model = 供应商/模型
```
模型也可以是 Fusion 模型。这样路由可以把特定请求导向视觉、搜索或工具增强模型。
## 验证方式
保存后发一次请求,到请求日志里检查:

View file

@ -394,14 +394,21 @@ const sidebarCloseLabel = locale === "zh" ? "关闭目录" : "Close navigation";
</h2>
<nav>
{
tocItems.map((item, index) => (
<a
class:list={{ active: index === 0 }}
href={typeof item === "string" ? `#section-${index + 1}` : item.href}
>
{typeof item === "string" ? item : item.label}
</a>
))
tocItems.map((item, index) => {
const tocItem = typeof item === "string"
? { label: item, href: `#section-${index + 1}`, depth: 2 }
: { depth: 2, ...item };
const depth = Math.min(Math.max(Number(tocItem.depth), 2), 6);
return (
<a
class:list={[`toc-depth-${depth}`, { active: index === 0 }]}
href={tocItem.href}
>
{tocItem.label}
</a>
);
})
}
</nav>
</div>

View file

@ -847,6 +847,8 @@ pre {
h1,
h2,
h3,
h4,
h5,
p {
overflow-wrap: anywhere;
}
@ -956,6 +958,24 @@ h1 {
font-weight: 720;
}
.doc-article h4,
.doc-markdown > h4 {
margin: 24px 0 8px;
color: var(--heading);
font-size: 15px;
line-height: 1.45;
font-weight: 700;
}
.doc-article h5,
.doc-markdown > h5 {
margin: 18px 0 6px;
color: var(--heading);
font-size: 14px;
line-height: 1.45;
font-weight: 680;
}
.doc-markdown > ul,
.doc-markdown > ol {
margin: 0 0 22px;
@ -1552,6 +1572,7 @@ pre code span {
}
.toc a {
--toc-dot-left: 0px;
position: relative;
display: block;
padding: 5px 0 5px 21px;
@ -1559,6 +1580,25 @@ pre code span {
line-height: 1.35;
}
.toc a.toc-depth-3 {
--toc-dot-left: 12px;
padding-left: 33px;
font-size: 13.5px;
}
.toc a.toc-depth-4 {
--toc-dot-left: 24px;
padding-left: 45px;
font-size: 13px;
}
.toc a.toc-depth-5,
.toc a.toc-depth-6 {
--toc-dot-left: 36px;
padding-left: 57px;
font-size: 12.5px;
}
.toc a.active {
color: var(--green);
font-weight: 720;
@ -1566,7 +1606,7 @@ pre code span {
.toc a.active::before {
position: absolute;
left: 0;
left: var(--toc-dot-left);
top: 13px;
width: 5px;
height: 5px;