From e60a2bedb95e0b7180da252e5e96f4aa560e345b Mon Sep 17 00:00:00 2001 From: fylorn <249551762+fylorn@users.noreply.github.com> Date: Sat, 3 Oct 2026 22:39:43 +0800 Subject: [PATCH] Use core v0.60.0: auxiliary requests are answered locally or forwarded Core v0.60.0 has two modes for client helper requests instead of three. "Pass through" and "Use routing" both went through the routing rules and differed only in whether a rule's auxiliary-request condition could match; they are now one mode, "Forward", and that condition takes effect as soon as a rule uses it. - Auxiliary tab: two-way segmented control, copy says forwarded requests follow the routing rules - Rule dialog: the "set these categories to be routed on save" checkbox is gone; a warning names the classes the condition picks that are answered locally (they never reach routing), unless "any auxiliary request" is chosen - Route dialog: no longer sends route_probes - Dry run: the pass-through outcome is gone - Pin core v0.60.0 (protocol 36); regenerated tw-api.ts, fixtures and the zh message table Co-Authored-By: Claude Opus 5.5 --- scripts/shots/core/en/overview.json | 6 +-- scripts/shots/core/en/status.json | 4 +- scripts/shots/core/zh/overview.json | 6 +-- scripts/shots/core/zh/status.json | 4 +- src-tauri/Cargo.lock | 44 ++++++++++---------- src-tauri/Cargo.toml | 12 +++--- src-tauri/src/notices/fixtures/snapshot.json | 6 +-- src/generated/tw-api.ts | 30 +++++-------- src/i18n/core.zh.json | 1 - src/routing/DryRunDialog.i18n.ts | 10 ++--- src/routing/DryRunDialog.tsx | 7 +--- src/routing/ProbesTab.i18n.ts | 17 +++----- src/routing/ProbesTab.tsx | 11 +++-- src/routing/RouteDialog.tsx | 14 +------ src/routing/RuleDialog.i18n.ts | 10 ++--- src/routing/RuleDialog.tsx | 41 ++++++++---------- 16 files changed, 91 insertions(+), 132 deletions(-) diff --git a/scripts/shots/core/en/overview.json b/scripts/shots/core/en/overview.json index 9111af73..158330cf 100644 --- a/scripts/shots/core/en/overview.json +++ b/scripts/shots/core/en/overview.json @@ -10,15 +10,15 @@ }, { "id": "titling", - "mode": "passthrough" + "mode": "forward" }, { "id": "topic_detect", - "mode": "passthrough" + "mode": "forward" }, { "id": "suggestion", - "mode": "passthrough" + "mode": "forward" } ], "clients": [ diff --git a/scripts/shots/core/en/status.json b/scripts/shots/core/en/status.json index cba85b6e..1c1abf89 100644 --- a/scripts/shots/core/en/status.json +++ b/scripts/shots/core/en/status.json @@ -1,5 +1,5 @@ { - "api_version": 35, + "api_version": 36, "clients": 4, "config_path": "", "gateway_addr": null, @@ -14,5 +14,5 @@ "reachable": [] }, "uptime_secs": 0, - "version": "0.59.0" + "version": "0.60.0" } diff --git a/scripts/shots/core/zh/overview.json b/scripts/shots/core/zh/overview.json index 2df9aa12..8665b2eb 100644 --- a/scripts/shots/core/zh/overview.json +++ b/scripts/shots/core/zh/overview.json @@ -10,15 +10,15 @@ }, { "id": "titling", - "mode": "passthrough" + "mode": "forward" }, { "id": "topic_detect", - "mode": "passthrough" + "mode": "forward" }, { "id": "suggestion", - "mode": "passthrough" + "mode": "forward" } ], "clients": [ diff --git a/scripts/shots/core/zh/status.json b/scripts/shots/core/zh/status.json index cba85b6e..1c1abf89 100644 --- a/scripts/shots/core/zh/status.json +++ b/scripts/shots/core/zh/status.json @@ -1,5 +1,5 @@ { - "api_version": 35, + "api_version": 36, "clients": 4, "config_path": "", "gateway_addr": null, @@ -14,5 +14,5 @@ "reachable": [] }, "uptime_secs": 0, - "version": "0.59.0" + "version": "0.60.0" } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c61d36c8..b17bbf8c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -82,7 +82,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "windows-sys 0.59.0", + "windows-sys 0.52.0", "wl-clipboard-rs", "x11rb", ] @@ -974,7 +974,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.3", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1175,7 +1175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2651,7 +2651,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3591,7 +3591,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3647,7 +3647,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4733,7 +4733,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5279,8 +5279,8 @@ dependencies = [ [[package]] name = "tw-api" -version = "0.59.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.59.0#ef10a8b0bbc1325edf43553a87169029672808a4" +version = "0.60.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.60.0#291ef531b121117872815cd6db731fdd04ac336a" dependencies = [ "serde", "serde_json", @@ -5291,8 +5291,8 @@ dependencies = [ [[package]] name = "tw-dialect" -version = "0.59.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.59.0#ef10a8b0bbc1325edf43553a87169029672808a4" +version = "0.60.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.60.0#291ef531b121117872815cd6db731fdd04ac336a" dependencies = [ "serde", "serde_json", @@ -5300,8 +5300,8 @@ dependencies = [ [[package]] name = "tw-guard" -version = "0.59.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.59.0#ef10a8b0bbc1325edf43553a87169029672808a4" +version = "0.60.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.60.0#291ef531b121117872815cd6db731fdd04ac336a" dependencies = [ "base64 0.22.1", "bytes", @@ -5316,8 +5316,8 @@ dependencies = [ [[package]] name = "tw-link" -version = "0.59.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.59.0#ef10a8b0bbc1325edf43553a87169029672808a4" +version = "0.60.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.60.0#291ef531b121117872815cd6db731fdd04ac336a" dependencies = [ "serde", "serde_json", @@ -5343,8 +5343,8 @@ dependencies = [ [[package]] name = "tw-types" -version = "0.59.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.59.0#ef10a8b0bbc1325edf43553a87169029672808a4" +version = "0.60.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.60.0#291ef531b121117872815cd6db731fdd04ac336a" dependencies = [ "serde", "ts-rs", @@ -5352,8 +5352,8 @@ dependencies = [ [[package]] name = "tw-watch" -version = "0.59.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.59.0#ef10a8b0bbc1325edf43553a87169029672808a4" +version = "0.60.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.60.0#291ef531b121117872815cd6db731fdd04ac336a" dependencies = [ "notify", "thiserror 2.0.21", @@ -5362,8 +5362,8 @@ dependencies = [ [[package]] name = "tw-yaml" -version = "0.59.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.59.0#ef10a8b0bbc1325edf43553a87169029672808a4" +version = "0.60.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.60.0#291ef531b121117872815cd6db731fdd04ac336a" dependencies = [ "saphyr-parser", "thiserror 2.0.21", @@ -5860,7 +5860,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0d40570f..4ff7f9aa 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,12 +26,12 @@ license = "MIT" # twcore 二进制必须和这里编译进去的协议镜像来自同一个 core 版本 —— 打包脚本正是 # 从 tw-api 锁到的 tag 去取二进制的(见 scripts/fetch-core.sh)。 [workspace.dependencies] -tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" } -tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" } -tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" } -tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" } -tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" } -tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.59.0" } +tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.60.0" } +tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.60.0" } +tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.60.0" } +tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.60.0" } +tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.60.0" } +tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.60.0" } [lib] name = "thinkwatch_lite_lib" diff --git a/src-tauri/src/notices/fixtures/snapshot.json b/src-tauri/src/notices/fixtures/snapshot.json index 5063e4ed..1b5e5d94 100644 --- a/src-tauri/src/notices/fixtures/snapshot.json +++ b/src-tauri/src/notices/fixtures/snapshot.json @@ -148,15 +148,15 @@ }, { "id": "titling", - "mode": "passthrough" + "mode": "forward" }, { "id": "topic_detect", - "mode": "passthrough" + "mode": "forward" }, { "id": "suggestion", - "mode": "passthrough" + "mode": "forward" } ], "retention": { diff --git a/src/generated/tw-api.ts b/src/generated/tw-api.ts index df95d279..7b268c3e 100644 --- a/src/generated/tw-api.ts +++ b/src/generated/tw-api.ts @@ -1,6 +1,6 @@ // Generated by tw-api (`tw_api::ts::export_all`). Do not edit by hand. -export const CONTROL_API_VERSION = 35; +export const CONTROL_API_VERSION = 36; /** * 一个账号上游登的是哪个账号。 @@ -672,7 +672,7 @@ export type Dialect = "anthropic" | "openai-chat" | "openai-responses" | "gemini /** * 一次试算的结论。 */ -export type DryRunOutcome = "route" | "deny" | "no_match" | "unavailable" | "intercepted" | "passthrough"; +export type DryRunOutcome = "route" | "deny" | "no_match" | "unavailable" | "intercepted"; /** * 「如果现在来这样一个请求,会走到哪儿」。 @@ -719,11 +719,11 @@ route: string, strategy?: GroupKind | null, /** * `route` | `deny` | `no_match` | `unavailable`(选中的上游都服务不了, - * 见 `skipped`)| `intercepted` | `passthrough` + * 见 `skipped`)| `intercepted` * - * **后两个说的是这个请求压根没到规则那一层。**客户端自己发的辅助 - * 请求先过 `client_probes`:本地应答的一个字节都不出本机,原样放行的 - * 直接转发 —— 两种情况下 `trace` 都是空的,因为确实一条规则都没求值。 + * **`intercepted` 说的是这个请求压根没到规则那一层。**客户端自己发的辅助 + * 请求先过 `client_probes`:本地应答的一个字节都不出本机,`trace` 是空的, + * 因为确实一条规则都没求值。转发的那几类照常走规则,和普通请求一样。 */ outcome: DryRunOutcome, /** @@ -2501,14 +2501,13 @@ export type ProbeClass = "health_check" | "warmup" | "titling" | "topic_detect" /** * 一类辅助请求怎么处理。 */ -export type ProbeMode = "intercept" | "route" | "passthrough"; +export type ProbeMode = "intercept" | "forward"; /** * 一类客户端辅助请求的处置。 * - * **这一段以前在界面上完全不存在,而它的缺席是连锁的**:路由条件 - * `when.intent` 只有在对应那一类被配成 `route` 时才可能命中,所以 - * 界面上那些写了 `intent` 的规则永远不会生效,而用户无从知道为什么。 + * 路由条件 `when.intent` 只对 `forward` 的类别命中 —— `intercept` 的一个字节都不出 + * 本机,到不了规则那一层。 */ export type ProbeView = { /** @@ -2516,7 +2515,7 @@ export type ProbeView = { */ id: ProbeClass, /** - * `intercept` / `route` / `passthrough` + * `intercept` / `forward` */ mode: ProbeMode, }; @@ -3165,14 +3164,7 @@ export type RouteSave = { route: RouteInput, base_version?: string | null, * 原来用它、不在列表里的改用默认路由,和路由本身在同一个版本里写入。 * 不给就不动密钥的选择 */ -keys?: Array | null, -/** - * 和路由一起写入:把这几类客户端辅助请求设为「交给路由」。 - * - * **规则里的辅助请求条件只对交给路由的类别生效** —— 其余类别的请求在 - * 进路由之前就被本地应答或原样放行,不带类别标记,那个条件永远不满足 - */ -route_probes?: Array, }; +keys?: Array | null, }; /** * 一段时间里各条路由走了多少请求、各条规则命中了多少,以及这些数从哪一刻起是全的 diff --git a/src/i18n/core.zh.json b/src/i18n/core.zh.json index fc8d322e..1020d265 100644 --- a/src/i18n/core.zh.json +++ b/src/i18n/core.zh.json @@ -526,7 +526,6 @@ "control.sheet_in_use": "价目表「{sheet}」仍被上游{upstreams!names}使用,请先解除关联再删除。", "control.default_route_cannot_delete": "路由「{route}」是默认路由,不能删除。请先将其他路由设为默认路由。", "control.reassign_to_deleted_route": "网关密钥不能改用正在删除的路由「{route}」。", - "control.unknown_probe_class": "没有「{class}」这一类辅助请求。", "control.builtin_group_fixed": "每个上游的内置策略组随上游列表自动生成,不能编辑或删除。", "control.route.duplicate_rule": "路由中有两条名为「{rule}」的规则。", "control.rule.condition_no_value": "规则「{rule}」:条件 {field} 没有值。", diff --git a/src/routing/DryRunDialog.i18n.ts b/src/routing/DryRunDialog.i18n.ts index 385fd9a4..ac4bf1b6 100644 --- a/src/routing/DryRunDialog.i18n.ts +++ b/src/routing/DryRunDialog.i18n.ts @@ -35,12 +35,10 @@ export const dryRunText = messages( denied: "拒绝", unavailable: "选中的上游均无法服务此请求,请求将返回错误", noMatch: "无规则命中,请求将返回错误", - // 这两类请求在规则之前就有了去向 —— 说清它去了哪儿,以及怎么让它走规则 + // 本地应答的请求在规则之前就有了去向 —— 说清它去了哪儿,以及怎么让它走规则 intercepted: "本地应答", interceptedWhat: "网关直接应答这一类请求,不发送到上游,也不产生费用。路由规则不参与。", - passedThrough: "原样放行", - passedThroughWhat: "作为普通请求转发,不经过路由规则。", - howToRoute: "要让它按规则走,在「辅助请求」中将这一类改为「交给路由」。", + howToRoute: "要让它按规则走,在「辅助请求」中将这一类改为「转发」。", decides: "命中 · 决定去向", applies: "命中 · 应用改写参数或安全要求", decidedBy: (n: number) => `命中 · 去向已由第 ${n} 条决定`, @@ -86,9 +84,7 @@ export const dryRunText = messages( intercepted: "Answered locally", interceptedWhat: "The gateway answers this kind directly. Nothing is sent upstream and no cost is incurred. The routing rules take no part.", - passedThrough: "Passed through", - passedThroughWhat: "Forwarded as an ordinary request, without going through the routing rules.", - howToRoute: "To have the rules decide, set this kind to “Use routing” under Auxiliary.", + howToRoute: "To have the rules decide, set this kind to “Forward” under Auxiliary.", decides: "Matched · decides the destination", applies: "Matched · applies parameter rewrites or security requirements", decidedBy: (n: number) => `Matched · destination already decided by rule ${n}`, diff --git a/src/routing/DryRunDialog.tsx b/src/routing/DryRunDialog.tsx index ad5d569d..6fb9beb7 100644 --- a/src/routing/DryRunDialog.tsx +++ b/src/routing/DryRunDialog.tsx @@ -338,7 +338,6 @@ function verdictTone(r: DryRunResult): StatusTone { case "route": return "ok"; case "intercepted": - case "passthrough": return "idle"; default: return "error"; @@ -368,7 +367,7 @@ function Result({ * **那时下面每一格都不适用** —— 路由、候选、改写说的都是规则走完之后 * 的事,而一条规则都没走。留着它们只会让人以为它真的去了那儿。 */ - const short = r.outcome === "intercepted" || r.outcome === "passthrough"; + const short = r.outcome === "intercepted"; const target = r.outcome === "route" ? (r.via_group ?? r.candidates[0] ?? null) : null; // 这一趟经过的路:密钥 → 路由 → 规则 → 去向。和路由图同一套标志 @@ -427,7 +426,7 @@ function Result({ {r.outcome === "deny" && r.reason &&

