From 6fe6014d92fbd76d3904ebf4b01e7da0c21f048a Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Tue, 13 Oct 2026 13:42:57 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=B8=85=E7=90=86=20emoji=20=E4=B8=8E?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=8F=91=E5=B8=83=E8=84=9A=E6=9C=AC=E4=BB=93?= =?UTF-8?q?=E5=BA=93=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 产品代码统一为纯文本风格(照参考实现),去掉 emoji:billing/quota/usage 插件、 channels 的提示文案、WebUI 文案,以及 go 测试、docs、packaging 脚本中的 注意/OK/FAIL 等符号化写法。第三方目录(vendor、node_modules、.probe)不动。 另:packaging/upload-release.sh 的仓库名改为真实全名 JianFeeeee/ModelRouter → HeNanMU-EEA/ModelRouter(前者是重定向,POST/PUT 不跟随 会 404)。 --- deploy.sh | 28 +++++++++++----------- docs/plugins.md | 10 ++++---- internal/billing/wildcard_test.go | 2 +- internal/core/channels_loomy_test.go | 2 +- internal/core/channels_plugin_test.go | 2 +- internal/core/channels_qoder_test.go | 2 +- internal/core/channels_raccoon_test.go | 2 +- internal/core/channels_store_test.go | 10 ++++---- internal/core/channels_stress_test.go | 4 ++-- internal/core/channels_zcode_test.go | 2 +- internal/core/device_mutate_test.go | 2 +- internal/core/kernel_effect_test.go | 2 +- internal/gateway/billing_rules_api_test.go | 10 ++++---- internal/gateway/stats_period_test.go | 2 +- internal/gateway/ui/index.html | 4 ++-- internal/gateway/ui_plugin_test.go | 2 +- internal/lua/billing_test.go | 6 ++--- internal/lua/billing_ui_test.go | 4 ++-- internal/lua/fastvalue_test.go | 2 +- internal/lua/fire_parallel_test.go | 6 ++--- internal/lua/persist_test.go | 10 ++++---- internal/lua/plugin_unload_test.go | 2 +- internal/lua/plugins/billing.lua | 2 +- internal/lua/plugins/quota.lua | 2 +- internal/lua/plugins/usage.lua | 2 +- internal/lua/plugins_test.go | 2 +- internal/lua/secrets_crypto_test.go | 8 +++---- internal/scheduler/empty_response_test.go | 2 +- packaging/config.example.yaml | 2 +- packaging/core-dist.sh | 6 ++--- packaging/install-browser-stack.sh | 14 +++++------ packaging/upload-release.sh | 8 +++---- packaging/verify-dist.sh | 2 +- 33 files changed, 83 insertions(+), 83 deletions(-) diff --git a/deploy.sh b/deploy.sh index b908167..07009a1 100755 --- a/deploy.sh +++ b/deploy.sh @@ -122,14 +122,14 @@ backup_old_state() { log "备份旧二进制到 $BACKUP_BIN" if [[ -f "$TARGET_BIN" ]]; then cp -f "$TARGET_BIN" "$BACKUP_BIN" - log "✓ 旧二进制已备份 (sha256=$(sha256sum "$BACKUP_BIN" | awk '{print $1}' | cut -c1-16))" + log "OK 旧二进制已备份 (sha256=$(sha256sum "$BACKUP_BIN" | awk '{print $1}' | cut -c1-16))" else warn "旧二进制不存在,跳过备份(无回滚点)" fi if [[ -f "$TARGET_CONFIG" ]]; then cp -f "$TARGET_CONFIG" "$BACKUP_CONFIG" - log "✓ 旧配置已备份到 $BACKUP_CONFIG (sha256=$(sha256sum "$BACKUP_CONFIG" | awk '{print $1}' | cut -c1-16))" + log "OK 旧配置已备份到 $BACKUP_CONFIG (sha256=$(sha256sum "$BACKUP_CONFIG" | awk '{print $1}' | cut -c1-16))" else warn "线上配置不存在: $TARGET_CONFIG(首次部署?无配置回滚点)" fi @@ -175,9 +175,9 @@ build() { ) for c in "${checks[@]}"; do if strings "$NEW_BIN" | grep -qF "$c"; then - log " ✓ 新二进制包含: $c" + log " OK 新二进制包含: $c" else - warn " ⚠ 新二进制不包含: $c(非预期但非致命)" + warn " 注意: 新二进制不包含: $c(非预期但非致命)" fi done } @@ -207,7 +207,7 @@ atomic_replace_binary() { # 验证 NEW_SHA256=$(sha256sum "$TARGET_BIN" | awk '{print $1}') - log "✓ 二进制已原子替换 (sha256=${NEW_SHA256:0:16})" + log "OK 二进制已原子替换 (sha256=${NEW_SHA256:0:16})" } # ---------- 原子替换配置 ---------- @@ -223,7 +223,7 @@ atomic_replace_config() { log " rename → $TARGET_CONFIG (原子)" mv -f "$STAGING_CONFIG" "$TARGET_CONFIG" - log "✓ 配置已原子替换 (sha256=$(sha256sum "$TARGET_CONFIG" | awk '{print $1}' | cut -c1-16))" + log "OK 配置已原子替换 (sha256=$(sha256sum "$TARGET_CONFIG" | awk '{print $1}' | cut -c1-16))" } # ---------- 配置预检 ---------- @@ -236,7 +236,7 @@ verify_config() { local out if out=$("$TARGET_BIN" -check -config "$TARGET_CONFIG" 2>&1); then - log "✓ 配置合法" + log "OK 配置合法" return 0 fi @@ -276,7 +276,7 @@ verify_master_key() { log " 钥匙来源: LLMS_PROXY_MASTER_KEY(环境变量)" # 走真实解密路径验证,而不是只检查格式 if out=$("$TARGET_BIN" -show-secrets -config "$TARGET_CONFIG" 2>&1); then - log "✓ 凭据可解封($(echo "$out" | grep -cE '^(source|key) ') 项)" + log "OK 凭据可解封($(echo "$out" | grep -cE '^(source|key) ') 项)" return 0 fi err "凭据无法用 LLMS_PROXY_MASTER_KEY 解封,未替换任何文件:" @@ -292,7 +292,7 @@ verify_master_key() { log " 钥匙文件: $key_file ($(stat -c '%A' "$key_file" 2>/dev/null || echo '?'),$(wc -c < "$key_file") 字节)" if out=$("$TARGET_BIN" -show-secrets -config "$TARGET_CONFIG" 2>&1); then - log "✓ 凭据可解封($(echo "$out" | grep -cE '^(source|key) ') 项)" + log "OK 凭据可解封($(echo "$out" | grep -cE '^(source|key) ') 项)" return 0 fi err "凭据无法解密(钥匙与配置不匹配),未替换任何文件:" @@ -388,7 +388,7 @@ sync_adapters() { grep -qE '\buses\b' "$TARGET_ADAPTERS/$f.lua" 2>/dev/null && ((usage_fixed++)) || true done log " usage 透传修复适配器: ${usage_fixed}/7 (openai/deepseek/github/groq/kimicode/mistral/opencode)" - log "✓ 适配器已同步到 $TARGET_ADAPTERS/" + log "OK 适配器已同步到 $TARGET_ADAPTERS/" } # 同步内置插件到 plugin_dir。 @@ -449,14 +449,14 @@ sync_plugins() { local name; name="$(basename "$src")" [[ -f "$TARGET_PLUGINS/$name" ]] || warn " 缺少插件: $name" done - log "✓ 插件已同步到 $TARGET_PLUGINS/(新增 $added,更新 $updated,未变 $same)" + log "OK 插件已同步到 $TARGET_PLUGINS/(新增 $added,更新 $updated,未变 $same)" } # ---------- 重启服务 ---------- restart_service() { log "重启服务(中断 <500ms)" systemctl restart llmsproxy.service || fail "systemd restart 失败" - log "✓ llmsproxy.service restarted" + log "OK llmsproxy.service restarted" } # ---------- 健康检查 ---------- @@ -471,7 +471,7 @@ healthcheck() { local code code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$HEALTH_URL" 2>/dev/null || echo "000") if [[ "$code" == "200" || "$code" == "401" ]]; then - log "✓ 服务健康 (status=$code, ${attempt}/${attempts})" + log "OK 服务健康 (status=$code, ${attempt}/${attempts})" return 0 fi log " 等待中 ($attempt/$attempts, status=$code)" @@ -540,7 +540,7 @@ rollback() { local code code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$HEALTH_URL" 2>/dev/null || echo "000") if [[ "$code" == "200" || "$code" == "401" ]]; then - log "✓ 回滚成功,旧版本已恢复服务 (status=$code)" + log "OK 回滚成功,旧版本已恢复服务 (status=$code)" log " 新版本部署失败,请检查构建/配置后重试" return 0 else diff --git a/docs/plugins.md b/docs/plugins.md index dce8052..deaf260 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -46,7 +46,7 @@ plugin.ui = { page = { page_id = "hello", -- kebab-case title = "Hello", - icon = "👋", + icon = "", order = 90, -- 侧栏排序 mount = [[
hello
]], }, @@ -211,7 +211,7 @@ plugin.ui = { page = { page_id = "billing", -- 必填,kebab-case。侧栏 data-tab 与 #tab-billing title = "Billing", -- 必填,侧栏文字 - icon = "💰", -- 可选 + icon = "", -- 可选 order = 40, -- 侧栏排序,默认 100 mount = [[...HTML...]], }, @@ -428,7 +428,7 @@ end 因此插件持有**唯一一个**状态,所有入口(`Fire` / `State` / `SetState` / **定时任务**) 都用这同一把互斥锁串行化。 -⚠️ 这条约束对**定时任务尤其关键**:任务和钩子必须是同一个 VM,否则任务写的 +注意: 这条约束对**定时任务尤其关键**:任务和钩子必须是同一个 VM,否则任务写的 `plugin.state` 是一个请求路径永远读不到的副本 —— token 轮换看起来成功了, 对实际流量完全无效。 @@ -449,7 +449,7 @@ end 与适配器相同:`json.encode` / `json.decode`、`log(level, msg)`、 `hmac_sha256_hex` / `sha256_hex` / `base64_encode` / `tohex`。 -⚠️ `os.date` 在极简运行时里可能不可用(计费插件因此有降级路径)。 +注意: `os.date` 在极简运行时里可能不可用(计费插件因此有降级路径)。 ### 6.4 命名 @@ -536,7 +536,7 @@ curl -H "Authorization: Bearer $ADMIN_KEY" \ { "prices": { ... }, "count_failures": true } ``` -### 7.5 ⚠️ 计费插件只报表,不执法 +### 7.5 注意: 计费插件只报表,不执法 **网关自己的配额会计(`internal/gateway/stats.go`,入口处强制)才是限额权威。** 本插件不参与任何路由或配额决策。 diff --git a/internal/billing/wildcard_test.go b/internal/billing/wildcard_test.go index 246b47b..512bba6 100644 --- a/internal/billing/wildcard_test.go +++ b/internal/billing/wildcard_test.go @@ -47,7 +47,7 @@ func TestWildcardTokenRuleDoesNotMarkEverySourcePriced(t *testing.T) { // Paid sources must NOT be marked, or their unlisted models bill $0. for _, name := range []string{"paid1", "paid2"} { if _, ok := srcs[name]; ok { - t.Errorf("★ wildcard token rule marked %q as priced at 0 — its unlisted "+ + t.Errorf("wildcard token rule marked %q as priced at 0 — its unlisted "+ "models will bill $0 instead of appearing as unpriced", name) } } diff --git a/internal/core/channels_loomy_test.go b/internal/core/channels_loomy_test.go index f218e06..7ee15be 100644 --- a/internal/core/channels_loomy_test.go +++ b/internal/core/channels_loomy_test.go @@ -128,7 +128,7 @@ func TestLoomyWechatPollBehavior(t *testing.T) { // 第二拍 404 —— **关键**:已扫码但**不是**完成(早期读反会卡这里) st := h.pollOnce(sid) if st["state"] != "pending" { - t.Fatalf("★ 404(已扫码待确认) 必须是 pending 而非 completed, 实得 %v", st["state"]) + t.Fatalf("404(已扫码待确认) 必须是 pending 而非 completed, 实得 %v", st["state"]) } }) diff --git a/internal/core/channels_plugin_test.go b/internal/core/channels_plugin_test.go index 690eff6..3100b51 100644 --- a/internal/core/channels_plugin_test.go +++ b/internal/core/channels_plugin_test.go @@ -92,7 +92,7 @@ func TestChannelsPluginEndToEnd(t *testing.T) { } // 且不得出现在 state 里 if stateHasSecret(ps, "channels", "tok-1") { - t.Fatal("★ 凭据泄漏进了 plugin.state") + t.Fatal("凭据泄漏进了 plugin.state") } // 5. 运维授权后,重新接入应当真的把源注册进内核 diff --git a/internal/core/channels_qoder_test.go b/internal/core/channels_qoder_test.go index bd1584e..3b68f70 100644 --- a/internal/core/channels_qoder_test.go +++ b/internal/core/channels_qoder_test.go @@ -175,7 +175,7 @@ func TestQoderDevicePollBehavior(t *testing.T) { t.Fatalf("池路径应完成: %v", st) } if _, ok, _ := h.ps.GetSecret("channels", "channels:qoder:"+aid); !ok { - t.Fatalf("★ 池路径凭据未落到 channels:qoder:%s", aid) + t.Fatalf("池路径凭据未落到 channels:qoder:%s", aid) } }) } diff --git a/internal/core/channels_raccoon_test.go b/internal/core/channels_raccoon_test.go index 1cdb29a..198d45d 100644 --- a/internal/core/channels_raccoon_test.go +++ b/internal/core/channels_raccoon_test.go @@ -159,7 +159,7 @@ func TestRaccoonQrPollBehavior(t *testing.T) { _, sid := hh.begin() st := hh.pollOnce(sid) if st["state"] != "pending" { - t.Fatalf("★ 缺 token 的 success 必须 pending(否则产出空凭据并卡死), 实得 %v", st["state"]) + t.Fatalf("缺 token 的 success 必须 pending(否则产出空凭据并卡死), 实得 %v", st["state"]) } }) } diff --git a/internal/core/channels_store_test.go b/internal/core/channels_store_test.go index c428065..057f20a 100644 --- a/internal/core/channels_store_test.go +++ b/internal/core/channels_store_test.go @@ -53,7 +53,7 @@ func TestChannelsStoreIsPluginOwned(t *testing.T) { storePath := filepath.Join(dir, ".channels-store.json") b, err := os.ReadFile(storePath) if err != nil { - t.Fatalf("★ 插件自有存储未写出(%s): %v\n"+ + t.Fatalf("插件自有存储未写出(%s): %v\n"+ " —— 状态落盘必须由插件自己负责,不能依赖内核分发路径", storePath, err) } var saved struct { @@ -94,7 +94,7 @@ func TestChannelsStoreIsPluginOwned(t *testing.T) { } } if !found { - t.Fatalf("★ 重启后账号 %s 未恢复 —— 持久化未生效", aid) + t.Fatalf("重启后账号 %s 未恢复 —— 持久化未生效", aid) } } @@ -155,7 +155,7 @@ func TestChannelsMigratesLegacyKernelState(t *testing.T) { } } if !found { - t.Fatal("★ 遗留账号未迁移 —— 升级即丢账号") + t.Fatal("遗留账号未迁移 —— 升级即丢账号") } // ② 自有存储必须已写出 @@ -164,7 +164,7 @@ func TestChannelsMigratesLegacyKernelState(t *testing.T) { } // ③ 遗留文件必须被删除(否则内核 restore() 会用它覆盖自有存储) if _, err := os.Stat(legacyPath); !os.IsNotExist(err) { - t.Fatal("★ 遗留内核文件应被删除,否则下次启动会被它覆盖") + t.Fatal("遗留内核文件应被删除,否则下次启动会被它覆盖") } } @@ -213,7 +213,7 @@ func TestChannelsMigrationKeepsLegacyOnWriteFailure(t *testing.T) { // 写失败的态下,遗留文件必须原样保留 if _, err := os.Stat(legacyPath); err != nil { - t.Fatalf("★ 写自有存储失败时遗留文件被删了 —— 数据被一次迁移弄丢: %v", err) + t.Fatalf("写自有存储失败时遗留文件被删了 —— 数据被一次迁移弄丢: %v", err) } // 且自有存储不应存在(写失败了) if _, err := os.Stat(filepath.Join(sub, ".channels-store.json")); err == nil { diff --git a/internal/core/channels_stress_test.go b/internal/core/channels_stress_test.go index 8f32ab6..7f9f9eb 100644 --- a/internal/core/channels_stress_test.go +++ b/internal/core/channels_stress_test.go @@ -204,7 +204,7 @@ func TestLegacyVaultPlaintextLoad(t *testing.T) { t.Fatal(err) } if strings.Contains(string(raw), "PLAIN-OLD-TOKEN") { - t.Fatalf("★ 重封后旧明文仍在盘上:\n%s", raw) + t.Fatalf("重封后旧明文仍在盘上:\n%s", raw) } if !strings.Contains(string(raw), "enc:v1:") { t.Fatalf("重封后应含密文:\n%s", raw) @@ -252,7 +252,7 @@ func TestLegacySourceSealedConfig(t *testing.T) { t.Fatal(err) } if strings.Contains(string(raw), "PLAIN-LEGACY-KEY") { - t.Fatalf("★ 旧明文 api_key 升级后仍明文落盘:\n%s", raw) + t.Fatalf("旧明文 api_key 升级后仍明文落盘:\n%s", raw) } if !strings.Contains(string(raw), "enc:v1:") { t.Fatalf("落盘应含 enc:v1 密文:\n%s", raw) diff --git a/internal/core/channels_zcode_test.go b/internal/core/channels_zcode_test.go index f152707..d973a34 100644 --- a/internal/core/channels_zcode_test.go +++ b/internal/core/channels_zcode_test.go @@ -182,7 +182,7 @@ func TestZcodeInitPollBehavior(t *testing.T) { t.Fatalf("池路径应完成: %v", st) } if _, ok, _ := h.ps.GetSecret("channels", "channels:zcode:"+aid); !ok { - t.Fatalf("★ 池路径凭据未落到 channels:zcode:%s", aid) + t.Fatalf("池路径凭据未落到 channels:zcode:%s", aid) } }) } diff --git a/internal/core/device_mutate_test.go b/internal/core/device_mutate_test.go index 223e4d0..528e6d6 100644 --- a/internal/core/device_mutate_test.go +++ b/internal/core/device_mutate_test.go @@ -66,7 +66,7 @@ func TestDevicePollMutants(t *testing.T) { t.Fatal("基准决策表未全过 —— 测试自身失效") } if newDeviceHarness(t, mutated).runDecisionTable() { - t.Fatalf("★ 变异体幸存(%s)—— 该分支无覆盖", mu.name) + t.Fatalf("变异体幸存(%s)—— 该分支无覆盖", mu.name) } }) } diff --git a/internal/core/kernel_effect_test.go b/internal/core/kernel_effect_test.go index f873f69..c583e12 100644 --- a/internal/core/kernel_effect_test.go +++ b/internal/core/kernel_effect_test.go @@ -34,6 +34,6 @@ return plugin t.Fatalf("动作未成功: %#v", m) } if _, ok := c.FindKey("sk-kernel-0001"); ok { - t.Fatal("★ 动作报告成功但副作用没发生 —— dispatch 是空转") + t.Fatal("动作报告成功但副作用没发生 —— dispatch 是空转") } } diff --git a/internal/gateway/billing_rules_api_test.go b/internal/gateway/billing_rules_api_test.go index 2a4a88b..82cf1f0 100644 --- a/internal/gateway/billing_rules_api_test.go +++ b/internal/gateway/billing_rules_api_test.go @@ -67,7 +67,7 @@ func TestBillingRulesCRUD(t *testing.T) { t.Fatal(err) } if n := len(got.DSL.Profiles[0].Rules); n != 2 { - t.Fatalf("★ re-adding an existing url appended instead of replacing: %d rules", n) + t.Fatalf("re-adding an existing url appended instead of replacing: %d rules", n) } var mode string for _, r := range got.DSL.Profiles[0].Rules { @@ -105,7 +105,7 @@ func TestBillingRulesRejectUnknownURLWithWarning(t *testing.T) { t.Fatal(err) } if len(got.Warnings) == 0 { - t.Fatal("★ a rule matching no configured source was accepted with no warning — " + + t.Fatal("a rule matching no configured source was accepted with no warning — " + "it will price nothing and every request will be recorded as unpriced") } if !strings.Contains(got.Warnings[0], "typo.example.com") { @@ -136,7 +136,7 @@ func TestBillingRulesInvalidIsRejectedNotPersisted(t *testing.T) { t.Fatal(err) } if len(got.DSL.Profiles[0].Rules) != 1 { - t.Errorf("★ a rejected rule was persisted anyway: %d rules", len(got.DSL.Profiles[0].Rules)) + t.Errorf("a rejected rule was persisted anyway: %d rules", len(got.DSL.Profiles[0].Rules)) } // An unparseable price must be refused too, not silently priced at zero. if rr := rulesDo(t, g, http.MethodPost, `{"profile":"p","rule":{"url":"https://y/v1","mode":"token", @@ -175,7 +175,7 @@ func TestBillingRulesPersistAndReachThePlugin(t *testing.T) { t.Fatalf("read config: %v", err) } if !strings.Contains(string(raw), "commandcode.ai") { - t.Errorf("★ the rule is not in config.yaml on disk — an edit that does not "+ + t.Errorf("the rule is not in config.yaml on disk — an edit that does not "+ "survive a restart:\n%s", raw) } reloaded, err := config.Load(cfg.Path) @@ -226,7 +226,7 @@ func TestBillingRulesPersistAndReachThePlugin(t *testing.T) { } cc, ok := sources["commandcode"] if !ok { - t.Fatalf("★ the rule was saved but never injected: prices has %v, "+ + t.Fatalf("the rule was saved but never injected: prices has %v, "+ "no commandcode entry. Every request on it stays unpriced.", keysOf(sources)) } if cc == nil { diff --git a/internal/gateway/stats_period_test.go b/internal/gateway/stats_period_test.go index b04e74a..78b6a6b 100644 --- a/internal/gateway/stats_period_test.go +++ b/internal/gateway/stats_period_test.go @@ -241,7 +241,7 @@ func TestPeriodEmptyWindowFlagsTruncated(t *testing.T) { t.Errorf("empty window reqs = %d, want 0", got.Total.Reqs) } if !got.Truncated { - t.Error("★ an empty bounded window must be flagged truncated — otherwise a " + + t.Error("an empty bounded window must be flagged truncated — otherwise a " + "rotated-away week reads as 'no traffic that week'") } } diff --git a/internal/gateway/ui/index.html b/internal/gateway/ui/index.html index f84d2da..e4b0091 100644 --- a/internal/gateway/ui/index.html +++ b/internal/gateway/ui/index.html @@ -992,7 +992,7 @@ adPoolPeak: "峰值", adPoolSteps: "步长 (扩/缩)", cModel: "模型", - cImageModels: "🖼 图像模型(不可选,请到「生图」页测试)", + cImageModels: "图像模型(不可选,请到「生图」页测试)", urlHint: "填主机地址,通常不带 /v1 —— 网关会自动拼接路径。但各家约定不同:若填 https://x.com/v1,对话路径会正确,而图像生成会变成 /v1/v1/… 而 404。不确定就用上方「自动探测模型」,它会探测并填好正确形式。", navImage: "生图", imgWelcome: "输入提示词开始生成", @@ -1326,7 +1326,7 @@ adPoolPeak: "Peak", adPoolSteps: "Steps (grow/shrink)", cModel: "Model", - cImageModels: "🖼 Image models (not selectable — use the Image tab)", + cImageModels: "Image models (not selectable — use the Image tab)", urlHint: "Host address, usually WITHOUT /v1 — the gateway appends the path. Vendors differ: with https://x.com/v1 chat works but image generation becomes /v1/v1/… and 404s. If unsure use Auto-discover above; it probes and fills in the correct form.", navImage: "Image", imgWelcome: "Describe an image to generate it", diff --git a/internal/gateway/ui_plugin_test.go b/internal/gateway/ui_plugin_test.go index 8e258a8..a4efd91 100644 --- a/internal/gateway/ui_plugin_test.go +++ b/internal/gateway/ui_plugin_test.go @@ -344,7 +344,7 @@ func TestPluginPagesAreReachableByGoTab(t *testing.T) { body := ui[goTab : goTab+end] if !strings.Contains(body, "PLUGIN_PAGES.forEach") { - t.Error("★ goTab() does not toggle plugin pages — a plugin page keeps its " + + t.Error("goTab() does not toggle plugin pages — a plugin page keeps its " + "`hidden` class forever, so the sidebar entry does nothing") } diff --git a/internal/lua/billing_test.go b/internal/lua/billing_test.go index cfb018b..a679f5c 100644 --- a/internal/lua/billing_test.go +++ b/internal/lua/billing_test.go @@ -909,7 +909,7 @@ func TestBillingTracksCacheUsage(t *testing.T) { } // Only the first request reported a cache number. if total["cache_reported_reqs"] != float64(1) { - t.Errorf("★ total.cache_reported_reqs = %v, want 1 — a source that never "+ + t.Errorf("total.cache_reported_reqs = %v, want 1 — a source that never "+ "reports cache usage must be distinguishable from one reporting zero hits", total["cache_reported_reqs"]) } @@ -986,11 +986,11 @@ func TestBillingCacheHitClampedInStats(t *testing.T) { st := ps.State("billing").(map[string]interface{}) tot := st["total"].(map[string]interface{}) if tot["cache_hit_tokens"] != float64(100) { - t.Errorf("★ cache_hit_tokens = %v, want 100 (clamped to prompt_tokens)", + t.Errorf("cache_hit_tokens = %v, want 100 (clamped to prompt_tokens)", tot["cache_hit_tokens"]) } if tot["cache_fresh_tokens"] != float64(0) { - t.Errorf("★ cache_fresh_tokens = %v, want 0, never negative", + t.Errorf("cache_fresh_tokens = %v, want 0, never negative", tot["cache_fresh_tokens"]) } } diff --git a/internal/lua/billing_ui_test.go b/internal/lua/billing_ui_test.go index 8d11ad4..be44ee7 100644 --- a/internal/lua/billing_ui_test.go +++ b/internal/lua/billing_ui_test.go @@ -228,7 +228,7 @@ func TestBillingPageCoversItsData(t *testing.T) { } // TestPluginIconIsNotARawEmoji guards the sidebar icon. The billing plugin -// declared icon = "💰" and the WebUI drops that verbatim into the button, while +// declared icon = "" and the WebUI drops that verbatim into the button, while // every native tab uses an inline SVG styled with `stroke: currentColor`. An // emoji there renders at the wrong size and ignores the theme, so it does not // match its neighbours — which is what the operator reported. @@ -335,7 +335,7 @@ func TestBillingTableHeaderMatchesRowColumns(t *testing.T) { td := strings.Count(row, "") if th != td { - t.Errorf("★ header declares %d columns but row() emits %d cells — the "+ + t.Errorf("header declares %d columns but row() emits %d cells — the "+ "table is misaligned from the first differing column on", th, td) } } diff --git a/internal/lua/fastvalue_test.go b/internal/lua/fastvalue_test.go index 75d47f0..b82cda5 100644 --- a/internal/lua/fastvalue_test.go +++ b/internal/lua/fastvalue_test.go @@ -90,7 +90,7 @@ func TestFastPathIsActuallyUsed(t *testing.T) { "ts": float64(1700000000), } if _, ok := fastToPlain(realistic); !ok { - t.Errorf("★ the realistic request payload does NOT take the fast path — " + + t.Errorf("the realistic request payload does NOT take the fast path — " + "the JSON round-trip is still on the hot path for real traffic") } } diff --git a/internal/lua/fire_parallel_test.go b/internal/lua/fire_parallel_test.go index 7480a1e..7ad5fd7 100644 --- a/internal/lua/fire_parallel_test.go +++ b/internal/lua/fire_parallel_test.go @@ -79,7 +79,7 @@ return p psOut := ps.Fire(StageRequestEnd, map[string]interface{}{"model": "m"}) if singleOut["who"] != "from-aaa" { - t.Errorf("★ single-plugin path dropped the return value: %v", singleOut) + t.Errorf("single-plugin path dropped the return value: %v", singleOut) } // Two plugins both return `who`; the merge order decides the winner, and it // must be deterministic (load order), not scheduler-dependent. @@ -120,7 +120,7 @@ return p for i := 0; i < 60; i++ { got := ps.Fire(StageRequestEnd, map[string]interface{}{"model": "m"})["winner"] if got != first { - t.Fatalf("★ winner changed between runs: %q then %q — merge depends on goroutine "+ + t.Fatalf("winner changed between runs: %q then %q — merge depends on goroutine "+ "scheduling, so the same configuration behaves differently run to run", first, got) } } @@ -164,7 +164,7 @@ return p t.Fatal("healthy plugin has no state") } if n := st["n"]; n != float64(1) { - t.Errorf("★ healthy plugin did not run alongside the throwing one: n = %v", n) + t.Errorf("healthy plugin did not run alongside the throwing one: n = %v", n) } he := ps.HookErrors() if len(he) == 0 { diff --git a/internal/lua/persist_test.go b/internal/lua/persist_test.go index 4a08e17..5c60921 100644 --- a/internal/lua/persist_test.go +++ b/internal/lua/persist_test.go @@ -91,7 +91,7 @@ func TestStateSurvivesRestart(t *testing.T) { loadCounter(t, ps2) if got := stateN(t, ps2); got != 25 { - t.Fatalf("★ total after restart = %v, want 25 — this is the 'restart loses the books' bug", got) + t.Fatalf("total after restart = %v, want 25 — this is the 'restart loses the books' bug", got) } } @@ -160,7 +160,7 @@ func TestPricesOnlyUpdatePersists(t *testing.T) { t.Fatal(err) } if saved.Prices["rate"] != float64(42) { - t.Errorf("★ new price not persisted: %v — a restart would revert to the old price", saved.Prices) + t.Errorf("new price not persisted: %v — a restart would revert to the old price", saved.Prices) } if saved.State["n"] != float64(5) { t.Errorf("state was clobbered by a price change: %v", saved.State["n"]) @@ -189,7 +189,7 @@ return plugin } out := ps.Fire(StageRequestEnd, map[string]interface{}{"model": "m", "source": "s"}) if out["cost_usd"] != 1.25 { - t.Errorf("★ hook return value lost: payload = %v", out) + t.Errorf("hook return value lost: payload = %v", out) } if out["verdict"] != "billed" { t.Errorf("merged field lost: %v", out) @@ -269,7 +269,7 @@ func TestCloseFlushesTail(t *testing.T) { b, err := os.ReadFile(ps.stateFile("counter")) if err != nil { - t.Fatalf("★ Close() did not flush: %v — a shutdown loses the tail", err) + t.Fatalf("Close() did not flush: %v — a shutdown loses the tail", err) } var saved struct { State map[string]interface{} `json:"state"` @@ -325,7 +325,7 @@ return plugin b, err := os.ReadFile(ps.stateFile("ondemand")) if err != nil { - t.Fatalf("★ an exported call did not mark the plugin dirty: %v — a plugin "+ + t.Fatalf("an exported call did not mark the plugin dirty: %v — a plugin "+ "with no hooks can never persist its work", err) } var saved struct { diff --git a/internal/lua/plugin_unload_test.go b/internal/lua/plugin_unload_test.go index 9a03a1e..9773dd3 100644 --- a/internal/lua/plugin_unload_test.go +++ b/internal/lua/plugin_unload_test.go @@ -47,6 +47,6 @@ return plugin` time.Sleep(500 * time.Millisecond) if err := exec.Command("kill", "-0", pid).Run(); err == nil { exec.Command("kill", "-9", pid).Run() - t.Fatal("★ unload 后进程仍存活 —— on_unload 未被调用") + t.Fatal("unload 后进程仍存活 —— on_unload 未被调用") } } diff --git a/internal/lua/plugins/billing.lua b/internal/lua/plugins/billing.lua index c1afeea..20fde5e 100644 --- a/internal/lua/plugins/billing.lua +++ b/internal/lua/plugins/billing.lua @@ -260,7 +260,7 @@ end -- 语义:命中任一 window ⇒ 乘以 multiplier。hours 用 {起,止} 闭区间,跨零点 -- 用 {{22,24}} 表示 22:00-24:00(24 是"当天最后一刻")。 -- --- ★ 为什么用 os.date 的 ! 前缀取 UTC:provider 的费率表按 UTC 标注,而网关 +-- 关键:为什么用 os.date 的 ! 前缀取 UTC:provider 的费率表按 UTC 标注,而网关 -- 跑在本地时区(这台机是 Asia/Hong_Kong)。混用本地小时会让峰谷整体偏移 8 -- 小时,白天算成夜间——比不做峰谷还糟。 local function inPeakWindow(ev) diff --git a/internal/lua/plugins/quota.lua b/internal/lua/plugins/quota.lua index 796d1dc..912cc56 100644 --- a/internal/lua/plugins/quota.lua +++ b/internal/lua/plugins/quota.lua @@ -1177,7 +1177,7 @@ PLUGIN.ui = { '" style="width:'+Math.min(100,p)+'%">'+ '
$'+used.toFixed(2)+' / $'+cap.toFixed(0)+'
'; if(r.cap_mismatch){ - h+='
⚠ 额度口径可能有变
'; + h+='
注意 额度口径可能有变
'; } return h+""; } diff --git a/internal/lua/plugins/usage.lua b/internal/lua/plugins/usage.lua index 6193004..54e12c5 100644 --- a/internal/lua/plugins/usage.lua +++ b/internal/lua/plugins/usage.lua @@ -20,7 +20,7 @@ -- 取自己的 /api/stats —— 它已按调用 key 隔离(实测:user key 231 次 = 自己的, -- admin 33847 次 = 全量)。 -- --- ★ 必须传**掩码**,不能传完整 key。 +-- 关键:必须传**掩码**,不能传完整 key。 -- /api/stats 的统计 map 以 keyID(req.Key)(即 "***f9a9ed")为键 —— by_key -- 返回的就是它,面板传的也是它。而 admin 身份下 ?key= 接受任意值:传完整 key -- 会查不到,拿到零值。实测: diff --git a/internal/lua/plugins_test.go b/internal/lua/plugins_test.go index 7fdc0f4..24359ab 100644 --- a/internal/lua/plugins_test.go +++ b/internal/lua/plugins_test.go @@ -220,7 +220,7 @@ p.ui = { page = { page_id = "billing", title = "Billing", - icon = "💰", + icon = "", order = 50, mount = "
hi
", }, diff --git a/internal/lua/secrets_crypto_test.go b/internal/lua/secrets_crypto_test.go index 28fe838..5baea0a 100644 --- a/internal/lua/secrets_crypto_test.go +++ b/internal/lua/secrets_crypto_test.go @@ -98,7 +98,7 @@ func TestSecretVaultEncryptedAtRest(t *testing.T) { t.Fatal(err) } if strings.Contains(string(raw), "SUPER-TOKEN-123") { - t.Fatalf("★ 保险库明文落盘! 文件内容含 token:\n%s", raw) + t.Fatalf("保险库明文落盘! 文件内容含 token:\n%s", raw) } if !strings.Contains(string(raw), testEncPrefix) { t.Fatalf("落盘值应带 %s 前缀(统一密文形态):\n%s", testEncPrefix, raw) @@ -140,7 +140,7 @@ func TestSecretVaultPlaintextFallbackAndUpgrade(t *testing.T) { } raw, _ := os.ReadFile(filepath.Join(dir, ".secrets", "channels.json")) if strings.Contains(string(raw), "OLD-PLAIN") { - t.Fatalf("★ 升级写后旧明文值仍在盘上:\n%s", raw) + t.Fatalf("升级写后旧明文值仍在盘上:\n%s", raw) } var m map[string]string if err := json.Unmarshal(raw, &m); err != nil { @@ -169,7 +169,7 @@ func TestSecretVaultWrongMasterKey(t *testing.T) { // 换一把钥匙(模拟 master.key 丢失重建) ps.SetSecretBox(failBox{}) if _, _, err := ps.GetSecret("channels", "k"); err == nil { - t.Fatal("★ 换 master key 后读库未报错 —— 会静默吞凭据") + t.Fatal("换 master key 后读库未报错 —— 会静默吞凭据") } } @@ -205,7 +205,7 @@ func TestSecretVaultConcurrentPlugins(t *testing.T) { // 跨插件必须不可见 other := "plug" + string(rune('A'+(p+1)%plugins)) if _, ok, _ := ps.GetSecret(other, key); ok { - t.Errorf("★ 凭据串台: %s 的键在 %s 可见", name, other) + t.Errorf("凭据串台: %s 的键在 %s 可见", name, other) return } if g == 1 && i%10 == 9 { diff --git a/internal/scheduler/empty_response_test.go b/internal/scheduler/empty_response_test.go index b86369d..8fc90c9 100644 --- a/internal/scheduler/empty_response_test.go +++ b/internal/scheduler/empty_response_test.go @@ -32,7 +32,7 @@ func TestResultIsEmptyMatchesTheReasoningOnlyResponse(t *testing.T) { } // The same response WITH text is a normal answer. - if resultIsEmpty(&types.UnifiedResponse{Content: "Hi! 👋", ReasoningContent: "hmm"}) { + if resultIsEmpty(&types.UnifiedResponse{Content: "Hi! ", ReasoningContent: "hmm"}) { t.Error("a response with content is not empty") } // A tool-calling agent turn has no text and is perfectly valid. diff --git a/packaging/config.example.yaml b/packaging/config.example.yaml index f1ef9c6..ad9441b 100644 --- a/packaging/config.example.yaml +++ b/packaging/config.example.yaml @@ -3,7 +3,7 @@ # 这个是打包进发行包、供首次安装使用的模板。它故意不含任何可用的凭据: # 首次启动时程序会生成一个随机 admin key 并写入实际配置(WebUI「密钥」页可见)。 # -# ⚠ 不要在本文件里填一个写死的具体 key。示例文件是公开的,写进去的任何值 +# 注意: 不要在本文件里填一个写死的具体 key。示例文件是公开的,写进去的任何值 # 都会成为一个全网皆知的 admin key(旧版示例里的 sk-gw-local-0001 就踩过 # 这个坑:它被直接抄进生产配置并真实可用)。 diff --git a/packaging/core-dist.sh b/packaging/core-dist.sh index 3013480..5b4e26d 100755 --- a/packaging/core-dist.sh +++ b/packaging/core-dist.sh @@ -103,7 +103,7 @@ for f in "$DIST"/llmsproxy_*.deb "$DIST"/llmsproxy-*.rpm "$DIST"/*.tar.gz; do [ -f "$f" ] || continue sz=$(stat -c%s "$f") [ "$sz" -gt 100000 ] || { echo "[core-dist] FATAL: $f suspiciously small ($sz B)"; exit 1; } - log " ✓ $f ($((sz/1024)) KB)" + log " OK $f ($((sz/1024)) KB)" done # dpkg-deb takes ONE archive; every extra argument is read as an additional # control-component name. With the glob unquoted this used to expand to three @@ -133,7 +133,7 @@ for want in llmsproxy config.example.yaml llmsproxy.service plugins/billing.lua; echo " (release notes claim the billing plugin ships with the core)" exit 1 fi - log " ✓ archive contains $want" + log " OK archive contains $want" done # Directories are matched by their CONTENTS, not by name: tar written with # `tar -czf` from a populated tree lists "…/adapters/openai.lua" and never a bare @@ -144,6 +144,6 @@ if [ "$n_adapters" -lt 10 ]; then echo "[core-dist] FATAL: archive has only $n_adapters adapters (expected >= 10)" exit 1 fi -log " ✓ archive contains $n_adapters adapters" +log " OK archive contains $n_adapters adapters" log "done: $DIST" \ No newline at end of file diff --git a/packaging/install-browser-stack.sh b/packaging/install-browser-stack.sh index 7026a0a..601b0f5 100755 --- a/packaging/install-browser-stack.sh +++ b/packaging/install-browser-stack.sh @@ -53,7 +53,7 @@ if command -v python3 >/dev/null 2>&1; then fi if [ ${#missing[@]} -eq 0 ]; then - echo "✓ 浏览器栈依赖齐全" + echo "OK 浏览器栈依赖齐全" echo " chromium: ${chromium}" echo " noVNC: ${novnc}" exit 0 @@ -98,24 +98,24 @@ echo echo "复核安装结果:" ok=1 for b in "${BINS[@]}"; do - if command -v "$b" >/dev/null 2>&1; then echo " ✓ $b" - else echo " ✗ $b"; ok=0; fi + if command -v "$b" >/dev/null 2>&1; then echo " OK $b" + else echo " FAIL $b"; ok=0; fi done chromium="" for c in "${CHROMIUM_CANDIDATES[@]}"; do if command -v "$c" >/dev/null 2>&1; then chromium="$c"; break; fi done -[ -n "$chromium" ] && echo " ✓ $chromium" || { echo " ✗ chromium"; ok=0; } +[ -n "$chromium" ] && echo " OK $chromium" || { echo " FAIL chromium"; ok=0; } novnc="" for d in "${NOVNC_DIRS[@]}"; do [ -f "$d/vnc.html" ] && { novnc="$d"; break; } done -[ -n "$novnc" ] && echo " ✓ noVNC ($novnc)" || { echo " ✗ noVNC"; ok=0; } -python3 -c 'import websocket' >/dev/null 2>&1 && echo " ✓ python3 websocket-client" || { echo " ✗ python3 websocket-client"; ok=0; } +[ -n "$novnc" ] && echo " OK noVNC ($novnc)" || { echo " FAIL noVNC"; ok=0; } +python3 -c 'import websocket' >/dev/null 2>&1 && echo " OK python3 websocket-client" || { echo " FAIL python3 websocket-client"; ok=0; } if [ "$ok" = 1 ]; then echo - echo "✓ 浏览器栈就绪。重启 llmsproxy 后,flow=vnc 的平台即可用 VNC 兜底登录。" + echo "OK 浏览器栈就绪。重启 llmsproxy 后,flow=vnc 的平台即可用 VNC 兜底登录。" else echo echo "WARN: 仍有缺失项,VNC 兜底在该机器上不可用。插件的 deps/status 端点会一样报告。" >&2 diff --git a/packaging/upload-release.sh b/packaging/upload-release.sh index 1895789..413b97b 100755 --- a/packaging/upload-release.sh +++ b/packaging/upload-release.sh @@ -20,7 +20,7 @@ set -uo pipefail TOKEN="$(tr -d '\n\r' < /tmp/gc_token)" -REPO="JianFeeeee/ModelRouter" +REPO="HeNanMU-EEA/ModelRouter" TAG="${1:?usage: upload-release.sh ...}" shift @@ -43,7 +43,7 @@ try: except Exception: print("")' 2>/dev/null)" if [ -z "$url" ]; then - echo "✗ 取得上传地址失败: ${resp:0:140}" + echo "FAIL 取得上传地址失败: ${resp:0:140}" continue fi @@ -63,9 +63,9 @@ print(subprocess.run(args, capture_output=True, text=True).stdout) ' "$f" "$url" 2>/dev/null)" if [ "$code" = "200" ] || [ "$code" = "201" ] || [ "$code" = "204" ]; then - echo "✓ HTTP=$code" + echo "OK HTTP=$code" else - echo "✗ HTTP=$code" + echo "FAIL HTTP=$code" fi sleep 1 done diff --git a/packaging/verify-dist.sh b/packaging/verify-dist.sh index 9e1ef8b..a90796a 100755 --- a/packaging/verify-dist.sh +++ b/packaging/verify-dist.sh @@ -43,7 +43,7 @@ for entry in "${checks[@]}"; do if [ "$sz" -lt "$min" ]; then fail "$(basename "$match") is only $sz bytes (min $min) — likely a degenerate shell like the 1.3.0 264KB exe" fi - log " ✓ $(basename "$match") ($((sz/1024)) KB)" + log " OK $(basename "$match") ($((sz/1024)) KB)" done log "all artifacts healthy"