diff --git a/.gitignore b/.gitignore index 905ab5c..538d9b0 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ __pycache__/ .paddlex_cache/ .paddlex_cache_cpu/ .paddlex_cache_gpu/ +# Local OCR model weights (downloaded by paddle, kept locally only) +official_models/ # Runtime output logs/ diff --git a/README.md b/README.md index 1982bb4..eced2f7 100644 --- a/README.md +++ b/README.md @@ -101,17 +101,11 @@ Greasy Fork 和仓库根目录的 `glm-coding-helper.user.js` 都是给普通用 ### 4. 启动后端 -如果下载的是自带环境包: - ```text -start-backend.cmd +start-backend-pipeline-gui.cmd ``` -如果下载的是在线安装包: - -```text -one-click-start.cmd -``` +首次使用如果环境没装好,会弹 PowerShell 提示,按提示输入 `1` 让它自动 `pip install`,或者先双击 `one-click-start.cmd` 装好环境再启动。 后端启动后默认监听: @@ -136,7 +130,7 @@ https://www.bigmodel.cn/glm-coding > **建议**:默认开 **2 个窗口**,先把流程跑稳。多窗口不等于高成功率,反而可能让账号被 RPM 风控盯上,整轮全废。 1. 先安装好油猴插件,配置好油猴脚本。使用 Chrome 时要在扩展页面开启开发者模式,然后找到 Tampermonkey 详情,把“允许用户脚本”“在无痕模式下启用”“允许访问文件网址”按需打开。 -2. 下载并解压 Release 包,双击 `start-backend.cmd` 或 `one-click-start.cmd` 启动本地后端。 +2. 下载并解压 Release 包,双击 `start-backend-pipeline-gui.cmd` 启动本地后端。 3. 打开 GLM Coding 页面测试脚本是否正常,脚本会自动补上内置优惠入口。 4. 每天 9 点 30 分前进入抢购页面准备,晚了可能就打不开了。提前准备好手机支付宝付款。 5. 多开几个窗口,等快到 10 点的时候点击好验证码但不要确定,等 10 点一到再按确定。**默认推荐 2 个窗口**(脚本弹窗默认值已从 3 改为 2,上限仍为 10,按需选择)。窗口开得越多,请求数量按窗口数放大,撞 RPM 上限的概率越高,近期已有大量高并发脚本因此全轮失败。 @@ -155,6 +149,7 @@ https://www.bigmodel.cn/glm-coding - 遇到真正有金额的支付二维码,请自行确认后再扫码支付。 - 多窗口并发不是越多越好。**2026-06 起智谱升级了 RPM 风控**,市面上高并发(多窗口批量请求)+ 屯码(预刷腾讯验证码 ticket 缓存复用)的同类脚本近期已**大面积失效**。窗口开得越多、请求越密集,越容易撞 RPM 上限,整轮秒杀全部返回 555/429。本项目走单窗口单发 + 实时 OCR 路线,相对安全。脚本默认 2 个窗口、上限 10 个,由用户按需选择;窗口开得越多,自身账号被风控的概率越高,请知悉。 - 抢购是否成功受库存、限流、账号状态、支付速度等因素影响,脚本不能保证一定抢到。 +- **如果之前抢过且账号被风控盯上,强烈建议试试 Chrome / Edge 的"无痕模式"窗口**(`Ctrl+Shift+N`)。无痕窗口没有历史 Cookie / 缓存 / Service Worker / 本地存储,可能消除隐形的风控标记。Tampermonkey 需在扩展详情页允许在无痕模式中启用(见上文)。注意:无痕窗口关掉就丢失所有数据,配置靠 `GM_setValue` 是同步到 Tampermonkey 内部的,正常保留。 油猴菜单里可以打开配置面板、一键多开窗口、清除今日套餐状态缓存。 @@ -202,24 +197,38 @@ https://www.bigmodel.cn/glm-coding 启动(任选其一): ```powershell -# 方式 1:双击 start-backend-pipeline.cmd(推荐 Windows 用户) -# 方式 2:命令行 -pwsh start-backend-pipeline.ps1 -# 方式 3:手动 +# 方式 1:双击 start-backend-pipeline-gui.cmd(推荐 Windows 用户,弹 GUI 窗口) +# 方式 2:命令行手动 +pwsh start-backend-pipeline-gui.ps1 +# 方式 3:直接跑后端 python backend/server.py ``` 双击启动器会自动检测 venv(`venv/` 或 `.venv_paddle/`)、检查依赖(fastapi/uvicorn/psutil)、缺失时自动 pip install;端口被占用时会显示中文提示(含 PID/进程名/命令行),杀进程前需用户确认。 +### 可视化 GUI 启动器 + +如果想在窗口里实时看后端状态(worker 就绪进度、最近识别结果、stdout 日志),用 GUI 启动器: + +```text +start-backend-pipeline-gui.cmd +``` + +`backend/gui.py` 会拉起 `backend.server` 子进程并接管其 stdout,弹出 Tk 窗口: + +- **顶部状态栏**:系统状态(启动中 / 运行中)、YOLO/OCR worker 数、监听地址 +- **中间识别列表**:最近 20 条识别结果(提示字、预测字、置信度、yolo/ocr 耗时) +- **底部日志框**:后端 stdout 实时滚动,`worker ready` / `[architect]` / 错误高亮 + +关闭窗口时 GUI 会自动 `terminate` 后端子进程,不用手动到任务管理器杀。 + ## 常用文件 | 文件 | 用途 | | --- | --- | | `glm-coding-helper.user.js` | 给 Tampermonkey 安装的主脚本 | -| `start-backend.cmd` | 启动已有本地后端环境(旧版单进程) | -| `start-backend-pipeline.cmd` | 双击启动 pipeline 后端(v8.20+ 推荐) | -| `one-click-start.cmd` | 自动安装环境并启动 | -| `install-env.cmd` | 手动安装 CPU 后端环境 | +| `one-click-start.cmd` | 首次安装环境(CPU 依赖) | +| `start-backend-pipeline-gui.cmd` | 日常启动 pipeline 后端 + 弹 Tk 可视化窗口 | | `scripts/` | 后端和打包脚本 | | `backend/` | Pipeline 后端(FastAPI + 多进程 YOLO→OCR) | | `models/` | 本地识别模型 | diff --git a/backend/gui.py b/backend/gui.py new file mode 100644 index 0000000..0dc5558 --- /dev/null +++ b/backend/gui.py @@ -0,0 +1,312 @@ +""" +Pipeline Backend GUI - Tk 监控面板 + +启动 backend.server 子进程,捕获 stdout 写入日志框; +定期拉取 /health 和 /recent,实时显示在状态栏和识别列表里。 +""" +import os +import sys +import json +import queue +import threading +import subprocess +import urllib.request +import urllib.error +from datetime import datetime +from pathlib import Path +from collections import deque +import tkinter as tk +from tkinter import ttk + +if getattr(sys, "frozen", False): + ROOT = Path(sys._MEIPASS) +else: + ROOT = Path(__file__).resolve().parent.parent +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +# ── 配置 ─────────────────────────────────────────────── +BACKEND_HOST = "127.0.0.1" +BACKEND_PORT = 8888 +BACKEND_URL = f"http://{BACKEND_HOST}:{BACKEND_PORT}" +POLL_HEALTH_MS = 1000 +POLL_RECENT_MS = 500 +MAX_LOG_LINES = 500 +MAX_RECENT_SHOWN = 20 + +# 颜色 +BG = "#f0f2f5" +FG_NORMAL = "#262626" +FG_SUCCESS = "#52c41a" +FG_WARN = "#faad14" +FG_ERROR = "#ff4d4f" +FG_INFO = "#1890ff" +FG_GREY = "#8c8c8c" + +state = { + "backend_proc": None, + "log_queue": queue.Queue(), + "log_lines": deque(maxlen=MAX_LOG_LINES), + "recent_results": deque(maxlen=MAX_RECENT_SHOWN), + "last_seen_req_id": 0, + "health": {"status": "starting", "ready_workers": 0, "alive_workers": 0, + "workers": 0, "n_yolo": 0, "n_ocr": 0, "port": BACKEND_PORT}, + "port": BACKEND_PORT, +} + + +def _read_proc_stdout(proc: subprocess.Popen): + """后台线程:读子进程 stdout/stderr,写入 log_queue""" + for stream in (proc.stdout, proc.stderr): + if stream is None: + continue + try: + for line in iter(stream.readline, b""): + try: + text = line.decode("utf-8", errors="replace").rstrip("\r\n") + except Exception: + text = str(line) + if text: + state["log_queue"].put(text) + except Exception: + pass + + +def _http_get_json(path: str, timeout: float = 1.0): + try: + with urllib.request.urlopen(BACKEND_URL + path, timeout=timeout) as r: + return json.loads(r.read().decode("utf-8")) + except (urllib.error.URLError, ConnectionError, OSError, json.JSONDecodeError): + return None + + +def _format_ts(ts: float) -> str: + return datetime.fromtimestamp(ts).strftime("%H:%M:%S") + + +class App: + def __init__(self, root: tk.Tk): + self.root = root + self.root.title("GLM Coding Captcha - Pipeline Backend") + self.root.geometry("720x600") + self.root.configure(bg=BG) + self.root.minsize(640, 480) + + style = ttk.Style() + try: + style.theme_use("clam") + except tk.TclError: + pass + style.configure("TLabel", background=BG, font=("Microsoft YaHei UI", 10)) + style.configure("Status.TLabel", font=("Microsoft YaHei UI", 12, "bold")) + style.configure("Big.TLabel", font=("Microsoft YaHei UI", 14, "bold")) + style.configure("Ok.TLabel", foreground=FG_SUCCESS, font=("Microsoft YaHei UI", 11, "bold")) + style.configure("Warn.TLabel", foreground=FG_WARN, font=("Microsoft YaHei UI", 11, "bold")) + style.configure("Err.TLabel", foreground=FG_ERROR, font=("Microsoft YaHei UI", 11, "bold")) + style.configure("Info.TLabel", foreground=FG_INFO, font=("Microsoft YaHei UI", 11, "bold")) + + self._build_ui() + self.root.protocol("WM_DELETE_WINDOW", self.on_close) + self._start_backend() + self.root.after(100, self._poll_logs) + self.root.after(POLL_HEALTH_MS, self._poll_health) + self.root.after(POLL_RECENT_MS, self._poll_recent) + + def _build_ui(self): + # 顶部状态栏 + top = ttk.Frame(self.root, padding="12 10") + top.pack(fill=tk.X) + + ttk.Label(top, text="智谱 GLM 验证码后端 (Pipeline)", style="Big.TLabel").grid( + row=0, column=0, columnspan=4, sticky=tk.W, pady=(0, 8)) + + # 第一行:系统状态 + ttk.Label(top, text="系统状态:").grid(row=1, column=0, sticky=tk.W, pady=2) + self.lbl_status = ttk.Label(top, text="启动中…", style="Warn.TLabel") + self.lbl_status.grid(row=1, column=1, sticky=tk.W, pady=2) + + # 第二行:worker 就绪 + ttk.Label(top, text="Workers:").grid(row=2, column=0, sticky=tk.W, pady=2) + self.lbl_workers = ttk.Label(top, text="0/0") + self.lbl_workers.grid(row=2, column=1, sticky=tk.W, pady=2) + ttk.Label(top, text="YOLO / OCR:").grid(row=2, column=2, sticky=tk.W, padx=(20, 4), pady=2) + self.lbl_pipeline = ttk.Label(top, text="-/-") + self.lbl_pipeline.grid(row=2, column=3, sticky=tk.W, pady=2) + + # 第三行:端口 / 地址 + ttk.Label(top, text="监听:").grid(row=3, column=0, sticky=tk.W, pady=2) + self.lbl_url = ttk.Label(top, text=f"{BACKEND_URL}", style="Info.TLabel") + self.lbl_url.grid(row=3, column=1, columnspan=3, sticky=tk.W, pady=2) + + # 中间:最近识别结果 + mid = ttk.LabelFrame(self.root, text="最近识别结果(最新在上)", padding="8") + mid.pack(fill=tk.BOTH, expand=True, padx=12, pady=(4, 4)) + + cols = ("time", "prompt", "pred", "conf", "ms", "yolo", "ocr", "req") + self.tree = ttk.Treeview(mid, columns=cols, show="headings", height=8) + for col, w, anchor in [ + ("time", 70, tk.W), ("prompt", 90, tk.W), ("pred", 110, tk.W), + ("conf", 60, tk.E), ("ms", 60, tk.E), ("yolo", 60, tk.E), + ("ocr", 60, tk.E), ("req", 60, tk.E), + ]: + self.tree.heading(col, text=col.upper()) + self.tree.column(col, width=w, anchor=anchor) + self.tree.tag_configure("ok", foreground=FG_SUCCESS) + self.tree.tag_configure("err", foreground=FG_ERROR) + self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + sb = ttk.Scrollbar(mid, orient=tk.VERTICAL, command=self.tree.yview) + self.tree.configure(yscrollcommand=sb.set) + sb.pack(side=tk.RIGHT, fill=tk.Y) + + # 底部:实时日志 + bot = ttk.LabelFrame(self.root, text="后端日志(stdout)", padding="6") + bot.pack(fill=tk.BOTH, expand=False, padx=12, pady=(0, 8)) + self.log_box = tk.Text(bot, height=10, font=("Consolas", 9), + bg="#1e1e1e", fg="#d4d4d4", insertbackground="#d4d4d4", + relief=tk.FLAT, wrap=tk.NONE) + self.log_box.tag_configure("info", foreground="#d4d4d4") + self.log_box.tag_configure("ready", foreground=FG_SUCCESS) + self.log_box.tag_configure("warn", foreground=FG_WARN) + self.log_box.tag_configure("err", foreground=FG_ERROR) + self.log_box.tag_configure("ts", foreground=FG_GREY) + self.log_box.configure(state=tk.DISABLED) + self.log_box.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + log_sb = ttk.Scrollbar(bot, orient=tk.VERTICAL, command=self.log_box.yview) + self.log_box.configure(yscrollcommand=log_sb.set) + log_sb.pack(side=tk.RIGHT, fill=tk.Y) + + def _append_log(self, line: str): + state["log_lines"].append(line) + ts = datetime.now().strftime("%H:%M:%S") + # 着色:[architect] 蓝;worker ready 绿;含 ERROR/Exception/失败 红;含 WARN/⚠ 黄 + tag = "info" + low = line.lower() + if "worker ready" in low or "✓" in line or "就绪" in line or "warmed" in low: + tag = "ready" + elif "error" in low or "exception" in low or "traceback" in low or "fail" in low or "err:" in low: + tag = "err" + elif "warn" in low or "⚠" in line or "warning" in low: + tag = "warn" + elif line.startswith("[architect]"): + tag = "info" + + self.log_box.configure(state=tk.NORMAL) + self.log_box.insert(tk.END, f"[{ts}] ", "ts") + self.log_box.insert(tk.END, line + "\n", tag) + # 裁剪到 MAX_LOG_LINES + line_count = int(self.log_box.index("end-1c").split(".")[0]) + if line_count > MAX_LOG_LINES: + self.log_box.delete("1.0", f"{line_count - MAX_LOG_LINES}.0") + self.log_box.see(tk.END) + self.log_box.configure(state=tk.DISABLED) + + def _start_backend(self): + """拉起 backend.server 子进程""" + env = os.environ.copy() + env["PYTHONIOENCODING"] = "utf-8" + env["PYTHONUTF8"] = "1" + cmd = [sys.executable, "-m", "backend.server"] + self._append_log(f"$ {sys.executable} -m backend.server") + try: + proc = subprocess.Popen( + cmd, cwd=str(ROOT), env=env, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + bufsize=1, + ) + state["backend_proc"] = proc + threading.Thread(target=_read_proc_stdout, args=(proc,), + daemon=True).start() + except Exception as e: + self._append_log(f"FATAL: 启动后端失败: {e}") + + def _poll_logs(self): + try: + while True: + line = state["log_queue"].get_nowait() + self._append_log(line) + except queue.Empty: + pass + self.root.after(100, self._poll_logs) + + def _poll_health(self): + data = _http_get_json("/health") + if data: + state["health"] = data + self._update_health_display(data) + else: + # 后端还没起来 + self.lbl_status.config(text="等待后端…", style="Warn.TLabel") + self.root.after(POLL_HEALTH_MS, self._poll_health) + + def _update_health_display(self, h: dict): + status = h.get("status", "starting") + ready = h.get("ready_workers", 0) + total = h.get("workers", 0) + alive = h.get("alive_workers", 0) + n_yolo = h.get("n_yolo", 0) + n_ocr = h.get("n_ocr", 0) + + if status == "ok" and alive == total: + self.lbl_status.config(text="● 运行中", style="Ok.TLabel") + elif status == "starting": + self.lbl_status.config(text="● 启动中", style="Warn.TLabel") + else: + self.lbl_status.config(text=f"● {status}", style="Warn.TLabel") + + self.lbl_workers.config(text=f"{ready}/{total} (alive={alive})") + self.lbl_pipeline.config(text=f"{n_yolo} YOLO / {n_ocr} OCR") + + def _poll_recent(self): + data = _http_get_json(f"/recent?limit={MAX_RECENT_SHOWN}") + if data and "results" in data: + self._update_recent_display(data["results"]) + self.root.after(POLL_RECENT_MS, self._poll_recent) + + def _update_recent_display(self, results: list): + # 清空并重绘(识别结果最多 20 条,开销可接受) + for iid in self.tree.get_children(): + self.tree.delete(iid) + for item in results: + ts = _format_ts(item.get("ts", 0)) + if item.get("success") is False: + self.tree.insert("", tk.END, values=( + ts, "-", f"ERR: {item.get('error','?')}", "-", "-", "-", "-", + item.get("req_id", "-") + ), tags=("err",)) + continue + prompt = "".join(item.get("prompt", [])) + pred = item.get("pred_text", "") + conf = f"{item.get('confidence', 0):.2f}" + ms = f"{item.get('elapsed_ms', 0):.0f}" + yolo = f"{item.get('yolo_ms', 0):.0f}" + ocr = f"{item.get('ocr_ms', 0):.0f}" + req = item.get("req_id", "-") + tag = "ok" if prompt and pred and prompt == pred else ("err" if pred.startswith("ERR") else "ok") + self.tree.insert("", tk.END, values=( + ts, prompt, pred, conf, ms, yolo, ocr, req + ), tags=(tag,)) + + def on_close(self): + proc = state.get("backend_proc") + if proc and proc.poll() is None: + try: + proc.terminate() + try: + proc.wait(timeout=3) + except subprocess.TimeoutExpired: + proc.kill() + except Exception: + pass + self.root.destroy() + + +def main(): + root = tk.Tk() + App(root) + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/backend/server.py b/backend/server.py index 6d20976..5c3cb40 100644 --- a/backend/server.py +++ b/backend/server.py @@ -89,6 +89,10 @@ ready_count = 0 ready_count_lock = threading.Lock() _shutdown = threading.Event() +# ── 最近识别结果 ring buffer(供 GUI 拉取)────────────────────── +from collections import deque +_recent_results: "deque[dict]" = deque(maxlen=20) + @asynccontextmanager async def lifespan(app: FastAPI): @@ -173,6 +177,7 @@ async def lifespan(app: FastAPI): def result_listener_thread(): + import time as _t while True: res = res_queue.get() if not res: @@ -180,6 +185,26 @@ def result_listener_thread(): req_id = res.get("req_id") with request_lock: future = pending_requests.pop(req_id, None) + # 写入最近识别结果(脱敏,只保留 GUI 需要的字段) + if res.get("success"): + snapshot = { + "ts": _t.time(), + "prompt": res.get("prompt", []), + "pred_text": res.get("pred_text", ""), + "confidence": res.get("confidence", 0.0), + "elapsed_ms": res.get("elapsed_ms", 0.0), + "yolo_ms": res.get("yolo_ms", 0.0), + "ocr_ms": res.get("ocr_ms", 0.0), + "req_id": req_id, + } + else: + snapshot = { + "ts": _t.time(), + "success": False, + "error": res.get("error", "unknown"), + "req_id": req_id, + } + _recent_results.append(snapshot) if future and not future.done(): future.get_loop().call_soon_threadsafe(future.set_result, res) @@ -222,7 +247,25 @@ async def health(): alive = sum(1 for p in workers_list if p.is_alive()) if workers_list else 0 total = N_YOLO + N_OCR status = "ok" if r >= total else "starting" - return {"status": status, "workers": total, "ready_workers": r, "alive_workers": alive} + return { + "status": status, + "workers": total, + "ready_workers": r, + "alive_workers": alive, + "n_yolo": N_YOLO, + "n_ocr": N_OCR, + "port": PORT, + } + + +@app.get("/recent") +async def recent_results(limit: int = 20): + """返回最近 N 条识别结果,供 GUI 轮询拉取""" + limit = max(1, min(20, limit)) + items = list(_recent_results)[-limit:] + # 反转,最新的在前 + items.reverse() + return {"count": len(items), "results": items} @app.post("/direct") diff --git a/install-env.cmd b/install-env.cmd deleted file mode 100644 index 1e0c3b9..0000000 --- a/install-env.cmd +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -chcp 65001 >nul -cd /d "%~dp0" -echo Installing GLM Coding Helper backend environment... -powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\setup_backend.ps1" -Target cpu -pause diff --git a/scripts/one_click_start.ps1 b/scripts/one_click_start.ps1 index 419cd87..ca44b31 100644 --- a/scripts/one_click_start.ps1 +++ b/scripts/one_click_start.ps1 @@ -74,7 +74,7 @@ if (-not $Ready) { } if (-not $Ready) { Write-Host "[FAIL] Backend environment repair failed. Required deps still missing." -ForegroundColor Red - Write-Host " Try running install-env.cmd manually." -ForegroundColor Red + Write-Host " Try running one-click-start.cmd manually." -ForegroundColor Red Read-Host "Press Enter to exit" exit 1 } @@ -93,7 +93,7 @@ if ($Target -eq "auto" -and $InstallTarget -eq "gpu" -and -not (Test-PythonImpor # ── 检查 pipeline 后端依赖(非阻塞,仅提示)───────────────── $PipelineDepsOk = Test-PythonImports $SelectedPython "import fastapi, uvicorn, psutil" if (-not $PipelineDepsOk) { - Write-Host "[INFO] Pipeline backend deps (fastapi/uvicorn/psutil) not installed. Run install-env.cmd to add them." -ForegroundColor Yellow + Write-Host "[INFO] Pipeline backend deps (fastapi/uvicorn/psutil) not installed. Run start-backend-pipeline-gui.cmd to add them." -ForegroundColor Yellow } Write-Host "Starting backend in $StartMode mode on port $Port..." diff --git a/scripts/release/build_portable.ps1 b/scripts/release/build_portable.ps1 index f23c933..1dc2701 100644 --- a/scripts/release/build_portable.ps1 +++ b/scripts/release/build_portable.ps1 @@ -12,13 +12,13 @@ $env:PYTHONIOENCODING = "utf-8" $VenvPython = Join-Path $Root ".venv_paddle\Scripts\python.exe" if (-not (Test-Path $VenvPython)) { - throw "Missing .venv_paddle. Run install-env.cmd first." + throw "Missing .venv_paddle. Run one-click-start.cmd first." } Write-Host "Checking portable CPU environment..." & $VenvPython -c "import ultralytics, paddleocr, paddlex, cv2, PIL, numpy; print('portable imports ok')" if ($LASTEXITCODE -ne 0) { - throw "The local .venv_paddle is incomplete. Run install-env.cmd again, then rerun this script." + throw "The local .venv_paddle is incomplete. Run one-click-start.cmd again, then rerun this script." } $Weight = Join-Path $Root "models\weights\yolo-captcha-detector.pt" @@ -39,21 +39,22 @@ New-Item -ItemType Directory -Path $PackageDir | Out-Null $Include = @( "glm-coding-helper.user.js", "scripts\userscripts\glm-coding-captcha-direct.user.js", - "start-backend.cmd", - "install-env.cmd", "one-click-start.cmd", + "start-backend-pipeline-gui.cmd", + "start-backend-pipeline-gui.ps1", "README.md", "CHANGELOG.md", "LICENSE", "requirements-backend-cpu.txt", "scripts", "models", + "backend", ".venv_paddle", ".paddlex_cache_cpu", ".paddle_home" ) -$KnownRootCmdItems = @("start-backend.cmd", "install-env.cmd", "one-click-start.cmd") +$KnownRootCmdItems = @("one-click-start.cmd", "start-backend-pipeline-gui.cmd") $ExtraRootCmdItems = Get-ChildItem -LiteralPath $Root -Filter "*.cmd" -File | Where-Object { $KnownRootCmdItems -notcontains $_.Name } | ForEach-Object { $_.Name } @@ -83,7 +84,7 @@ $Guide = @" GLM Coding Helper portable CPU package 1. Install or update Tampermonkey script from glm-coding-helper.user.js. -2. Double-click start-backend.cmd, one-click-start.cmd, or the localized start shortcut if present. +2. Double-click one-click-start.cmd to install the environment on first run, or start-backend-pipeline-gui.cmd to launch the pipeline backend with GUI. 3. Open the GLM Coding page from your normal browser session. This package includes the CPU Python environment and local model files. diff --git a/scripts/release/build_release_zips.ps1 b/scripts/release/build_release_zips.ps1 index 9ded79f..458661d 100644 --- a/scripts/release/build_release_zips.ps1 +++ b/scripts/release/build_release_zips.ps1 @@ -71,19 +71,20 @@ if (Test-Path $OnlineDir) { Remove-Item -LiteralPath $OnlineDir -Recurse -Force $CommonItems = @( "glm-coding-helper.user.js", "scripts\userscripts\glm-coding-captcha-direct.user.js", - "start-backend.cmd", - "install-env.cmd", "one-click-start.cmd", + "start-backend-pipeline-gui.cmd", + "start-backend-pipeline-gui.ps1", "README.md", "CHANGELOG.md", "LICENSE", "requirements-backend-cpu.txt", "requirements-backend-gpu.txt", "scripts", - "models" + "models", + "backend" ) -$KnownRootCmdItems = @("start-backend.cmd", "install-env.cmd", "one-click-start.cmd") +$KnownRootCmdItems = @("one-click-start.cmd", "start-backend-pipeline-gui.cmd") $ExtraRootCmdItems = Get-ChildItem -LiteralPath $Root -Filter "*.cmd" -File | Where-Object { $KnownRootCmdItems -notcontains $_.Name } | ForEach-Object { $_.Name } @@ -103,7 +104,8 @@ Recommended: 3. It will install CPU/GPU backend dependencies automatically when missing, then start the backend. Manual: -- install-env.cmd installs CPU backend environment. +- one-click-start.cmd installs the CPU backend environment on first run. +- start-backend-pipeline-gui.cmd launches the pipeline backend with a Tk GUI window. - start-backend.cmd starts CPU backend after environment exists. "@ Set-Content -LiteralPath (Join-Path $OnlineDir "ONLINE_INSTALLER_README.txt") -Value $OnlineGuide -Encoding UTF8 diff --git a/scripts/tools/evaluate_pipeline_compare.py b/scripts/tools/evaluate_pipeline_compare.py new file mode 100644 index 0000000..7e29e6e --- /dev/null +++ b/scripts/tools/evaluate_pipeline_compare.py @@ -0,0 +1,269 @@ +from __future__ import annotations + +import argparse +import importlib.util +import json +import logging +import math +import os +import subprocess +import sys +import time +from pathlib import Path + +from PIL import Image, ImageFont + + +ROOT = Path(__file__).resolve().parents[2] +PROJECT_ROOT = ROOT.parents[1] + + +def find_image(name: str, dataset_root: Path) -> Path | None: + candidates = [ + dataset_root / "auto_captured" / name, + dataset_root / "auto_captured_archive" / "captured_310_20260513_042005" / name, + dataset_root / "auto_captured_autolabel" / "images" / name, + dataset_root / "char_detector_yolo" / "images" / "train" / name, + dataset_root / "char_detector_yolo" / "images" / "val" / name, + ] + for path in candidates: + if path.exists(): + return path + return None + + +def point_for_prompt(prompt: str, box_chars: list[str], boxes: list[list[float]]) -> list[dict]: + used: set[int] = set() + out: list[dict] = [] + for ch in prompt: + found = None + for idx, box_ch in enumerate(box_chars): + if idx not in used and box_ch == ch: + found = idx + break + if found is None: + raise ValueError(f"cannot map prompt={prompt!r} box_chars={box_chars!r}") + used.add(found) + x1, y1, x2, y2 = boxes[found] + out.append({"x": (x1 + x2) / 2.0, "y": (y1 + y2) / 2.0, "char": ch}) + return out + + +def points_ok(pred: list[dict], gt: list[dict], threshold: float) -> tuple[bool, float]: + if len(pred) != len(gt): + return False, float("inf") + max_dist = 0.0 + for p, g in zip(pred, gt): + d = math.hypot(float(p["x"]) - float(g["x"]), float(p["y"]) - float(g["y"])) + max_dist = max(max_dist, d) + if d > threshold: + return False, max_dist + return True, max_dist + + +def scan_windows_fonts() -> list[tuple[str, int]]: + font_dir = Path(os.environ.get("WINDIR", r"C:\Windows")) / "Fonts" + names = [ + "simsun.ttc", + "simhei.ttf", + "simkai.ttf", + "simfang.ttf", + "msyh.ttc", + "msyhbd.ttc", + "msyhl.ttc", + "Deng.ttf", + "Dengb.ttf", + "Dengl.ttf", + ] + fonts: list[tuple[str, int]] = [] + seen: set[tuple[str, int]] = set() + for name in names: + path = font_dir / name + if not path.exists(): + continue + for idx in range(8): + try: + font = ImageFont.truetype(str(path), 40, index=idx) + cn = font.getbbox("测") + en = font.getbbox("A") + except Exception: + break + cn_w = cn[2] - cn[0] + en_w = en[2] - en[0] + if cn_w >= 25 and cn_w >= en_w * 1.2 and (str(path), idx) not in seen: + fonts.append((str(path), idx)) + seen.add((str(path), idx)) + return fonts + + +def load_grabber(reference_root: Path): + server_path = reference_root / "captcha" / "ddddocr_server.py" + spec = importlib.util.spec_from_file_location("grabber_ddddocr_server", server_path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot load {server_path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + logging.getLogger("ddddocr-server").disabled = True + module._all_font_paths[:] = scan_windows_fonts() + module._variant_cache.clear() + module._font_obj_cache.clear() + return module + + +def start_our_worker(mode: str) -> subprocess.Popen: + env = os.environ.copy() + env["PYTHONIOENCODING"] = "utf-8" + env["PYTHONUTF8"] = "1" + env["CNCAPTCHA_OCR_MODE"] = mode + env.setdefault("CNCAPTCHA_YOLO_DEVICE", "cpu") + py = ROOT / ".venv_paddle" / "Scripts" / "python.exe" + if not py.exists(): + py = Path(sys.executable) + return subprocess.Popen( + [str(py), "-u", str(ROOT / "scripts" / "tools" / "captcha_worker.py")], + cwd=str(ROOT), + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) + + +def ask_our_worker(proc: subprocess.Popen, image_path: Path, prompt: str, timeout: float = 45.0) -> dict: + assert proc.stdin is not None and proc.stdout is not None + payload = json.dumps({"image_path": str(image_path), "chars": list(prompt)}, ensure_ascii=False) + "\n" + proc.stdin.write(payload.encode("utf-8")) + proc.stdin.flush() + start = time.perf_counter() + while time.perf_counter() - start < timeout: + line = proc.stdout.readline() + if not line: + raise RuntimeError("our worker exited") + text = line.decode("utf-8", errors="replace").strip() + if not text.startswith("{"): + continue + return json.loads(text) + raise TimeoutError("our worker timeout") + + +def run(args: argparse.Namespace) -> int: + dataset_root = Path(args.dataset_root).resolve() + labels = json.loads((dataset_root / "glm_ocr_labels_all.json").read_text(encoding="utf-8")) + items = [] + for name, row in labels.items(): + if row.get("has_error"): + continue + image = find_image(name, dataset_root) + if image is None: + continue + prompt = str(row["prompt"]) + box_chars = list(row["box_chars"]) + boxes = list(row["boxes"]) + if len(prompt) != 3 or len(box_chars) != 3 or len(boxes) != 3: + continue + try: + gt = point_for_prompt(prompt, box_chars, boxes) + except Exception: + continue + items.append({"name": name, "image": image, "prompt": prompt, "gt": gt}) + if args.limit: + items = items[: args.limit] + + out_dir = Path(args.output_dir) + out_dir.mkdir(parents=True, exist_ok=True) + threshold = float(args.threshold) + summary = {"items": len(items), "threshold": threshold, "runs": {}} + + if args.engine in {"grabber", "both"}: + grabber = load_grabber(Path(args.reference_root).resolve()) + rows = [] + ok_count = 0 + times = [] + for idx, item in enumerate(items, 1): + t0 = time.perf_counter() + try: + pred = grabber.solve_click_captcha(item["image"].read_bytes(), item["prompt"]) + success, max_dist = points_ok(pred, item["gt"], threshold) + err = "" + except Exception as exc: + pred = [] + success = False + max_dist = float("inf") + err = str(exc) + ms = (time.perf_counter() - t0) * 1000 + times.append(ms) + ok_count += int(success) + rows.append({"name": item["name"], "ok": success, "max_dist": max_dist, "ms": round(ms, 1), "error": err}) + if idx % 25 == 0: + print(f"grabber {idx}/{len(items)} ok={ok_count}", flush=True) + summary["runs"]["grabber_winfonts"] = { + "ok": ok_count, + "total": len(items), + "acc": ok_count / max(len(items), 1), + "avg_ms": sum(times) / max(len(times), 1), + "p50_ms": sorted(times)[len(times) // 2] if times else 0, + "fonts": len(getattr(grabber, "_all_font_paths", [])), + } + (out_dir / "grabber_winfonts_rows.json").write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8") + + if args.engine in {"our", "both"}: + proc = start_our_worker(args.our_mode) + rows = [] + ok_count = 0 + times = [] + try: + for idx, item in enumerate(items, 1): + t0 = time.perf_counter() + try: + resp = ask_our_worker(proc, item["image"], item["prompt"]) + except Exception as exc: + resp = {"success": False, "error": str(exc)} + if resp.get("success"): + img = Image.open(item["image"]) + pred = [{"x": float(c["nx"]) * img.width, "y": float(c["ny"]) * img.height} for c in resp.get("click_coords", [])] + success, max_dist = points_ok(pred, item["gt"], threshold) + err = "" + ms = float(resp.get("elapsed_ms") or ((time.perf_counter() - t0) * 1000)) + else: + success = False + max_dist = float("inf") + err = str(resp.get("error", "failed")) + ms = (time.perf_counter() - t0) * 1000 + times.append(ms) + ok_count += int(success) + rows.append({"name": item["name"], "ok": success, "max_dist": max_dist, "ms": round(ms, 1), "error": err, "resp": resp}) + if idx % 25 == 0: + print(f"our {idx}/{len(items)} ok={ok_count}", flush=True) + finally: + try: + proc.kill() + except Exception: + pass + summary["runs"][f"our_{args.our_mode}"] = { + "ok": ok_count, + "total": len(items), + "acc": ok_count / max(len(items), 1), + "avg_ms": sum(times) / max(len(times), 1), + "p50_ms": sorted(times)[len(times) // 2] if times else 0, + } + (out_dir / f"our_{args.our_mode}_rows.json").write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8") + + (out_dir / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8") + print(json.dumps(summary, ensure_ascii=False, indent=2), flush=True) + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--dataset-root", default=str(PROJECT_ROOT / "dataset")) + parser.add_argument("--reference-root", default=str(PROJECT_ROOT / "_reference" / "glm-coding-grabber")) + parser.add_argument("--output-dir", default=str(PROJECT_ROOT / "dataset" / "extreme_compare")) + parser.add_argument("--engine", choices=["grabber", "our", "both"], default="both") + parser.add_argument("--our-mode", default="cpu") + parser.add_argument("--threshold", type=float, default=35.0) + parser.add_argument("--limit", type=int, default=0) + return run(parser.parse_args()) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/userscripts/glm-coding-helper.user.js b/scripts/userscripts/glm-coding-helper.user.js index c504ea3..98f4c92 100644 --- a/scripts/userscripts/glm-coding-helper.user.js +++ b/scripts/userscripts/glm-coding-helper.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name 智谱 GLM Coding Plan 抢购助手 + 本地 OCR 自动验证码 // @namespace http://tampermonkey.net/ -// @version 8.20 +// @version 8.21.1 // @description GLM Coding Rush / 智谱 GLM Coding Plan 抢购助手,一键抢购油猴脚本 / Tampermonkey userscript,配合本地 CPU/GPU OCR 自动识别中文点选验证码并点击,支持多窗口并发、限流重试和支付页安全保护 // @author mumumi // @include https://*bigmodel.cn/glm-coding* @@ -514,10 +514,19 @@ RUSH_TARGET_HOUR : 9, RUSH_TARGET_MIN : 59, RUSH_TARGET_SEC : 58, + // v8.21: 高级模式(默认关闭;开启后可调点击/限流重试间隔,加 ±20% 随机抖动防风控) + ADVANCED_MODE : false, + CAPTCHA_CLICK_DELAY : 220, // 验证码三个字之间的间隔(ms) + RL_RETRY_DELAY : 1000, // 限流弹窗关闭后多久再点购买(ms) }; function loadCfg() { try { const s = GM_getValue(STORAGE_KEY, null); return s ? { ...DEF, ...JSON.parse(s) } : { ...DEF }; } catch { return { ...DEF }; } } function saveCfg(c) { GM_setValue(STORAGE_KEY, JSON.stringify(c)); } const CFG = loadCfg(); + // v8.21: 延迟统一加 ±20% 随机抖动(经典/高级模式都有,防 RPM 风控) + function jitterDelay(base) { + const delta = base * 0.2; + return Math.max(1, Math.round(base + (Math.random() * 2 - 1) * delta)); + } GM_registerMenuCommand('⚙️ 打开配置面板', openConfigPanel); GM_registerMenuCommand('🗑️ 清除今日套餐状态缓存', () => { localStorage.removeItem(_dsKey); alert('今日状态已清除,即将刷新。'); location.reload(); }); GM_registerMenuCommand('🚀 一键多开窗口', openMultipleWindows); @@ -875,6 +884,24 @@ : +
+ +
+
+ 验证码点击间隔 (ms) + + 默认 220,±20% 抖动 +
+
+ 限流重试间隔 (ms) + + 默认 1000,±20% 抖动 +
+
@@ -887,6 +914,9 @@ const getPkgs = buildTransferBox(document.getElementById('glm-wp'), PKGS_MAP, CFG.PACKAGES_PRIORITY, '套餐优先级'); const getTabs = buildTransferBox(document.getElementById('glm-wt'), TABS_MAP, CFG.TABS_PRIORITY, '订阅周期优先级'); panel.querySelector('#glm-cc').onclick = () => ov.remove(); + panel.querySelector('#glm-advanced').onchange = (e) => { + panel.querySelector('#glm-advanced-panel').style.display = e.target.checked ? 'block' : 'none'; + }; panel.querySelector('#glm-multi').onclick = () => { openMultipleWindows(); }; panel.querySelector('#glm-cs').onclick = () => { const p = getPkgs(), t = getTabs(); @@ -904,6 +934,9 @@ RUSH_TARGET_HOUR: parseInt(panel.querySelector('#glm-rh').value, 10), RUSH_TARGET_MIN: parseInt(panel.querySelector('#glm-rm').value, 10), RUSH_TARGET_SEC: parseInt(panel.querySelector('#glm-rs').value, 10), + ADVANCED_MODE: panel.querySelector('#glm-advanced').checked, + CAPTCHA_CLICK_DELAY: Math.max(50, parseInt(panel.querySelector('#glm-ccd').value, 10) || 220), + RL_RETRY_DELAY: Math.max(100, parseInt(panel.querySelector('#glm-rld').value, 10) || 1000), SAFE_DEFAULTS_VERSION, }); ov.remove(); alert('已保存,即将刷新。'); location.reload(); @@ -920,6 +953,11 @@ console.log('[GLM] rush lock cleared'); } if (ensureDiscountEntry()) return; + // v8.21: 黄金时间内每天首条提示建议试试无痕模式(sessionStorage 去重) + if (isGoldenTime() && !sessionStorage.getItem('glm_nudge_incognito_v1')) { + sessionStorage.setItem('glm_nudge_incognito_v1', '1'); + setBar('🕶️ 抢不到?试试无痕窗口(Ctrl+Shift+N)!没有历史 Cookie/缓存/Service Worker,可能消除隐形的风控标记。Tampermonkey 需在扩展详情页允许在无痕中启用。', '#722ed1'); + } if (state === 'SLEEPING') { const rem = sleepUntil - Date.now(); if (rem <= 0) { @@ -1074,7 +1112,12 @@ return; } setBar(`⚠️ 限流 ${taskRLCount}/${MAX_RL},自动关闭后重试...`, '#d46b08'); - taskPhase = 'IDLE'; return; + // v8.21: 高级模式可调限流重试间隔(带 ±20% 抖动防风控) + // 期间用 WAITING_RL 状态占位,避免下一轮 tick 重复关弹窗 + const _rlDelay = jitterDelay(CFG.RL_RETRY_DELAY); + taskPhase = 'WAITING_RL'; + setTimeout(() => { taskPhase = 'IDLE'; }, _rlDelay); + return; } if (isPayDialog()) { const verdict = checkPayDialog(); @@ -1768,7 +1811,8 @@ if (!Number.isFinite(ny) && Number.isFinite(Number(c.rel_y))) ny = Number(c.rel_y) / rect.height; if (!Number.isFinite(nx) || !Number.isFinite(ny)) continue; dispatchClickAt(bgEl, nx * rect.width, ny * rect.height, c.char || String(i + 1)); - await new Promise(function(r) { setTimeout(r, 220); }); + var _clickDelay = jitterDelay(CFG.CAPTCHA_CLICK_DELAY); + await new Promise(function(r) { setTimeout(r, _clickDelay); }); } await new Promise(function(r) { setTimeout(r, 350); }); rushState = 'idle'; diff --git a/start-backend-pipeline.cmd b/start-backend-pipeline-gui.cmd similarity index 80% rename from start-backend-pipeline.cmd rename to start-backend-pipeline-gui.cmd index f616383..ed0caa1 100644 --- a/start-backend-pipeline.cmd +++ b/start-backend-pipeline-gui.cmd @@ -1,5 +1,5 @@ @echo off chcp 65001 >nul cd /d "%~dp0" -powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0start-backend-pipeline.ps1" +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0start-backend-pipeline-gui.ps1" pause diff --git a/start-backend-pipeline.ps1 b/start-backend-pipeline-gui.ps1 similarity index 71% rename from start-backend-pipeline.ps1 rename to start-backend-pipeline-gui.ps1 index 4f4f579..7ae39e6 100644 --- a/start-backend-pipeline.ps1 +++ b/start-backend-pipeline-gui.ps1 @@ -1,12 +1,13 @@ -# GLM Coding Helper - Pipeline Backend Launcher -# Usage: powershell -File start-backend-pipeline.ps1 -# or double-click start-backend-pipeline.cmd +# GLM Coding Helper - Pipeline Backend GUI Launcher +# Usage: powershell -File start-backend-pipeline-gui.ps1 +# or double-click start-backend-pipeline-gui.cmd +# 弹 Tk 窗口实时显示后端状态、最近识别结果、worker 启动日志 $ErrorActionPreference = "Continue" $Root = Split-Path -Parent $MyInvocation.MyCommand.Path Set-Location $Root -Write-Host "GLM Coding Helper - Pipeline Backend" -ForegroundColor Cyan +Write-Host "GLM Coding Helper - Pipeline Backend (GUI)" -ForegroundColor Cyan Write-Host "" # ── 查找 Python venv ── @@ -24,15 +25,14 @@ if (Test-Path "$Root\venv\Scripts\python.exe") { Write-Host "[信息] 使用 Python: $Python" -ForegroundColor Gray -# ── 依赖检查 ── -$depsCheck = & $Python -c "import fastapi, uvicorn, psutil" 2>&1 +# ── 依赖检查 (pipeline 依赖 + tkinter) ── +$depsCheck = & $Python -c "import fastapi, uvicorn, psutil, tkinter" 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host "" - Write-Host "[警告] 当前后端环境缺少 pipeline 依赖 (fastapi/uvicorn/psutil)。" -ForegroundColor Yellow - Write-Host "[WARN] Missing pipeline backend dependencies. Environment needs repair." -ForegroundColor Yellow + Write-Host "[警告] 当前后端环境缺少依赖 (fastapi/uvicorn/psutil/tkinter)。" -ForegroundColor Yellow + Write-Host "[WARN] Missing backend dependencies. Environment needs repair." -ForegroundColor Yellow Write-Host "" - # 尝试自动调用 setup_backend.ps1 $setupScript = "$Root\scripts\setup_backend.ps1" if (Test-Path $setupScript) { Write-Host "是否自动安装缺失的依赖?" @@ -42,11 +42,9 @@ if ($LASTEXITCODE -ne 0) { Write-Host "[信息] 正在安装 pipeline 依赖..." -ForegroundColor Cyan $env:PYTHONUTF8 = "1" $env:PYTHONIOENCODING = "utf-8" - # 直接 pip install 到当前 venv & $Python -m pip install fastapi "uvicorn[standard]" psutil --quiet if ($LASTEXITCODE -ne 0) { Write-Host "[失败] 自动安装失败,请手动运行: install-env.cmd" -ForegroundColor Red - Write-Host "[FAIL] Auto-install failed. Run manually: install-env.cmd" -ForegroundColor Red Read-Host "按 Enter 退出" exit 1 } @@ -70,19 +68,16 @@ if ($portLines) { $parts = $line -split '\s+' $portPid = $parts[-1] - # 获取进程信息 $procName = "" $procCmd = "" try { $proc = Get-Process -Id $portPid -ErrorAction SilentlyContinue if ($proc) { $procName = $proc.ProcessName } $procCmd = (Get-CimInstance Win32_Process -Filter "ProcessId=$portPid" -ErrorAction SilentlyContinue).CommandLine - if (-not $procCmd) { $procCmd = "" } } catch {} Write-Host "" Write-Host "[警告] 端口 8888 已被占用,后端可能已经在运行。" -ForegroundColor Yellow - Write-Host "[WARN] Port 8888 is already in use. The backend may already be running." -ForegroundColor Yellow Write-Host "" Write-Host "占用进程:" -ForegroundColor DarkYellow Write-Host " PID : $portPid" @@ -93,10 +88,6 @@ if ($portLines) { Write-Host " 1 - 关闭该进程并重新启动后端" Write-Host " Enter - 不处理,直接退出" Write-Host "" - Write-Host "Choose:" - Write-Host " 1 - Stop this process and restart backend" - Write-Host " Enter - Exit without changing anything" - Write-Host "" $choice = Read-Host "输入 (Input)" if ($choice -eq "1") { @@ -109,14 +100,11 @@ if ($portLines) { } } -Write-Host "[信息] 正在启动 pipeline 后端 http://127.0.0.1:8888" -ForegroundColor Green -Write-Host "[提示] 首次启动需要加载模型 (~10秒),请等待 worker 就绪。" -ForegroundColor DarkYellow -Write-Host "[信息] Ctrl+C 停止" -ForegroundColor Gray +Write-Host "[信息] 启动 GUI 窗口,后端会在窗口中自动拉起。" -ForegroundColor Green +Write-Host "[提示] 关闭窗口会同时停止后端子进程。" -ForegroundColor DarkYellow Write-Host "" $env:PYTHONUTF8 = "1" $env:PYTHONIOENCODING = "utf-8" -& $Python "$Root\backend\server.py" - -Read-Host "按 Enter 退出" +& $Python "$Root\backend\gui.py" diff --git a/start-backend.cmd b/start-backend.cmd deleted file mode 100644 index b166ede..0000000 --- a/start-backend.cmd +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -chcp 65001 >nul -cd /d "%~dp0" -echo Starting GLM Coding Helper backend... -powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\one_click_start.ps1" -Target cpu -Port 8888 -pause diff --git a/启动后端.cmd b/启动后端.cmd deleted file mode 100644 index b166ede..0000000 --- a/启动后端.cmd +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -chcp 65001 >nul -cd /d "%~dp0" -echo Starting GLM Coding Helper backend... -powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\one_click_start.ps1" -Target cpu -Port 8888 -pause diff --git a/首次安装环境.cmd b/首次安装环境.cmd deleted file mode 100644 index 1e0c3b9..0000000 --- a/首次安装环境.cmd +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -chcp 65001 >nul -cd /d "%~dp0" -echo Installing GLM Coding Helper backend environment... -powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\setup_backend.ps1" -Target cpu -pause