{t.reason(r.reason)}

} {short && (
- {r.outcome === "intercepted" ? t.interceptedWhat : t.passedThroughWhat} + {t.interceptedWhat} {t.howToRoute}
)} @@ -577,8 +576,6 @@ function headline(r: DryRunResult): string { return m.unavailable; case "intercepted": return m.intercepted; - case "passthrough": - return m.passedThrough; default: return m.noMatch; } diff --git a/src/routing/ProbesTab.i18n.ts b/src/routing/ProbesTab.i18n.ts index 709470e8..5b748465 100644 --- a/src/routing/ProbesTab.i18n.ts +++ b/src/routing/ProbesTab.i18n.ts @@ -6,23 +6,18 @@ export const probesTabText = messages( "客户端自动发起的辅助请求,不由用户操作触发,同样产生费用。这一层先过,剩下的才轮到规则。", intercept: "本地应答", interceptWhat: "由网关直接应答,不发送到上游,不产生费用。", - passthrough: "原样放行", - passthroughWhat: "作为普通请求转发,按上游计费方式产生费用。", - routed: "交给路由", - routedWhat: "按「路由」标签里的规则转发,按转发到的上游产生费用,可分流至费用更低的上游。", - ruleHint: "规则里的「辅助请求」条件只对设为「交给路由」的类别成立。", + forward: "转发", + forwardWhat: "和普通请求一样按路由规则转发,按转发到的上游产生费用。", + ruleHint: "规则可用「辅助请求」条件匹配设为转发的类别,例如分流至费用更低的上游。", }, { intro: "Auxiliary requests that clients send on their own, without any user action. They incur costs as well, and they pass through here before any rule is evaluated.", intercept: "Answer locally", interceptWhat: "The gateway answers directly. Nothing is sent upstream and no cost is incurred.", - passthrough: "Pass through", - passthroughWhat: "Forwarded as an ordinary request, with costs according to the upstream's billing.", - routed: "Use routing", - routedWhat: - "Forwarded by the rules under the Routes tab, with costs according to the upstream it goes to; the rules can send it to a lower-cost upstream.", + forward: "Forward", + forwardWhat: "Forwarded by the routing rules like any other request, with costs according to the upstream it goes to.", ruleHint: - "The “Auxiliary request” condition in a rule only holds for the kinds set to “Use routing”.", + "Rules can match the kinds set to “Forward” with the “Auxiliary request” condition, for example to send them to a lower-cost upstream.", }, ); diff --git a/src/routing/ProbesTab.tsx b/src/routing/ProbesTab.tsx index d086156e..388f9908 100644 --- a/src/routing/ProbesTab.tsx +++ b/src/routing/ProbesTab.tsx @@ -10,11 +10,11 @@ import type { Overview, ProbeMode } from "@/types"; import { probesTabText } from "./ProbesTab.i18n"; /** - * 客户端自己发的辅助请求,以及每一类怎么处理。 + * 客户端自己发的辅助请求,以及每一类怎么处理:本地应答,或者转发。 * - * **它属于路由,不属于网关。**三档里有一档就叫「交给路由」,而规则的 - * `intent` 条件只对那一档成立 —— 写规则的地方和决定它能不能命中的地方 - * 隔着一个导航项时,规则会静悄悄地永远不命中。 + * **它属于路由,不属于网关。**转发的那几类带着类别走规则,规则的 `intent` + * 条件只对它们成立 —— 写规则的地方和决定它能不能命中的地方隔着一个导航项 + * 时,规则会静悄悄地永远不命中。 * * 换档是一次配置写入:**先按新档画**,写入期间那一类旁边转圈、分段控件失效; * 失败时拨回原档并报错。成功不弹提示 —— 分段控件已经停在新档上了。 @@ -40,8 +40,7 @@ export function ProbesTab({ ov }: { ov: Overview }) { const modes: { id: ProbeMode; label: string; what: string }[] = [ { id: "intercept", label: t.intercept, what: t.interceptWhat }, - { id: "passthrough", label: t.passthrough, what: t.passthroughWhat }, - { id: "route", label: t.routed, what: t.routedWhat }, + { id: "forward", label: t.forward, what: t.forwardWhat }, ]; async function set(id: string, mode: ProbeMode) { diff --git a/src/routing/RouteDialog.tsx b/src/routing/RouteDialog.tsx index a620fd7a..f025febc 100644 --- a/src/routing/RouteDialog.tsx +++ b/src/routing/RouteDialog.tsx @@ -121,13 +121,6 @@ export function RouteDialog({ const [rules, setRules] = useState(() => source ? source.rules.map(draftFromView) : [{ ...blankRule(ALL_UPSTREAMS), name: t.catchAllName }], ); - /** - * 每条规则保存时要一并「交给路由」的辅助请求类别,按规则的 `key` 记。**保存那条规则时 - * 整个换掉**:重开之后取消了勾选、去掉了那个条件,都要算数;删掉的规则不算。以前这里 - * 只加不减,取消勾选之后保存路由,那几类照样被改成交给路由 —— 连通性检查、预热从 - * 网关本地应答变成发给付费的上游 - */ - const [probes, setProbes] = useState>(() => new Map()); /** * 保存时带的版本号:**草稿起步的那一版**,不是保存那一刻的。 * @@ -177,7 +170,6 @@ export function RouteDialog({ base_version: base, // 默认路由的使用者是「没指定路由的密钥」,这里不改 keys: isDefault ? undefined : keys, - route_probes: [...new Set(rules.flatMap((r) => probes.get(r.key) ?? []))], }; if (mode.kind === "edit") await api.updateRoute(mode.name, save); else await api.createRoute(save); @@ -202,9 +194,6 @@ export function RouteDialog({ onSelect: () => { const c = { ...copyDraft(rules[i]!), name: uniqueName(rt.copyName(rules[i]!.name), rules) }; setRules((r) => [...r.slice(0, i + 1), c, ...r.slice(i + 1)]); - // 复制出来的那条带着同样的条件,也带着「交给路由」的那个选择 - const chosen = probes.get(rules[i]!.key); - if (chosen) setProbes((m) => new Map(m).set(c.key, chosen)); }, }, { kind: "sep" }, @@ -460,13 +449,12 @@ export function RouteDialog({ onChanged(); }} onClose={() => setEditing(null)} - onSave={(d, routeProbes) => { + onSave={(d) => { setRules((r) => editing.index == null ? [...r.slice(0, editing.at), d, ...r.slice(editing.at)] : r.map((x, j) => (j === editing.index ? d : x)), ); - setProbes((m) => new Map(m).set(d.key, routeProbes)); setEditing(null); }} /> diff --git a/src/routing/RuleDialog.i18n.ts b/src/routing/RuleDialog.i18n.ts index 5b0b77c7..1f844ec5 100644 --- a/src/routing/RuleDialog.i18n.ts +++ b/src/routing/RuleDialog.i18n.ts @@ -8,8 +8,8 @@ export const ruleDialogText = messages( namePlaceholder: "例如 长上下文", conditions: "条件", allOf: "同时满足以下条件", - unrouted: (probes: string[]) => `${probes.join("、")}当前未设为交给路由,此条件不会满足。`, - routeProbes: "保存时将这些类别改为交给路由", + intercepted: (probes: string[]) => `${probes.join("、")}当前由网关本地应答,不会进入路由,此条件对其不成立。`, + toForward: "可在「辅助请求」中改为转发。", onMatch: "命中后", forward: "转发", forwardTo: "转发至", @@ -49,9 +49,9 @@ export const ruleDialogText = messages( namePlaceholder: "e.g. Long context", conditions: "Conditions", allOf: "All of the following must be met", - unrouted: (probes: string[]) => - `${andList(probes)} ${probes.length === 1 ? "is" : "are"} not set to be routed, so this condition is never met.`, - routeProbes: "Set these categories to be routed on save", + intercepted: (probes: string[]) => + `${andList(probes)} ${probes.length === 1 ? "is" : "are"} answered locally by the gateway and never ${probes.length === 1 ? "reaches" : "reach"} routing, so this condition never holds for ${probes.length === 1 ? "it" : "them"}.`, + toForward: "This can be changed to “Forward” under Auxiliary.", onMatch: "On match", forward: "Forward", forwardTo: "Forward to", diff --git a/src/routing/RuleDialog.tsx b/src/routing/RuleDialog.tsx index a66d9715..d6c37c6f 100644 --- a/src/routing/RuleDialog.tsx +++ b/src/routing/RuleDialog.tsx @@ -2,7 +2,6 @@ import { useMemo, useState, type ReactNode } from "react"; import { ChevronRightIcon, PlusIcon } from "lucide-react"; import { Banner } from "@/ui/banner"; import { Button } from "@/ui/button"; -import { Checkbox } from "@/ui/checkbox"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/ui/collapsible"; import { Dialog, @@ -19,7 +18,6 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/ui/dropdown-menu"; -import { Field, FieldLabel } from "@/ui/field"; import { Input } from "@/ui/input"; import { NativeSelect, NativeSelectOptGroup, NativeSelectOption } from "@/ui/native-select"; import { cn } from "@/lib/utils"; @@ -36,7 +34,6 @@ import { TargetIcon } from "./parts"; import { COND_FIELDS, DIALECTS, - PROBE_IDS, addOnsText, blankCondition, compareOps, @@ -88,8 +85,7 @@ export function RuleDialog({ /** 在这里新建了策略组:外面要重读概览,路由对话框接着用写完的版本 `version` */ onChanged: (version: string) => void; onClose: () => void; - /** `routeProbes`:保存路由时一并设为「交给路由」的辅助请求类别 */ - onSave: (rule: RuleDraft, routeProbes: string[]) => void; + onSave: (rule: RuleDraft) => void; }) { const t = useText(ruleDialogText); const rt = useText(routingText); @@ -99,21 +95,22 @@ export function RuleDialog({ const [rewriteOpen, setRewriteOpen] = useState( () => initial.model !== "" || initial.maxTokens !== "" || initial.thinking !== "keep", ); - const [routeProbes, setRouteProbes] = useState(null); const [newGroup, setNewGroup] = useState(false); const modelIds = useMemo(() => models.map((m) => m.id), [models]); const phaseTwo = isPhaseTwo(d); const problem = ruleProblem(d, takenNames); - // 辅助请求条件里还没交给路由的类别:不交给路由,这个条件永远不满足 + /** + * 辅助请求条件里点名的、设为本地应答的类别:它们到不了路由,这个条件对它们永远不成立。 + * 选了「任一辅助请求」就不提 —— 那是在说「转发的那几类里随便哪一类」,本地应答的连通性 + * 检查、预热本来就不在里面 + */ const intent = d.conditions.find((c) => c.field === "intent"); const anyProbe = intent?.values.includes("assistant_internal") ?? false; - const unrouted = (anyProbe ? PROBE_IDS : (intent?.values ?? [])).filter( - (id) => ov.client_probes.find((p) => p.id === id)?.mode !== "route", - ); - // 「任一辅助请求」时不默认勾选:连通性检查和预热原本由网关本地应答,改为交给路由会产生费用 - const routing = routeProbes ?? !anyProbe; + const intercepted = anyProbe + ? [] + : (intent?.values ?? []).filter((id) => ov.client_probes.find((p) => p.id === id)?.mode === "intercept"); const rewriteSummary = addOnsText(d); @@ -160,17 +157,13 @@ export function RuleDialog({ onRemove={() => set({ conditions: d.conditions.filter((_, j) => j !== i) })} /> ))} - 0} title={t.unrouted(unrouted.map(probeLabel))}> - - setRouteProbes(v === true)} - /> - - {t.routeProbes} - - + 0} + title={t.intercepted(intercepted.map(probeLabel))} + > + {t.toForward} c.field)} @@ -291,7 +284,7 @@ export function RuleDialog({ -