diff --git a/src/components/CopyCommand.astro b/src/components/CopyCommand.astro
index 21f7b33..925d3a9 100644
--- a/src/components/CopyCommand.astro
+++ b/src/components/CopyCommand.astro
@@ -1,5 +1,6 @@
---
-// A shell command with a copy button: the command scrolls, the button stays.
+// A shell command with a copy button. A long command wraps rather than
+// scrolling out of sight, so the whole of it can be read before it is run.
// Used for the twcore install command on the Core page and on the home page.
import { getLang, t } from "~/i18n";
@@ -17,9 +18,8 @@ const ui = t(getLang(Astro)).common;
class="flex min-w-0 items-center rounded-[14px] border border-[var(--color-border-strong)] bg-[var(--color-surface)] font-mono leading-relaxed"
data-copy-command
>
-
- $ {command}
-
+ {/* One line: the command wraps with white-space: pre-wrap, which would keep indentation as a leading space */}
+ $ {command}
diff --git a/src/components/mocks/LogExplorerMock.tsx b/src/components/mocks/LogExplorerMock.tsx
index 0ad5522..3594ab9 100644
--- a/src/components/mocks/LogExplorerMock.tsx
+++ b/src/components/mocks/LogExplorerMock.tsx
@@ -93,7 +93,7 @@ export default function LogExplorerMock() {
}, [inView]);
return (
-
{c.install.serviceUser}
-{asServiceUser}
+ {asServiceUser}
+
{current.tagline}
diff --git a/src/components/sections/HowItWorks.astro b/src/components/sections/HowItWorks.astro index a83aba8..e18a297 100644 --- a/src/components/sections/HowItWorks.astro +++ b/src/components/sections/HowItWorks.astro @@ -24,7 +24,7 @@ const providers = [
diff --git a/src/components/sections/LivePanels.astro b/src/components/sections/LivePanels.astro index 4bde451..2100bec 100644 --- a/src/components/sections/LivePanels.astro +++ b/src/components/sections/LivePanels.astro @@ -14,7 +14,7 @@ const s = t(getLang(Astro)).live;
diff --git a/src/components/sections/MCPAdvantage.astro b/src/components/sections/MCPAdvantage.astro
index 7af863a..b34924a 100644
--- a/src/components/sections/MCPAdvantage.astro
+++ b/src/components/sections/MCPAdvantage.astro
@@ -46,7 +46,7 @@ const treatmentClass: Record
diff --git a/src/components/sections/Problem.astro b/src/components/sections/Problem.astro
index 9dda631..5079042 100644
--- a/src/components/sections/Problem.astro
+++ b/src/components/sections/Problem.astro
@@ -1,6 +1,7 @@
---
import TiltCard from "../ui/TiltCard.tsx";
import { getLang, t } from "~/i18n";
+import { spanClass } from "~/lib/grid";
const s = t(getLang(Astro)).problem;
@@ -20,7 +21,7 @@ const icons: Record
@@ -28,9 +29,9 @@ const icons: Record
+
{s.title}{s.titleHighlight}
+
{s.title}{s.titleHighlight}
diff --git a/src/content/docs-core/en/crate-layers.md b/src/content/docs-core/en/crate-layers.md index 3cca065..8773f74 100644 --- a/src/content/docs-core/en/crate-layers.md +++ b/src/content/docs-core/en/crate-layers.md @@ -1,9 +1,9 @@ # Crate layers -ThinkWatch Core consists of sixteen crates and one binary, `twcore`. The workspace divides the crates in two: the three that ThinkWatch Enterprise depends on, and the crates of the gateway that `twcore` runs, which ThinkWatch Enterprise does not use. Within the second part, the crates are grouped by role. +ThinkWatch Core consists of seventeen crates and one binary, `twcore`. The workspace divides the crates in two: the four that ThinkWatch Enterprise depends on, and the crates of the gateway that `twcore` runs, which ThinkWatch Enterprise does not use. Within the second part, the crates are grouped by role. ``` -tw-dialect · tw-guard · tw-breaker ← shared with ThinkWatch Enterprise +tw-dialect · tw-guard · tw-breaker · tw-bedrock ← shared with ThinkWatch Enterprise tw-types · tw-engine · tw-pricing · tw-yaml · tw-secret · tw-watch ← domain logic tw-api · tw-link ← control-plane contract tw-config · tw-store · tw-observe ← assembly @@ -17,6 +17,7 @@ No crate depends on a group below its own. | Shared with ThinkWatch Enterprise | `tw-dialect` | Conversion of requests, responses and streams between Anthropic Messages, OpenAI Chat Completions, OpenAI Responses and Gemini; usage parsing | | | `tw-guard` | Outbound redaction and restoration, inspection of the tool calls an upstream returns, hidden characters, content filtering and the output length limit | | | `tw-breaker` | The circuit-breaker state machine | +| | `tw-bedrock` | Amazon Bedrock on the wire: SigV4 signing, event-stream framing, addresses and the model catalog | | Domain logic | `tw-types` | Messages for people: a stable code, its arguments and the English sentence | | | `tw-engine` | The routing rule engine and strategy groups | | | `tw-pricing` | The public price table, price sheets, and cost in three states: measured, estimated and unpriced | @@ -33,7 +34,7 @@ No crate depends on a group below its own. ## Shared with ThinkWatch Enterprise -ThinkWatch Enterprise depends on the first group and nothing else: format conversion and usage parsing (`tw-dialect`), the guards (`tw-guard`) and the circuit breaker (`tw-breaker`). These three depend only on each other, which a test in `tw-dialect` enforces, and CI builds ThinkWatch Enterprise against every change to them. A component that only one product uses lives in that product's repository rather than in Core. +ThinkWatch Enterprise depends on the first group and nothing else: format conversion and usage parsing (`tw-dialect`), the guards (`tw-guard`), the circuit breaker (`tw-breaker`) and Amazon Bedrock's wire protocol (`tw-bedrock`). These four depend only on each other, which a test in `tw-dialect` enforces, and CI builds ThinkWatch Enterprise against every change to them. A component that only one product uses lives in that product's repository rather than in Core. ## Used by ThinkWatch Lite diff --git a/src/content/docs-core/en/overview.md b/src/content/docs-core/en/overview.md index d8037e0..059cc6f 100644 --- a/src/content/docs-core/en/overview.md +++ b/src/content/docs-core/en/overview.md @@ -1,6 +1,6 @@ # ThinkWatch Core -ThinkWatch Core is the gateway engine of the ThinkWatch products: a set of MIT-licensed Rust crates and `twcore`, a self-contained AI API gateway binary. `twcore` is the gateway inside the desktop app, [ThinkWatch Lite](/docs/lite), and also runs on its own as a systemd service on a Linux server. ThinkWatch Enterprise depends on three of the crates: `tw-dialect`, `tw-guard` and `tw-breaker`. +ThinkWatch Core is the gateway engine of the ThinkWatch products: a set of MIT-licensed Rust crates and `twcore`, a self-contained AI API gateway binary. `twcore` is the gateway inside the desktop app, [ThinkWatch Lite](/docs/lite), and also runs on its own as a systemd service on a Linux server. ThinkWatch Enterprise depends on four of the crates: `tw-dialect`, `tw-guard`, `tw-breaker` and `tw-bedrock`. ## Scope of Core diff --git a/src/content/docs-core/en/quick-start.md b/src/content/docs-core/en/quick-start.md index 797a5d3..35cc06c 100644 --- a/src/content/docs-core/en/quick-start.md +++ b/src/content/docs-core/en/quick-start.md @@ -22,7 +22,7 @@ On a Linux server, the install script does this in one step, and also creates a curl -fsSL https://raw.githubusercontent.com/ThinkWatchProject/ThinkWatch-Core/main/scripts/install.sh | sudo sh ``` -To build from source instead, with a stable Rust toolchain (1.85 or newer): +To build from source instead, with a stable Rust toolchain (1.94.1 or newer): ```sh git clone https://github.com/ThinkWatchProject/ThinkWatch-Core.git diff --git a/src/content/docs-core/zh-CN/crate-layers.md b/src/content/docs-core/zh-CN/crate-layers.md index a9596d2..23c542c 100644 --- a/src/content/docs-core/zh-CN/crate-layers.md +++ b/src/content/docs-core/zh-CN/crate-layers.md @@ -1,9 +1,9 @@ # crate 分层 -ThinkWatch Core 由十六个 crate 和一个二进制 `twcore` 组成。工作区把这些 crate 分为两部分:ThinkWatch 企业版依赖的三个 crate,以及 `twcore` 所运行网关的其余 crate(ThinkWatch 企业版不使用)。第二部分再按职责分组。 +ThinkWatch Core 由十七个 crate 和一个二进制 `twcore` 组成。工作区把这些 crate 分为两部分:ThinkWatch 企业版依赖的四个 crate,以及 `twcore` 所运行网关的其余 crate(ThinkWatch 企业版不使用)。第二部分再按职责分组。 ``` -tw-dialect · tw-guard · tw-breaker ← 与 ThinkWatch 企业版共用 +tw-dialect · tw-guard · tw-breaker · tw-bedrock ← 与 ThinkWatch 企业版共用 tw-types · tw-engine · tw-pricing · tw-yaml · tw-secret · tw-watch ← 领域逻辑 tw-api · tw-link ← 控制面契约 tw-config · tw-store · tw-observe ← 装配 @@ -17,6 +17,7 @@ tw-gateway · tw-control ← 数据 | 与 ThinkWatch 企业版共用 | `tw-dialect` | 请求、响应与流在 Anthropic Messages、OpenAI Chat Completions、OpenAI Responses 与 Gemini 之间的转换;用量解析 | | | `tw-guard` | 出站脱敏与还原、上游返回的工具调用审查、隐藏字符、内容过滤与输出长度限制 | | | `tw-breaker` | 熔断器状态机 | +| | `tw-bedrock` | Amazon Bedrock 的线上协议:SigV4 签名、事件流拆帧、接口地址与模型目录 | | 领域逻辑 | `tw-types` | 面向用户的消息:稳定的消息码、参数与英文句子 | | | `tw-engine` | 路由规则引擎与策略组 | | | `tw-pricing` | 公开价目表、自定义价目表,以及实测、估算、无法计价三种状态的费用 | @@ -33,7 +34,7 @@ tw-gateway · tw-control ← 数据 ## 与 ThinkWatch 企业版共用 -ThinkWatch 企业版只依赖第一组:格式转换与用量解析(`tw-dialect`)、各项防护(`tw-guard`)与熔断器(`tw-breaker`)。这三个 crate 只依赖彼此,`tw-dialect` 中的一项测试保证这一点;每次改动它们,CI 都会用 ThinkWatch 企业版编译一遍。只有一个产品使用的组件放在该产品自己的仓库中,不留在 Core。 +ThinkWatch 企业版只依赖第一组:格式转换与用量解析(`tw-dialect`)、各项防护(`tw-guard`)、熔断器(`tw-breaker`)与 Amazon Bedrock 的线上协议(`tw-bedrock`)。这四个 crate 只依赖彼此,`tw-dialect` 中的一项测试保证这一点;每次改动它们,CI 都会用 ThinkWatch 企业版编译一遍。只有一个产品使用的组件放在该产品自己的仓库中,不留在 Core。 ## ThinkWatch Lite 使用的部分 diff --git a/src/content/docs-core/zh-CN/overview.md b/src/content/docs-core/zh-CN/overview.md index fc2ce9e..effbce1 100644 --- a/src/content/docs-core/zh-CN/overview.md +++ b/src/content/docs-core/zh-CN/overview.md @@ -1,6 +1,6 @@ # ThinkWatch Core -ThinkWatch Core 是 ThinkWatch 各产品共用的网关引擎,由一组采用 MIT 许可证的 Rust crate 和独立运行的 AI API 网关二进制 `twcore` 组成。`twcore` 是桌面应用 [ThinkWatch Lite](/zh-CN/docs/lite) 内置的网关,也可以作为 systemd 服务独立运行在 Linux 服务器上。ThinkWatch 企业版依赖其中的三个 crate:`tw-dialect`、`tw-guard` 与 `tw-breaker`。 +ThinkWatch Core 是 ThinkWatch 各产品共用的网关引擎,由一组采用 MIT 许可证的 Rust crate 和独立运行的 AI API 网关二进制 `twcore` 组成。`twcore` 是桌面应用 [ThinkWatch Lite](/zh-CN/docs/lite) 内置的网关,也可以作为 systemd 服务独立运行在 Linux 服务器上。ThinkWatch 企业版依赖其中的四个 crate:`tw-dialect`、`tw-guard`、`tw-breaker` 与 `tw-bedrock`。 ## Core 的定位 diff --git a/src/content/docs-core/zh-CN/quick-start.md b/src/content/docs-core/zh-CN/quick-start.md index a566422..075c80e 100644 --- a/src/content/docs-core/zh-CN/quick-start.md +++ b/src/content/docs-core/zh-CN/quick-start.md @@ -22,7 +22,7 @@ curl -fsSL https://raw.githubusercontent.com/ThinkWatchProject/ThinkWatch-Core/main/scripts/install.sh | sudo sh ``` -如需从源码构建,需要 Rust 稳定版工具链(1.85 或更新): +如需从源码构建,需要 Rust 稳定版工具链(1.94.1 或更新): ```sh git clone https://github.com/ThinkWatchProject/ThinkWatch-Core.git diff --git a/src/content/docs/_meta.ts b/src/content/docs/_meta.ts index 696eb70..2b0ce5f 100644 --- a/src/content/docs/_meta.ts +++ b/src/content/docs/_meta.ts @@ -250,8 +250,8 @@ export const products: Product[] = [ locales: both, group: "concepts", summary: { - en: "The sixteen crates grouped by role, the three that ThinkWatch Enterprise depends on, and what ThinkWatch Lite compiles.", - "zh-CN": "十六个 crate 按职责的分组、ThinkWatch 企业版依赖的三个 crate,以及 ThinkWatch Lite 编译的部分。", + en: "The seventeen crates grouped by role, the four that ThinkWatch Enterprise depends on, and what ThinkWatch Lite compiles.", + "zh-CN": "十七个 crate 按职责的分组、ThinkWatch 企业版依赖的四个 crate,以及 ThinkWatch Lite 编译的部分。", }, }, // Published from the Core repository (src/lib/core-docs.mjs). diff --git a/src/i18n/pages/core.ts b/src/i18n/pages/core.ts index 6124ddd..4810655 100644 --- a/src/i18n/pages/core.ts +++ b/src/i18n/pages/core.ts @@ -2,7 +2,7 @@ // README and CONTRIBUTING (what the crates do, how releases are built), the // workspace Cargo.toml and each crate's dependencies (the groups under "Crate // layers"), docs/server.md and scripts/install.sh (server deployment), and from -// ThinkWatch Enterprise's Cargo.toml for the three crates it depends on. +// ThinkWatch Enterprise's Cargo.toml for the four crates it depends on. // // meta.twcoreDescription describes the twcore binary in the structured data // (src/lib/structured-data.ts); the build fails without it. @@ -20,7 +20,7 @@ export const coreCopy = { eyebrow: "ThinkWatch Core · Gateway engine", titleA: "An AI API gateway engine ", titleHighlight: "for desktops and servers", - sub: "MIT-licensed Rust crates and the twcore binary. twcore is the gateway inside ThinkWatch Lite, and runs on its own as a systemd service on a Linux server, managed from ThinkWatch Lite over an encrypted control channel. ThinkWatch Enterprise uses its format-conversion, guard and circuit-breaker crates.", + sub: "MIT-licensed Rust crates and the twcore binary. twcore is the gateway inside ThinkWatch Lite, and runs on its own as a systemd service on a Linux server, managed from ThinkWatch Lite over an encrypted control channel. ThinkWatch Enterprise uses its format-conversion, guard, circuit-breaker and Bedrock crates.", ctaInstall: "Install twcore", ctaDocs: "Core documentation", ctaGithub: "View on GitHub", @@ -40,7 +40,7 @@ export const coreCopy = { }, { title: "Failover before the first byte", - body: "Until the first byte reaches the client, a failing upstream is replaced by the next one without the client noticing; after that point, the failure is reported. A circuit breaker keeps requests away from an upstream that keeps failing.", + body: "Until the first byte reaches the client, a failing upstream is replaced by the next one without the client noticing; after that point, the failure is reported. A failing upstream is paused for as long as its reason calls for: an exhausted balance, a used-up quota until its reset, a rate limit for the wait it asks.", }, { title: "Cost accounting", @@ -48,11 +48,11 @@ export const coreCopy = { }, { title: "Outbound redaction", - body: "Credentials in a request are replaced with placeholders before the request leaves, and restored when the model echoes them back.", + body: "A relay sees every request in full. Credentials in a request can be replaced with placeholders before it leaves, so the relay never holds the real values, and are restored where the answer repeats them.", }, { - title: "Tool-call inspection", - body: "Tool calls returned by a model are checked against rules, and a dangerous call can be cut off mid-stream. Together with checks for hidden characters, content rules and an output limit, these form five guards, each set to off, observe or enforce.", + title: "Malicious tool calls cut off", + body: "A relay can rewrite an answer and slip in a tool call for the client to run. Tool calls in an answer are checked against rules for download-and-run, sending out credentials and similar commands, and a dangerous call can be cut off mid-stream. Together with checks for hidden characters, content rules and an output limit, these form five guards, each set to off, observe or enforce.", }, { title: "Encrypted control plane", @@ -94,7 +94,7 @@ export const coreCopy = { releases: "All releases", version: "Version", sourceTitle: "Build from source", - sourceBody: "With a stable Rust toolchain, 1.85 or newer. The binary is written to target/release/twcore.", + sourceBody: "With a stable Rust toolchain, 1.94.1 or newer. The binary is written to target/release/twcore.", }, layers: { eyebrow: "Crate layers", @@ -102,14 +102,14 @@ export const coreCopy = { sharedTag: "Also used by ThinkWatch Enterprise", localNote: "Single-machine implementation", rows: [ - { name: "Conversion, guards and circuit breaker", crates: "tw-dialect · tw-guard · tw-breaker", mark: "shared" }, + { name: "Conversion, guards, circuit breaker and Bedrock", crates: "tw-dialect · tw-guard · tw-breaker · tw-bedrock", mark: "shared" }, { name: "Domain logic", crates: "tw-types · tw-engine · tw-pricing · tw-yaml · tw-secret · tw-watch", mark: null }, { name: "Control-plane contract", crates: "tw-api · tw-link", mark: null }, { name: "Assembly", crates: "tw-config · tw-store · tw-observe", mark: "local" }, { name: "Data plane and control plane", crates: "tw-gateway · tw-control", mark: "local" }, ], footnote: - "ThinkWatch Enterprise depends on the first group and nothing else. Those three crates depend only on each other, which a test enforces, and CI builds ThinkWatch Enterprise against every change to them. No crate depends on a group below its own. The last two groups are the single-machine implementation (SQLite, the local control channel and the optional remote port) and are intentionally not shared: single-machine SQLite and multi-tenant Postgres differ too much for one abstraction to serve both.", + "ThinkWatch Enterprise depends on the first group and nothing else. Those four crates depend only on each other, which a test enforces, and CI builds ThinkWatch Enterprise against every change to them. No crate depends on a group below its own. The last two groups are the single-machine implementation (SQLite, the local control channel and the optional remote port) and are intentionally not shared: single-machine SQLite and multi-tenant Postgres differ too much for one abstraction to serve both.", docs: "Crate layers in detail", }, dev: { @@ -138,7 +138,7 @@ export const coreCopy = { eyebrow: "ThinkWatch Core · 网关引擎", titleA: "适用于桌面与服务器的 ", titleHighlight: "AI API 网关引擎", - sub: "采用 MIT 许可证的 Rust crate 与 twcore 二进制。twcore 是 ThinkWatch Lite 内置的网关,也可以作为 systemd 服务独立运行在 Linux 服务器上,由 ThinkWatch Lite 通过加密的控制通道远程管理。ThinkWatch 企业版使用其中的格式转换、防护与熔断 crate。", + sub: "采用 MIT 许可证的 Rust crate 与 twcore 二进制。twcore 是 ThinkWatch Lite 内置的网关,也可以作为 systemd 服务独立运行在 Linux 服务器上,由 ThinkWatch Lite 通过加密的控制通道远程管理。ThinkWatch 企业版使用其中的格式转换、防护、熔断与 Bedrock crate。", ctaInstall: "安装 twcore", ctaDocs: "Core 文档", ctaGithub: "在 GitHub 上查看", @@ -158,7 +158,7 @@ export const coreCopy = { }, { title: "首字节前的故障转移", - body: "首字节到达客户端之前,出错的上游由下一个上游替换,客户端无从察觉;此后发生的故障如实报告。熔断器使持续出错的上游暂不接收请求。", + body: "首字节到达客户端之前,出错的上游由下一个上游替换,客户端无从察觉;此后发生的故障如实报告。出错的上游按原因暂停:余额不足、额度用完(到重置时间为止)与限流(按上游要求的等待时间)各有时长。", }, { title: "费用核算", @@ -166,11 +166,11 @@ export const coreCopy = { }, { title: "出站脱敏", - body: "请求发出之前,其中的凭据替换为占位符;模型回显时再恢复原值。", + body: "中转站能看到请求的全部内容。请求发出之前,其中的凭据可以替换为占位符,中转站拿不到原值;回答中重复出现时再恢复原值。", }, { - title: "工具调用审查", - body: "模型返回的工具调用按规则审查,高危调用可在流式传输中途截断。它与隐藏字符检查、内容规则和输出长度限制合为五项防护,每项可设为关闭、观察或拦截。", + title: "拦截恶意工具调用", + body: "中转站可以改写回答,塞入让客户端执行的工具调用。回答中的工具调用按下载即执行、外发凭据等危险命令规则审查,高危调用可在流式传输中途截断。它与隐藏字符检查、内容规则和输出长度限制合为五项防护,每项可设为关闭、观察或拦截。", }, { title: "加密的控制面", @@ -211,21 +211,21 @@ export const coreCopy = { releases: "全部发布版本", version: "版本", sourceTitle: "从源码构建", - sourceBody: "需要 Rust 稳定版工具链(1.85 或更新)。生成的二进制位于 target/release/twcore。", + sourceBody: "需要 Rust 稳定版工具链(1.94.1 或更新)。生成的二进制位于 target/release/twcore。", }, layers: { eyebrow: "crate 分层", sharedTag: "ThinkWatch 企业版同样使用", localNote: "单机实现", rows: [ - { name: "格式转换、防护与熔断", crates: "tw-dialect · tw-guard · tw-breaker", mark: "shared" }, + { name: "格式转换、防护、熔断与 Bedrock", crates: "tw-dialect · tw-guard · tw-breaker · tw-bedrock", mark: "shared" }, { name: "领域逻辑", crates: "tw-types · tw-engine · tw-pricing · tw-yaml · tw-secret · tw-watch", mark: null }, { name: "控制面契约", crates: "tw-api · tw-link", mark: null }, { name: "装配", crates: "tw-config · tw-store · tw-observe", mark: "local" }, { name: "数据面与控制面", crates: "tw-gateway · tw-control", mark: "local" }, ], footnote: - "ThinkWatch 企业版只依赖第一组。这三个 crate 只依赖彼此,由一项测试保证;每次改动它们,CI 都会用 ThinkWatch 企业版编译一遍。任何 crate 都不依赖排在其所在组下方的组。最后两组是单机实现(SQLite、本地控制通道与可选的远程端口),有意不共享:单机 SQLite 与多租户 Postgres 差异过大,统一的抽象难以同时满足两者。", + "ThinkWatch 企业版只依赖第一组。这四个 crate 只依赖彼此,由一项测试保证;每次改动它们,CI 都会用 ThinkWatch 企业版编译一遍。任何 crate 都不依赖排在其所在组下方的组。最后两组是单机实现(SQLite、本地控制通道与可选的远程端口),有意不共享:单机 SQLite 与多租户 Postgres 差异过大,统一的抽象难以同时满足两者。", docs: "crate 分层详解", }, dev: { diff --git a/src/lib/grid.ts b/src/lib/grid.ts new file mode 100644 index 0000000..e414f0c --- /dev/null +++ b/src/lib/grid.ts @@ -0,0 +1,23 @@ +// Column spans for a card grid that should not leave a lonely last row: three +// cards a row on wide screens and two on medium ones, with the last row's cards +// widened to fill it. The grid itself is `grid-cols-2 lg:grid-cols-6` (the +// medium breakpoint is passed in, since pages differ on it). +// +// The class names are written out in full so that Tailwind sees them. + +const MEDIUM = { + sm: { one: "sm:col-span-1", two: "sm:col-span-2" }, + md: { one: "md:col-span-1", two: "md:col-span-2" }, +} as const; + +export function spanClass(i: number, n: number, medium: keyof typeof MEDIUM = "md"): string { + const m = MEDIUM[medium]; + // Medium: two a row; an odd count widens the last card to the full row. + const mid = n % 2 === 1 && i === n - 1 ? m.two : m.one; + // Wide: three a row (two of six columns each); a remainder of one fills the + // row, a remainder of two takes half each. + const rest = n % 3; + const inLast = i >= n - rest; + const wide = !inLast || rest === 0 ? "lg:col-span-2" : rest === 1 ? "lg:col-span-6" : "lg:col-span-3"; + return `${mid} ${wide}`; +} diff --git a/src/pages/docs/_DocsHome.astro b/src/pages/docs/_DocsHome.astro index c0118c6..c61b777 100644 --- a/src/pages/docs/_DocsHome.astro +++ b/src/pages/docs/_DocsHome.astro @@ -20,7 +20,7 @@ const copy = zh "ThinkWatch 企业版、ThinkWatch Lite 与 ThinkWatch Core 的文档,涵盖架构、部署、配置、API 参考、安全模型、桌面应用与共享核心。", h1: "文档", intro: - "三个产品分别提供文档:面向组织的 ThinkWatch 企业版;桌面应用 ThinkWatch Lite,在本机运行网关,也可以连接服务器上的 ThinkWatch Core;以及 ThinkWatch Core,即 ThinkWatch Lite 所基于的网关引擎与 Rust crate,ThinkWatch 企业版也使用其中的三个 crate。可通过上方的切换器选择产品,或从下方卡片进入。", + "三个产品分别提供文档:面向组织的 ThinkWatch 企业版;桌面应用 ThinkWatch Lite,在本机运行网关,也可以连接服务器上的 ThinkWatch Core;以及 ThinkWatch Core,即 ThinkWatch Lite 所基于的网关引擎与 Rust crate,ThinkWatch 企业版也使用其中的四个 crate。可通过上方的切换器选择产品,或从下方卡片进入。", open: (name: string) => `进入 ${name} 文档`, more: "其他资源", }