From 96e8ff459ba14b08346ffa44871fc28c41d5c5c5 Mon Sep 17 00:00:00 2001 From: budian Date: Sun, 11 Oct 2026 10:16:43 +0000 Subject: [PATCH 1/3] fix: stabilize desktop sidecar, native tools and persistent chat --- .github/workflows/verify.yml | 30 + .gitignore | 11 + DS code 2.0.1-0712-NEXT demo/README.md | 96 +- DS code 2.0.1-0712-NEXT demo/package.json | 17 - .../renderer/css/app.css | 344 -------- .../renderer/css/theme.css | 72 -- .../renderer/index.html | 241 ----- .../renderer/js/app.js | 531 ----------- .../renderer/js/i18n.js | 279 ------ .../renderer/js/utils.js | 60 -- DS code 2.0.1-0712-NEXT demo/start.sh | 51 +- DS code 2.0.1-0712-NEXT demo/stop.sh | 11 +- README.md | 143 ++- data/config.json | 1 - docs/DELIVERY.md | 56 ++ electron/main.js | 173 ++-- electron/preload.js | 4 + electron/sidecar-process.js | 75 ++ engine/src/core/chat_engine/_base.py | 42 +- engine/src/core/chat_engine/_command_mixin.py | 16 +- .../src/core/chat_engine/_deep_loop_mixin.py | 4 +- engine/src/core/config.py | 23 +- engine/src/core/deepseek_api.py | 77 +- engine/src/gui/animations/_shadow.py | 2 +- engine/src/gui/animations/_widgets.py | 2 +- engine/src/gui/bubbles/reasoning.py | 2 +- engine/src/gui/bubbles/tool_bubbles.py | 2 +- engine/src/gui/panels/left_panel.py | 2 +- engine/src/gui/slash_popup.py | 1 + engine/src/sandbox/history.py | 42 +- .../observable/dom/WebSocketSubject.js | 249 ------ .../observable/dom/WebSocketSubject.js.map | 1 - .../observable/dom/animationFrames.js | 38 - .../observable/dom/animationFrames.js.map | 1 - .../dist/cjs/internal/observable/dom/fetch.js | 79 -- .../cjs/internal/observable/dom/fetch.js.map | 1 - .../cjs/internal/observable/dom/webSocket.js | 9 - .../internal/observable/dom/webSocket.js.map | 1 - .../observable/dom/WebSocketSubject.js | 214 ----- .../observable/dom/WebSocketSubject.js.map | 1 - .../observable/dom/animationFrames.js | 34 - .../observable/dom/animationFrames.js.map | 1 - .../dist/esm/internal/observable/dom/fetch.js | 53 -- .../esm/internal/observable/dom/fetch.js.map | 1 - .../esm/internal/observable/dom/webSocket.js | 5 - .../internal/observable/dom/webSocket.js.map | 1 - .../observable/dom/WebSocketSubject.js | 221 ----- .../observable/dom/WebSocketSubject.js.map | 1 - .../observable/dom/animationFrames.js | 34 - .../observable/dom/animationFrames.js.map | 1 - .../esm5/internal/observable/dom/fetch.js | 54 -- .../esm5/internal/observable/dom/fetch.js.map | 1 - .../esm5/internal/observable/dom/webSocket.js | 5 - .../internal/observable/dom/webSocket.js.map | 1 - .../observable/dom/WebSocketSubject.d.ts | 173 ---- .../observable/dom/WebSocketSubject.d.ts.map | 1 - .../observable/dom/animationFrames.d.ts | 76 -- .../observable/dom/animationFrames.d.ts.map | 1 - .../types/internal/observable/dom/fetch.d.ts | 7 - .../internal/observable/dom/fetch.d.ts.map | 1 - .../internal/observable/dom/webSocket.d.ts | 159 ---- .../observable/dom/webSocket.d.ts.map | 1 - package-lock.json | 833 ++++++++++++++++++ package.json | 7 +- renderer/css/app.css | 8 + renderer/index.html | 2 + renderer/js/app.js | 197 +++-- renderer/js/chat-client.js | 58 ++ renderer/js/dialogs.js | 29 + renderer/js/utils.js | 33 +- requirements.lock | 20 + requirements.txt | 4 + sidecar/__init__.py | 1 + sidecar/http_protocol.py | 99 +++ sidecar/runtime.py | 180 ++++ sidecar/service.py | 184 ++++ sidecar/sidecar.py | 713 +++------------ sidecar/sidecar_api.py | 273 +----- sidecar/sidecar_engine.py | 168 +--- start.sh | 59 +- stop.sh | 22 +- tests/electron-smoke.cjs | 25 + tests/electron.test.js | 28 + tests/electron_smoke.py | 34 + tests/renderer.test.js | 58 ++ tests/test_core.py | 121 +++ tests/test_http.py | 112 +++ tests/test_integration.py | 144 +++ tests/test_parent.py | 51 ++ tests/test_runtime.py | 111 +++ 90 files changed, 2895 insertions(+), 4515 deletions(-) create mode 100644 .github/workflows/verify.yml create mode 100644 .gitignore delete mode 100644 DS code 2.0.1-0712-NEXT demo/package.json delete mode 100644 DS code 2.0.1-0712-NEXT demo/renderer/css/app.css delete mode 100644 DS code 2.0.1-0712-NEXT demo/renderer/css/theme.css delete mode 100644 DS code 2.0.1-0712-NEXT demo/renderer/index.html delete mode 100644 DS code 2.0.1-0712-NEXT demo/renderer/js/app.js delete mode 100644 DS code 2.0.1-0712-NEXT demo/renderer/js/i18n.js delete mode 100644 DS code 2.0.1-0712-NEXT demo/renderer/js/utils.js mode change 100644 => 100755 DS code 2.0.1-0712-NEXT demo/start.sh mode change 100644 => 100755 DS code 2.0.1-0712-NEXT demo/stop.sh delete mode 100644 data/config.json create mode 100644 docs/DELIVERY.md create mode 100644 electron/preload.js create mode 100644 electron/sidecar-process.js delete mode 100644 node_modules/rxjs/dist/cjs/internal/observable/dom/WebSocketSubject.js delete mode 100644 node_modules/rxjs/dist/cjs/internal/observable/dom/WebSocketSubject.js.map delete mode 100644 node_modules/rxjs/dist/cjs/internal/observable/dom/animationFrames.js delete mode 100644 node_modules/rxjs/dist/cjs/internal/observable/dom/animationFrames.js.map delete mode 100644 node_modules/rxjs/dist/cjs/internal/observable/dom/fetch.js delete mode 100644 node_modules/rxjs/dist/cjs/internal/observable/dom/fetch.js.map delete mode 100644 node_modules/rxjs/dist/cjs/internal/observable/dom/webSocket.js delete mode 100644 node_modules/rxjs/dist/cjs/internal/observable/dom/webSocket.js.map delete mode 100644 node_modules/rxjs/dist/esm/internal/observable/dom/WebSocketSubject.js delete mode 100644 node_modules/rxjs/dist/esm/internal/observable/dom/WebSocketSubject.js.map delete mode 100644 node_modules/rxjs/dist/esm/internal/observable/dom/animationFrames.js delete mode 100644 node_modules/rxjs/dist/esm/internal/observable/dom/animationFrames.js.map delete mode 100644 node_modules/rxjs/dist/esm/internal/observable/dom/fetch.js delete mode 100644 node_modules/rxjs/dist/esm/internal/observable/dom/fetch.js.map delete mode 100644 node_modules/rxjs/dist/esm/internal/observable/dom/webSocket.js delete mode 100644 node_modules/rxjs/dist/esm/internal/observable/dom/webSocket.js.map delete mode 100644 node_modules/rxjs/dist/esm5/internal/observable/dom/WebSocketSubject.js delete mode 100644 node_modules/rxjs/dist/esm5/internal/observable/dom/WebSocketSubject.js.map delete mode 100644 node_modules/rxjs/dist/esm5/internal/observable/dom/animationFrames.js delete mode 100644 node_modules/rxjs/dist/esm5/internal/observable/dom/animationFrames.js.map delete mode 100644 node_modules/rxjs/dist/esm5/internal/observable/dom/fetch.js delete mode 100644 node_modules/rxjs/dist/esm5/internal/observable/dom/fetch.js.map delete mode 100644 node_modules/rxjs/dist/esm5/internal/observable/dom/webSocket.js delete mode 100644 node_modules/rxjs/dist/esm5/internal/observable/dom/webSocket.js.map delete mode 100644 node_modules/rxjs/dist/types/internal/observable/dom/WebSocketSubject.d.ts delete mode 100644 node_modules/rxjs/dist/types/internal/observable/dom/WebSocketSubject.d.ts.map delete mode 100644 node_modules/rxjs/dist/types/internal/observable/dom/animationFrames.d.ts delete mode 100644 node_modules/rxjs/dist/types/internal/observable/dom/animationFrames.d.ts.map delete mode 100644 node_modules/rxjs/dist/types/internal/observable/dom/fetch.d.ts delete mode 100644 node_modules/rxjs/dist/types/internal/observable/dom/fetch.d.ts.map delete mode 100644 node_modules/rxjs/dist/types/internal/observable/dom/webSocket.d.ts delete mode 100644 node_modules/rxjs/dist/types/internal/observable/dom/webSocket.d.ts.map create mode 100644 package-lock.json create mode 100644 renderer/js/chat-client.js create mode 100644 renderer/js/dialogs.js create mode 100644 requirements.lock create mode 100644 requirements.txt create mode 100644 sidecar/__init__.py create mode 100644 sidecar/http_protocol.py create mode 100644 sidecar/runtime.py create mode 100644 sidecar/service.py mode change 100644 => 100755 start.sh mode change 100644 => 100755 stop.sh create mode 100644 tests/electron-smoke.cjs create mode 100644 tests/electron.test.js create mode 100644 tests/electron_smoke.py create mode 100644 tests/renderer.test.js create mode 100644 tests/test_core.py create mode 100644 tests/test_http.py create mode 100644 tests/test_integration.py create mode 100644 tests/test_parent.py create mode 100644 tests/test_runtime.py diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000..91dad20 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,30 @@ +name: Verify +on: + push: + pull_request: +jobs: + regression: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + - run: python -m pip install -r requirements.lock ruff==0.17.0 + - run: npm ci --ignore-scripts + - run: PYTHONPATH=engine python -m unittest discover -s tests -v + - run: npm test + - run: python -m ruff check --select F821,F822,F823,E9 engine sidecar tests + - run: python -m compileall -q engine sidecar + - run: node --check renderer/js/app.js && node --check electron/main.js && bash -n start.sh stop.sh + - run: node node_modules/electron/install.js + - run: xvfb-run -a python tests/electron_smoke.py + - uses: actions/upload-artifact@v4 + if: always() + with: + name: desktop-smoke + path: test-results/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f79cf69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +.venv/ +__pycache__/ +*.py[cod] +.ruff_cache/ +data/* +!data/projects/ +!data/projects/.gitkeep +dscode_data/ +.DS_Store +test-results/ diff --git a/DS code 2.0.1-0712-NEXT demo/README.md b/DS code 2.0.1-0712-NEXT demo/README.md index 128e21e..62af67e 100644 --- a/DS code 2.0.1-0712-NEXT demo/README.md +++ b/DS code 2.0.1-0712-NEXT demo/README.md @@ -1,98 +1,8 @@ -# DS Code v2.0.1-0712-NEXT - -> AI 编程助手 · Electron + Python Sidecar · DeepSeek V4 API - -## 快速启动 / Quick Start +此目录仅保留旧启动路径的兼容入口。完整源码、依赖和说明都在仓库根目录: ```bash -cd "DS code 2.0.1-0712-NEXT demo" +cd .. ./start.sh ``` -首次启动会自动下载 Electron,之后秒开。 - -## 目录结构 / File Structure - -``` -├── start.sh # 一键启动脚本 -├── stop.sh # 停止脚本 -├── sidecar/ # Python Sidecar (纯 stdlib HTTP/SSE) -│ ├── sidecar.py # 主程序 (端口 8765) -│ ├── sidecar_engine.py # 引擎封装 (备用) -│ └── sidecar_api.py # FastAPI 版本 (备用) -├── renderer/ # 前端界面 -│ ├── index.html # 主页面 -│ ├── js/ -│ │ ├── app.js # 主逻辑 (向导/聊天/设置) -│ │ ├── i18n.js # 中英文国际化 -│ │ └── utils.js # Markdown 渲染 / 工具函数 -│ └── css/ -│ ├── theme.css # 设计令牌 + 深色模式 -│ └── app.css # 布局 + 组件样式 -├── engine/src/ # AI 引擎 -│ ├── core/ # ChatEngine / DeepSeek API / Config / Hooks -│ ├── tools/ # 91 内置工具 -│ ├── sandbox/ # 项目隔离 / 历史记录 -│ ├── prompts/ # 系统提示词 -│ └── agents/ # 子代理 -├── electron/ # Electron 窗口 -├── data/ # 配置和数据 -├── assets/ # 图标/图片 -└── node_modules/ # 依赖 (npm install 后) -``` - -## 功能 / Features - -### 对话 / Chat -- DeepSeek V4 Pro / Flash 流式对话 -- Thinking 模式:实时推理链输出 -- 对话历史自动保存与恢复 - -### 引导页 / Onboarding Wizard -- 选择服务提供商:DeepSeek 官方 / 自定义供应商 -- 自定义供应商支持:Base URL + 模型配置 (最多 2 个) -- 1M Context 开关 per model - -### 工具调用 / Tool Calls -- 91 个内置工具 (文件操作 / 命令执行 / Git / 搜索 / MCP / …) -- 原生 DeepSeek V4 function calling (`tools` 参数) -- 文本提取 fallback 兼容旧格式 - -### UI -- `/` 斜杠命令菜单 -- 中英文双语 (实时切换) -- 深色/浅色主题 -- 项目侧边栏 (创建/切换/删除) -- 待办事项、文件管理面板 - -### API 端点 / API Endpoints -| 端点 | 说明 | -|---|---| -| `GET /api/health` | 健康检查 | -| `GET /api/config` | 读取配置 | -| `PUT /api/config` | 更新配置 | -| `GET /api/projects` | 列出项目 | -| `POST /api/projects` | 创建项目 | -| `DELETE /api/projects/{name}` | 删除项目 | -| `GET /api/chat/stream` | SSE 流式对话 | -| `POST /api/chat/stop` | 停止对话 | -| `GET /api/balance` | 查询余额 (http.client) | -| `GET /api/models` | 列出模型 | - -## 配置 / Configuration - -首次启动会弹出引导页。配置加密存储在 `~/dscode_data/config.enc` (Fernet)。 - -## 技术栈 / Tech Stack - -| 层 | 技术 | -|---|---| -| 前端 | HTML/CSS/JS (vanilla, zero framework) | -| 后端 | Python 3.9+ asyncio stdlib (zero pip install) | -| 引擎 | OpenAI SDK + http.client | -| 流式 | Server-Sent Events (SSE) | -| 配置 | Fernet 加密存储 | - -## License - -Apache-2.0 +旧路径 `./start.sh` 会转发到根目录,不再维护重复的前端副本。 diff --git a/DS code 2.0.1-0712-NEXT demo/package.json b/DS code 2.0.1-0712-NEXT demo/package.json deleted file mode 100644 index acca814..0000000 --- a/DS code 2.0.1-0712-NEXT demo/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "dscode-v2", - "version": "2.0.1-0712-NEXT", - "description": "DScode V2 — Electron + Python Sidecar", - "main": "electron/main.js", - "scripts": { - "start": "electron .", - "sidecar": "python3 sidecar/sidecar.py", - "dev": "concurrently \"npm run sidecar\" \"npm start\"" - }, - "dependencies": { - "electron": "^33.0.0" - }, - "devDependencies": { - "concurrently": "^9.0.0" - } -} diff --git a/DS code 2.0.1-0712-NEXT demo/renderer/css/app.css b/DS code 2.0.1-0712-NEXT demo/renderer/css/app.css deleted file mode 100644 index 9076e66..0000000 --- a/DS code 2.0.1-0712-NEXT demo/renderer/css/app.css +++ /dev/null @@ -1,344 +0,0 @@ -/* DScode V2 — 完整布局 + 组件,圆角卡片风 */ - -/* ── 侧边栏 ── */ -#sidebar { - width: 220px; min-width: 220px; - background: var(--card); border-right: 1px solid var(--border); - display: flex; flex-direction: column; - transition: width .2s, min-width .2s; overflow: hidden; -} -#sidebar.collapsed { width: 0; min-width: 0; padding: 0; } - -#sidebar-expand-btn { - display: none; position: fixed; left: 6px; top: 14px; z-index: 50; - width: 32px; height: 32px; border: 1px solid var(--border); border-radius: 8px; - background: var(--card); color: var(--text-3); font-size: 16px; cursor: pointer; - box-shadow: 0 2px 8px rgba(0,0,0,.08); -} -#sidebar-expand-btn:hover { color: var(--text); background: var(--surface); } -#sidebar.collapsed ~ #sidebar-expand-btn { display: flex; align-items: center; justify-content: center; } - -#sidebar-hdr { display: flex; align-items: center; gap: 6px; padding: 14px 12px 8px; } -#sidebar-ttl { font-weight: 700; font-size: 15px; color: var(--accent); } -.btn-icon { font-size: 16px; cursor: pointer; color: var(--text-3); user-select: none; padding: 2px; } -.btn-icon:hover { color: var(--text); } - -#btn-new-chat { - margin: 0 8px 8px; padding: 8px; - border: none; border-radius: 10px; - background: var(--accent); color: #fff; font-size: 13px; font-weight: 500; cursor: pointer; -} -#btn-new-chat:hover { background: var(--accent-hover); } - -#project-list { flex: 1; overflow-y: auto; padding: 0 6px; } -.pj-item { - padding: 8px 10px; border-radius: 8px; cursor: pointer; - font-size: 13px; color: var(--text-2); display: flex; align-items: center; gap: 6px; -} -.pj-item:hover { background: var(--surface); } -.pj-item.sel { background: var(--accent-light); color: var(--accent); font-weight: 500; } -.pj-item .pj-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex-shrink: 0; } - -#sidebar-btm { - padding: 8px; border-top: 1px solid var(--border); display: flex; gap: 6px; -} -.btn-btm { - padding: 7px 8px; background: transparent; color: var(--text-3); - border: none; border-radius: 8px; cursor: pointer; font-size: 12px; text-align: left; - white-space: nowrap; -} -.btn-btm:hover { background: var(--surface); color: var(--text); } - -/* ── 主区域 ── */ -#main-area { flex: 1; display: flex; flex-direction: column; min-width: 0; } - -/* ── 聊天 ── */ -#chat-container { flex: 1; overflow-y: auto; padding: 24px 32px 0; } -#chat-msgs { max-width: 760px; margin: 0 auto; display: flex; flex-direction: column; gap: 14px; padding-bottom: 12px; } - -/* ── 欢迎卡(圆角大卡片 + 投影)── */ -#welcome-card { - text-align: center; padding: 32px 0; - background: var(--card); border-radius: 20px; border: none; - box-shadow: 0 2px 16px rgba(0,0,0,.06); - max-width: 420px; margin: 24px auto; -} -.wc-icon { width: 64px; height: 64px; object-fit: contain; display: block; margin: 0 auto 8px; } -#welcome-card h2 { font-size: 20px; font-weight: 700; margin-bottom: 4px; color: var(--text); } -#welcome-card > p { font-size: 13px; color: var(--text-3); margin-bottom: 20px; } - -/* 2x2 统计格子 */ -#wc-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--border); border-radius: 12px; overflow: hidden; margin: 0 20px; } -.wc-cell { background: var(--bg); padding: 12px 12px; text-align: center; } -.wc-t { display: block; font-size: 10px; color: var(--text-4); text-transform: uppercase; margin-bottom: 3px; letter-spacing: 0.3px; } -.wc-v { display: block; font-size: 13px; font-weight: 600; color: var(--text); } - -/* ── 气泡(圆角 iMessage 风)── */ -.bubble { max-width: 85%; word-wrap: break-word; line-height: 1.65; font-size: 14px; } -.bubble-user { - align-self: flex-end; background: var(--accent); color: #fff; - padding: 10px 16px; border-radius: 18px 4px 18px 18px; -} -.bubble-ai { align-self: flex-start; } -.bubble-ai .ai-inner { - background: var(--card); border: 1px solid var(--border); - padding: 12px 16px; border-radius: 4px 18px 18px 18px; - box-shadow: 0 1px 4px rgba(0,0,0,.04); -} -.bubble-ai .ai-content { white-space: pre-wrap; } -.bubble-error .ai-inner { border-left: 3px solid var(--danger); } - -/* ── 推理块 ── */ -.rsn-block { margin-bottom: 6px; } -.rsn-hdr { font-size: 11px; color: var(--text-3); cursor: pointer; padding: 2px 0; user-select: none; display: flex; align-items: center; gap: 4px; } -.rsn-hdr:hover { color: var(--text); } -.rsn-body { font-size: 12px; color: var(--reasoning-text); padding: 8px 12px; background: var(--surface); border-radius: 8px; margin-top: 4px; white-space: pre-wrap; line-height: 1.5; max-height: 180px; overflow-y: auto; } -.rsn-body.hidden { display: none; } - -/* ── 工具卡片 ── */ -.tool-card { font-size: 11px; margin: 4px 0; border-radius: 8px; border: 1px solid var(--border); background: var(--surface); overflow: hidden; } -.tool-hdr { display: flex; align-items: center; gap: 6px; padding: 6px 10px; cursor: pointer; font-family: var(--font-code); color: var(--text-2); } -.tool-hdr:hover { background: var(--accent-light); } -.tool-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex-shrink: 0; } -.tool-dot.ok { background: var(--success); } -.tool-body { padding: 6px 10px; font-size: 11px; color: var(--text-2); white-space: pre-wrap; max-height: 120px; overflow-y: auto; border-top: 1px solid var(--border); display: none; } - -/* ── 状态信息卡(圆角胶囊,可点击打开快捷设置)── */ -#status-card { - display: flex; align-items: center; justify-content: space-between; - margin: 0 24px 2px; padding: 8px 16px; font-size: 11px; cursor: pointer; - background: var(--card); border: 1px solid var(--border); border-radius: 14px; - box-shadow: 0 1px 4px rgba(0,0,0,.04); -} -#status-card:hover { border-color: var(--accent); box-shadow: 0 2px 8px rgba(0,0,0,.08); } -#st-left, #st-right { display: flex; align-items: center; gap: 6px; } -#st-left { color: var(--text-2); font-weight: 500; } -#st-right { color: var(--text-3); font-size: 10px; } -.st-sep { color: var(--border); } -#st-model { color: var(--accent); font-weight: 600; } - -/* ── 输入区 ── */ -#input-area { display: flex; align-items: center; gap: 6px; padding: 8px 16px 6px; position: relative; } -#input-box { - flex: 1; resize: none; box-sizing: border-box; border: 2px solid var(--border); border-radius: 24px; - padding: 10px 16px; font-size: 14px; font-family: var(--font); - background: var(--card); color: var(--text); outline: none; - min-height: 40px; max-height: 140px; line-height: 1.45; - transition: border-color .15s; -} -#input-box:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-light); } -#btn-attach, #btn-send, #btn-stop { - width: 38px; height: 38px; border-radius: 50%; border: none; cursor: pointer; - display: flex; align-items: center; justify-content: center; font-size: 16px; -} -#btn-attach { background: transparent; color: var(--text-3); font-weight: 700; } -#btn-attach:hover { background: var(--surface); color: var(--text); } -#btn-send { background: var(--accent); color: #fff; } -#btn-send:disabled { background: var(--border); color: var(--text-4); cursor: default; } -#btn-send:hover:not(:disabled) { background: var(--accent-hover); } -#btn-stop { background: var(--danger); color: #fff; } -.hidden { display: none !important; } -#disclaimer { text-align: center; padding: 0 16px 8px; font-size: 9px; } -#disclaimer a { color: var(--text-4); text-decoration: none; } - -/* ── 快捷设置弹窗(圆角大卡片)── */ -.qs-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 100; align-items: center; justify-content: center; } -.qs-overlay.show { display: flex; } -#qs-card { background: var(--card); padding: 24px 28px; border-radius: 18px; box-shadow: 0 12px 48px rgba(0,0,0,.15); min-width: 320px; max-width: 380px; } -#qs-card h3 { font-size: 16px; font-weight: 700; margin-bottom: 16px; color: var(--text); } -.qs-sec { margin-bottom: 14px; } -.qs-sec label { display: block; font-size: 10px; font-weight: 600; color: var(--text-3); text-transform: uppercase; margin-bottom: 4px; letter-spacing: .3px; } -.qs-row { display: flex; gap: 5px; } -.qs-btn { - flex: 1; padding: 7px 6px; background: var(--surface); color: var(--text-2); - border: 1px solid var(--border); border-radius: 7px; font-size: 11px; cursor: pointer; text-align: center; -} -.qs-btn.on { background: var(--accent); color: #fff; border-color: var(--accent); } -.qs-btn:hover:not(.on) { background: var(--accent-light); } -.qs-close { - margin-top: 14px; width: 100%; padding: 9px; border: 1px solid var(--border); - border-radius: 8px; font-size: 13px; cursor: pointer; background: transparent; color: var(--text-3); -} -.qs-close:hover { background: var(--surface); } - -/* ── 右侧面板 ── */ -#right-panel { width: 240px; min-width: 240px; display: flex; flex-direction: column; gap: 8px; padding: 8px; overflow-y: auto; } -.rp-card { background: var(--card); border: 1px solid var(--border); border-radius: 14px; overflow: hidden; flex-shrink: 0; } -.rp-card.collapsed .rp-card-body { display: none; } -.rp-card-hdr { display: flex; align-items: center; justify-content: space-between; padding: 7px 10px; background: var(--surface); border-bottom: 1px solid var(--border); cursor: pointer; user-select: none; } -.rp-card-hdr:hover { background: var(--accent-light); } -.rp-card-title { font-size: 10px; font-weight: 600; color: var(--text-2); text-transform: uppercase; letter-spacing: .3px; } -.rp-card-count { font-size: 9px; color: var(--text-4); background: var(--border); padding: 1px 6px; border-radius: 8px; } -.rp-chevron { font-size: 10px; color: var(--text-4); transition: transform .15s; } -.rp-card.collapsed .rp-chevron { transform: rotate(-90deg); } -.rp-folder-btn { background: transparent; border: none; cursor: pointer; font-size: 13px; padding: 2px 4px; border-radius: 4px; color: var(--text-3); } -.rp-folder-btn:hover { background: var(--accent-light); color: var(--accent); } -.rp-empty { font-size: 11px; color: var(--text-4); text-align: center; padding: 16px 10px; } - -/* ── 待办 ── */ -.todo-input-row { display: flex; gap: 4px; padding: 6px 8px; border-bottom: 1px solid var(--border); } -.todo-input-row input { flex: 1; border: none; padding: 5px 6px; font-size: 11px; font-family: var(--font); background: transparent; color: var(--text); outline: none; } -.todo-input-row button { width: 22px; height: 22px; border-radius: 50%; border: none; background: var(--accent); color: #fff; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; } -#todos-list { max-height: 180px; overflow-y: auto; padding: 4px 0; } -.todo-row { display: flex; align-items: center; gap: 6px; padding: 5px 8px; font-size: 12px; color: var(--text-2); } -.todo-row:hover { background: var(--surface); } -.todo-row .todo-dot { width: 14px; height: 14px; border: 1.5px solid var(--border); border-radius: 3px; flex-shrink: 0; cursor: pointer; } -.todo-row .todo-dot.done { background: var(--accent); border-color: var(--accent); } -.todo-row .todo-dot.done::after { content: '✓'; color: #fff; font-size: 9px; position: absolute; top: -1px; left: 2px; } -.todo-row .todo-dot.done { position: relative; } -.todo-row .todo-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.todo-row .todo-text.done { color: var(--text-4); text-decoration: line-through; } -.todo-row .todo-del { opacity: 0; cursor: pointer; color: var(--danger); font-size: 14px; padding: 0 2px; } -.todo-row:hover .todo-del { opacity: 1; } - -/* ── 文件 ── */ -#files-body { max-height: 260px; overflow-y: auto; padding: 4px 0; } -.file-row { display: flex; align-items: center; gap: 5px; padding: 4px 8px; font-size: 11px; color: var(--text-2); cursor: pointer; font-family: var(--font-code); } -.file-row:hover { background: var(--surface); } -.file-icon { font-size: 13px; flex-shrink: 0; } -/* ── 详细设置弹窗 ── */ -#ds-card { - background: var(--card); padding: 20px 24px; border-radius: 18px; box-shadow: 0 12px 48px rgba(0,0,0,.15); - min-width: 380px; max-width: 440px; max-height: 80vh; overflow-y: auto; -} -#ds-card h3 { font-size: 16px; font-weight: 700; margin-bottom: 12px; color: var(--text); } -.ds-sec { margin-bottom: 10px; } -.ds-sec label { display: block; font-size: 10px; font-weight: 600; color: var(--text-3); text-transform: uppercase; margin-bottom: 3px; letter-spacing: .3px; } -.ds-sec input, .ds-sec select { - width: 100%; padding: 7px 10px; border: 1px solid var(--border); border-radius: 6px; - font-size: 12px; font-family: var(--font); background: var(--surface); color: var(--text); outline: none; -} -.ds-sec input:focus { border-color: var(--accent); } -.ds-saved { font-size: 9px; color: var(--success); margin-top: 2px; display: inline-block; transition: opacity .3s; opacity: 0; } -.ds-tabs { display: flex; gap: 4px; margin-bottom: 14px; } -.ds-tab { - flex: 1; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; - background: transparent; color: var(--text-3); font-size: 12px; cursor: pointer; text-align: center; -} -.ds-tab.active { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 500; } -.ds-tab-page { margin-bottom: 10px; } -.ds-about { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 4px; } -.ds-about-sub { font-size: 11px; color: var(--text-4); margin-bottom: 4px; } -.ds-footer { margin-top: 14px; } - -/* ── 首次引导向导 ── */ -#wiz-overlay { z-index: 200; } -#wiz-card { - background: var(--card); padding: 36px 28px; border-radius: 24px; box-shadow: 0 16px 56px rgba(0,0,0,.18); - max-width: 400px; text-align: center; -} -.wiz-page h2 { font-size: 20px; font-weight: 700; margin: 8px 0 4px; color: var(--text); } -.wiz-page h3 { font-size: 18px; font-weight: 700; margin: 8px 0 16px; color: var(--text); } -.wiz-page p { font-size: 13px; color: var(--text-3); margin-bottom: 20px; } -.wiz-icon { width: 72px; height: 72px; object-fit: contain; display: block; margin: 0 auto 12px; } -.wiz-icon-sm { width: 48px; height: 48px; object-fit: contain; display: block; margin: 0 auto 8px; } -.wiz-input { - width: 100%; padding: 10px 14px; border: 2px solid var(--border); border-radius: 12px; - font-size: 14px; font-family: var(--font); background: var(--bg); color: var(--text); outline: none; margin-bottom: 16px; text-align: center; -} -.wiz-input:focus { border-color: var(--accent); } -.wiz-next { - padding: 10px 32px; border: none; border-radius: 24px; background: var(--accent); color: #fff; - font-size: 14px; font-weight: 600; cursor: pointer; -} -.wiz-next:disabled { background: var(--border); color: var(--text-4); cursor: default; } -.wiz-next:hover:not(:disabled) { background: var(--accent-hover); } -.wiz-disclaimer-list { text-align: left; margin-bottom: 12px; padding-left: 20px; font-size: 12px; color: var(--text-2); line-height: 1.9; } -.wiz-link { display: block; margin-bottom: 16px; font-size: 12px; color: var(--accent); text-decoration: underline; cursor: pointer; } -.wiz-check { display: flex; align-items: center; gap: 6px; justify-content: center; font-size: 12px; color: var(--text-2); margin-bottom: 16px; cursor: pointer; } -.wiz-check input[type=checkbox] { width: 16px; height: 16px; cursor: pointer; } - -/* ── wizard: provider selection ── */ -.wiz-provider-cards { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; } -.wiz-prov-card { - padding: 16px 14px; border: 2px solid var(--border); border-radius: 14px; - cursor: pointer; text-align: left; transition: border-color .15s, box-shadow .15s; -} -.wiz-prov-card:hover { border-color: var(--accent); box-shadow: 0 2px 12px rgba(59,130,246,.12); } -.wiz-prov-card.selected { border-color: var(--accent); background: var(--accent-light); } -.wiz-prov-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } -.wiz-prov-icon { font-size: 24px; margin-bottom: 4px; } -.wiz-prov-name { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 2px; } -.wiz-prov-hint { font-size: 11px; color: var(--text-3); } - -/* ── wizard: custom provider ── */ -.wiz-field { margin-bottom: 16px; } -.wiz-hint { font-size: 11px; color: var(--text-4); margin-top: 2px; margin-bottom: 0; line-height: 1.4; } -.wiz-model-card { - background: var(--surface); border: 1px solid var(--border); border-radius: 14px; - padding: 14px; margin-bottom: 16px; text-align: left; -} -.wiz-model-card-title { - font-size: 13px; font-weight: 600; color: var(--text-2); margin-bottom: 10px; - padding-bottom: 8px; border-bottom: 1px solid var(--border); -} -.wiz-model-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } -.wiz-model-name { - flex: 1; padding: 6px 10px; border: 1px solid var(--border); border-radius: 8px; - font-size: 12px; font-family: var(--font); background: var(--card); color: var(--text); - outline: none; min-width: 0; -} -.wiz-model-name:focus { border-color: var(--accent); } -.wiz-toggle-group { display: flex; gap: 3px; flex-shrink: 0; } -.wiz-toggle-group .qs-btn { flex: none; padding: 4px 10px; font-size: 10px; border-radius: 6px; } -.wiz-model-del-btn { - width: 24px; height: 24px; border: none; border-radius: 50%; - background: transparent; color: var(--text-4); cursor: pointer; - font-size: 12px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; -} -.wiz-model-del-btn:hover { background: var(--danger); color: #fff; } -.wiz-add-model-btn { - display: block; width: 100%; padding: 6px; border: 1px dashed var(--border); - border-radius: 8px; background: transparent; color: var(--accent); font-size: 12px; - cursor: pointer; font-family: var(--font); -} -.wiz-add-model-btn:hover { background: var(--accent-light); border-style: solid; } - -/* ── 流式速度 & token 统计 ── */ -#token-stats { - font-size: 10px; color: var(--text-4); text-align: right; padding: 2px 0; min-height: 14px; -} - -/* ── AI 操作按钮 ── */ -.ai-actions { display: flex; gap: 8px; margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--border); } -.ai-actions button { - padding: 3px 10px; border: none; border-radius: 14px; font-size: 10px; cursor: pointer; - background: var(--surface); color: var(--text-3); -} -.ai-actions button:hover { background: var(--accent-light); color: var(--accent); } - -/* ── 斜杠命令菜单 ── */ -#cmd-popup { - display: none; position: absolute; bottom: 100%; left: 16px; right: 60px; - background: var(--card); border: 1px solid var(--border); border-radius: 12px; - box-shadow: 0 8px 32px rgba(0,0,0,.12); padding: 6px; z-index: 50; - max-height: 240px; overflow-y: auto; margin-bottom: 4px; -} -#cmd-popup.show { display: block; } -.cmd-item { - display: flex; align-items: center; gap: 8px; - padding: 8px 10px; border-radius: 8px; cursor: pointer; - font-size: 13px; color: var(--text-2); -} -.cmd-item:hover, .cmd-item.sel { background: var(--accent-light); color: var(--accent); } -.cmd-item .cmd-icon { font-size: 16px; width: 20px; text-align: center; flex-shrink: 0; } -.cmd-item .cmd-name { font-weight: 600; min-width: 80px; } -.cmd-item .cmd-desc { font-size: 11px; color: var(--text-4); flex: 1; } - -/* ── Easter Egg ── */ -#ee-overlay { - display: none; position: fixed; inset: 0; z-index: 999; - background: rgba(0,0,0,.7); align-items: center; justify-content: center; -} -#ee-overlay.show { display: flex; } -#ee-overlay img { - max-width: 80vw; max-height: 80vh; border-radius: 20px; - box-shadow: 0 0 60px rgba(255,255,255,.3); - animation: eePop .4s cubic-bezier(.34,1.56,.64,1); -} -@keyframes eePop { - from { transform: scale(0); opacity: 0; } - to { transform: scale(1); opacity: 1; } -} -.code-block { background: var(--code-bg); padding: 10px 14px; border-radius: 6px; font-family: var(--font-code); font-size: 12px; line-height: 1.5; overflow-x: auto; margin: 6px 0; white-space: pre; } -.inline-code { background: var(--code-bg); padding: 1px 4px; border-radius: 3px; font-family: var(--font-code); font-size: 12px; color: var(--accent); } diff --git a/DS code 2.0.1-0712-NEXT demo/renderer/css/theme.css b/DS code 2.0.1-0712-NEXT demo/renderer/css/theme.css deleted file mode 100644 index 496a03e..0000000 --- a/DS code 2.0.1-0712-NEXT demo/renderer/css/theme.css +++ /dev/null @@ -1,72 +0,0 @@ -/* DScode V2 — 设计令牌 + 深色模式 */ - -:root { - --bg: #F8F9FB; - --card: #FFFFFF; - --text: #111827; - --text-2: #374151; - --text-3: #6B7280; - --text-4: #9CA3AF; - --surface: #F0F2F5; - --accent: #3B82F6; - --accent-hover: #2563EB; - --accent-light: #DBEAFE; - --border: #E5E7EB; - --divider: #F3F4F6; - --success: #10B981; - --danger: #EF4444; - --code-bg: #F3F4F6; - --reasoning-text: #6B7280; - --scrollbar: #D1D5DB; - --shadow: 0 1px 3px rgba(0,0,0,0.08); - --radius-sm: 6px; - --radius-md: 10px; - --radius-lg: 16px; - --radius-pill: 20px; - --font: -apple-system, "SF Pro Display", "SF Pro Text", "PingFang SC", "Microsoft YaHei", sans-serif; - --font-code: "SF Mono", Menlo, Consolas, monospace; -} - -[data-theme="dark"] { - --bg: #0F172A; - --card: #1E293B; - --text: #F1F5F9; - --text-2: #CBD5E1; - --text-3: #94A3B8; - --text-4: #64748B; - --surface: #1A2332; - --accent: #60A5FA; - --accent-hover: #3B82F6; - --accent-light: #1E3A5F; - --border: #334155; - --divider: #1E293B; - --success: #34D399; - --danger: #F87171; - --code-bg: #1A2332; - --reasoning-text: #94A3B8; - --scrollbar: #334155; - --shadow: 0 1px 3px rgba(0,0,0,0.25); -} - -* { margin: 0; padding: 0; box-sizing: border-box; } - -body { - font-family: var(--font); - font-size: 14px; - background: var(--bg); - color: var(--text); - overflow: hidden; - height: 100vh; - -webkit-font-smoothing: antialiased; -} - -#app { - display: flex; - height: 100vh; -} - -/* Scrollbar */ -::-webkit-scrollbar { width: 6px; } -::-webkit-scrollbar-track { background: transparent; } -::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 3px; } -::-webkit-scrollbar-thumb:hover { background: var(--text-4); } diff --git a/DS code 2.0.1-0712-NEXT demo/renderer/index.html b/DS code 2.0.1-0712-NEXT demo/renderer/index.html deleted file mode 100644 index 9dfa501..0000000 --- a/DS code 2.0.1-0712-NEXT demo/renderer/index.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - -DS Code - - - - -
- - - - -
-
-
-
- DS Code -

DS Code

-

v2.0.1 NEXT — AI that writes code for you

-
-
模型V4 Pro
-
缓存命中--
-
本次成本--
-
账户余额--
-
-
-
-
- -
-
- · - · - -
-
- · - · - -
-
- -
-
- - - - -
- -
- - -
- - -
-
-

Quick Settings

-
-
-
-
- -
-
- - -
-
-

Settings

-
- - - -
- -
-
-
-
-
-
-
-
-
- - - - - - -
-
- - -
-
-
-
- DS Code -

Welcome to DS Code

-

AI coding assistant, always at your service

- -
- - - - - -
-
-
- -
🐳
- - - - - diff --git a/DS code 2.0.1-0712-NEXT demo/renderer/js/app.js b/DS code 2.0.1-0712-NEXT demo/renderer/js/app.js deleted file mode 100644 index 14bdce3..0000000 --- a/DS code 2.0.1-0712-NEXT demo/renderer/js/app.js +++ /dev/null @@ -1,531 +0,0 @@ -// DScode V2 v2.0.1-0712-NEXT -(function(){'use strict'; -var API='http://127.0.0.1:8765'; -var H=[],MAX_HISTORY=50,HIST_IDX=-1; -var cp='demo',cm='deepseek-v4-pro',cmd='normal',cperm='full',crea='high'; -var isDark=false,busy=false,curAI=null,cCost=0,cCache=0,cBal=0,cTok=0; -var sStart=0,sChars=0,todos=[]; -var DEFAULT_MODELS=[['deepseek-v4-flash','V4 Flash'],['deepseek-v4-pro','V4 Pro']]; -var availModels=DEFAULT_MODELS.slice(); -function $(id){return document.getElementById(id);} -function E(s){return(s||'').replace(/&/g,'&').replace(//g,'>');} - -// ── init ── -document.addEventListener('DOMContentLoaded',function(){ - setupInput();setupKeyboard();setupDragDrop(); - loadModels();loadCfg();loadProjects();updateStatus(); -}); - -// ── keyboard ── -function setupKeyboard(){ - document.addEventListener('keydown',function(e){ - var t=e.target; - if(t.tagName==='INPUT'||t.tagName==='TEXTAREA')return; - if(e.key==='Escape'){ - closeQuickSettings();closeDetailSettings(); - if($('wiz-overlay')&&$('wiz-overlay').classList.contains('show'))return; - } - if(e.ctrlKey||e.metaKey){ - if(e.key==='b'||e.key==='B'){e.preventDefault();toggleSidebar();} - if(e.key==='l'||e.key==='L'){e.preventDefault();var ib=$('input-box');if(ib)ib.focus();} - if(e.key==='n'||e.key==='N'){e.preventDefault();newChat();} - } - }); -} - -// ── drag & drop ── -function setupDragDrop(){ - var box=$('input-box');if(!box)return; - ['dragenter','dragover','dragleave','drop'].forEach(function(ev){ - box.addEventListener(ev,function(e){e.preventDefault();e.stopPropagation();}); - }); - box.addEventListener('drop',function(e){ - var files=e.dataTransfer.files; - if(files.length){var names=[];for(var i=0;i=0?Math.round(cCache)+'%':'--'],['wc-cost',cCost>=0?'¥'+cCost.toFixed(4):'--'],['wc-balance',cBal>0?'¥'+cBal.toFixed(2):'--']]; - s.forEach(function(p){var e=$(p[0]);if(e)e.textContent=p[1];}); -} -function updateStatus(){ - var s=[['st-model',cm.includes('flash')?'DeepSeek V4 Flash':'DeepSeek V4 Pro'],['st-reason',crea],['st-mode',({normal:'Std',plan:'plan',ultra:'ultra'})[cmd]||cmd],['st-cost',cCost>=0?'¥'+cCost.toFixed(4):'--'],['st-tokens','tokens '+(cTok||0)],['st-cache',cCache>=0?'Cache '+Math.round(cCache)+'%':'--']]; - s.forEach(function(p){var e=$(p[0]);if(e)e.textContent=p[1];}); -} - -// ── quick settings ── -function openQuickSettings(){buildQS();$('qs-overlay').classList.add('show');} -function closeQuickSettings(){$('qs-overlay').classList.remove('show');} -function buildQS(){ - var s=[ - {id:'qs-model',k:'model',c:cm,o:availModels}, - {id:'qs-reasoning',k:'reasoning_effort',c:crea,o:[['high','high'],['max','max']]}, - {id:'qs-permission',k:'permission_mode',c:cperm,o:[['reviewer','Reviewer'],['coder','Coder'],['full','Full']]}, - {id:'qs-mode',k:'chat_mode',c:cmd,o:[['normal','Standard'],['plan','plan'],['ultra','ultra']]} - ]; - s.forEach(function(s){ - var c=$(s.id);if(!c)return;c.innerHTML=''; - s.o.forEach(function(o){ - var b=document.createElement('button');b.className='qs-btn'+(o[0]===s.c?' on':''); - b.textContent=o[1]; - b.onclick=function(){var obj={};obj[s.k]=o[0];apiPut(obj);if(s.k==='model')cm=o[0];else if(s.k==='chat_mode')cmd=o[0];else if(s.k==='permission_mode')cperm=o[0];else crea=o[0];updateStatus();wcStats();buildQS();}; - c.appendChild(b); - }); - }); -} - -// ── detail settings ── -function openSettings(){switchDSTab('basic');$('ds-overlay').classList.add('show'); - apiGet('/api/config').then(function(c){if(!c)return;buildDSRows(c);fillDSCfg(c);});} -function closeDetailSettings(){$('ds-overlay').classList.remove('show');} -function fillDSCfg(c){ - if(!c)return; - var nn=$('ds-nickname');if(nn){if(c.nickname)nn.value=c.nickname;nn.setAttribute('data-saved',nn.value||'');} - var ak=$('ds-apikey');if(ak){ak.value=c.api_key_exists?'********':'';ak.setAttribute('data-saved',ak.value);} - var bu=$('ds-baseurl');if(bu){if(c.base_url)bu.value=c.base_url;bu.setAttribute('data-saved',bu.value||'');} -} -function saveDSField(key, value){ - var el=document.getElementById('ds-'+({nickname:'nickname',api_key:'apikey',base_url:'baseurl'}[key]||key)); - // 如果是 api_key 且 value 为空且之前是脱敏状态,不保存 - if(key==='api_key'&&!value&&el&&el.getAttribute('data-saved')==='********')return; - var saved=(el?el.getAttribute('data-saved'):null)||''; - if(String(value)===saved)return; - var o={};o[key]=value;apiPut(o); - if(el)el.setAttribute('data-saved',String(value)); - // 保存反馈 - var t=el?el.nextElementSibling:null; - if(t&&t.classList.contains('ds-saved')){t.textContent=I18N.t('saved_feedback');t.style.opacity='1';setTimeout(function(){t.style.opacity='0';},1500);} -} -function buildDSRows(c){ - if(!c)return; - buildDSRow('ds-language',[['zh','Chinese'],['en','English']],'',c.language||'zh'); - buildDSRow('ds-theme',[['light','Light'],['dark','Dark'],['system','System']],'',c.theme||'light'); - buildDSRow('ds-model', availModels, 'model', cm); - buildDSRow('ds-permission',[['reviewer','Reviewer'],['coder','Coder'],['full','Full']],'',c.permission_mode||'full'); -} -function buildDSRow(id,opts,k2,curVal){ - var c=$(id);if(!c)return;c.innerHTML=''; - opts.forEach(function(o){ - var b=document.createElement('button'); - b.className='qs-btn'+(o[0]===curVal?' on':''); - b.textContent=o[1]; - b.onclick=function(){ - var k=k2||(id==='ds-language'?'language':id==='ds-theme'?'theme':id==='ds-permission'?'permission_mode':id==='ds-model'?'model':''); - var o2={};o2[k]=o[0];apiPut(o2); - if(k==='language')I18N.setLang(o[0]); - if(k==='model')cm=o[0]; - // 更新高亮 - var btns=c.querySelectorAll('.qs-btn'); - btns.forEach(function(b2){b2.classList.remove('on');}); - b.classList.add('on'); - }; - c.appendChild(b); - }); -} -function switchDSTab(t){ - document.querySelectorAll('.ds-tab').forEach(function(t2){t2.classList.toggle('active',t2.getAttribute('data-tab')===t);}); - ['basic','provider','about'].forEach(function(t2){var p=$('ds-tab-'+t2);if(p)p.style.display=(t2===t)?'block':'none';}); -} -function addProvider(){alert(I18N.t('provider_coming_soon'));} - -// ── wizard ── -var wizPg=0; -var wizProvider='official'; -var wizCustomModels=[{name:'',context1m:true}]; -var wizModelCount=1; -var WIZ_SEQ=['wiz-p0','wiz-p-provider','wiz-p-custom','wiz-p1','wiz-p2','wiz-p3']; -function showWiz(i){ - for(var j=0;j0){ - o2.model=validModels[0].name.trim(); - o2.custom_models=validModels; - } - } - fetch(API+'/api/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(o2)}) - .then(function(){$('wiz-overlay').classList.remove('show');loadCfg();loadProjects();}) - .catch(function(){$('wiz-overlay').classList.remove('show');loadCfg();loadProjects();}); - } -} - -// ── wizard helpers ── -function selectProvider(type){ - wizProvider=type; - var off=$('wiz-prov-official'),cust=$('wiz-prov-custom'); - if(off)off.classList.toggle('selected',type==='official'); - if(cust)cust.classList.toggle('selected',type==='custom'); - var btn=$('wiz-btn-provider');if(btn)btn.disabled=false; -} -function updateCustomModelName(idx,name){ - if(idx=2)return; - wizModelCount++; - wizCustomModels.push({name:'',context1m:true}); - var idx=wizCustomModels.length-1; - var container=$('wiz-model-rows');if(!container)return; - var row=document.createElement('div');row.className='wiz-model-row';row.setAttribute('data-row',idx); - var inp=document.createElement('input');inp.type='text';inp.className='wiz-model-name'; - inp.setAttribute('data-i18n-placeholder','wiz_model_name_ph'); - inp.placeholder=I18N.t('wiz_model_name_ph'); - inp.oninput=function(){updateCustomModelName(idx,this.value);}; - var tg=document.createElement('div');tg.className='wiz-toggle-group';tg.id='wiz-toggle-'+idx; - var btn1M=document.createElement('button');btn1M.className='qs-btn on';btn1M.textContent='1M'; - btn1M.onclick=function(){updateCustomModelToggle(idx,true,btn1M);}; - var btnStd=document.createElement('button');btnStd.className='qs-btn';btnStd.textContent='Std'; - btnStd.onclick=function(){updateCustomModelToggle(idx,false,btnStd);}; - tg.appendChild(btn1M);tg.appendChild(btnStd); - var del=document.createElement('button');del.className='wiz-model-del-btn';del.innerHTML='✕'; - del.onclick=function(){removeCustomModelRow(idx,row);}; - row.appendChild(inp);row.appendChild(tg);row.appendChild(del); - container.appendChild(row); - var addBtn=$('wiz-add-model');if(addBtn&&wizModelCount>=2)addBtn.style.display='none'; -} -function removeCustomModelRow(idx,rowEl){ - wizCustomModels.splice(idx,1);wizModelCount--; - if(rowEl&&rowEl.parentElement)rowEl.parentElement.removeChild(rowEl); - var rows=document.querySelectorAll('#wiz-model-rows .wiz-model-row'); - for(var i=0;i_cmdsBaseLen){CMDS.pop();} - availModels.forEach(function(m){ - CMDS.push({cmd:'/model '+m[1], icon:'🔀', name:'/model '+m[1], - desc:'切换 '+m[1], zh:'切换 '+m[1], en:'Switch to '+m[1]}); - }); -} -var cmdIdx=-1,cmdList=[]; - -function showCmdPopup(){ - var box=$('input-box'),v=box?box.value:''; - if(!v.startsWith('/')||v.length<1){hideCmdPopup();return;} - var q=v.toLowerCase();cmdList=CMDS.filter(function(c){return c.cmd.toLowerCase().indexOf(q)===0;}); - if(cmdList.length===0){hideCmdPopup();return;} - var p=$('cmd-popup');if(!p)return; - p.innerHTML='';cmdIdx=-1; - var isEn=I18N.current==='en'; - cmdList.forEach(function(c,i){ - var row=document.createElement('div');row.className='cmd-item'; - row.innerHTML=''+c.icon+''+c.name+''+(isEn?c.en:c.zh)+''; - row.onclick=function(){selectCmd(i);}; - row.onmousedown=function(e){e.preventDefault();}; - p.appendChild(row); - }); - p.classList.add('show'); -} -function hideCmdPopup(){var p=$('cmd-popup');if(p){p.classList.remove('show');p.innerHTML='';}cmdIdx=-1;cmdList=[];} -function moveCmdSel(d){ - cmdIdx+=d;cmdIdx=Math.max(-1,Math.min(cmdList.length-1,cmdIdx)); - var rows=document.querySelectorAll('#cmd-popup .cmd-item'); - rows.forEach(function(r,i){r.classList.toggle('sel',i===cmdIdx);}); -} -function selectCmd(idx){ - if(idx<0||idx>=cmdList.length)return; - handleSlashCmd(cmdList[idx].cmd); - hideCmdPopup(); -} -function handleSlashCmd(cmd){ - if(cmd==='/dscodeisnice'){showEasterEgg();$('input-box').value='';updateSB();return;} - if(cmd==='/clear'){clearMsgs();$('input-box').value='';updateSB();return;} - if(cmd==='/help'){alert('DS Code v2.0.1\n/clear /plan /ultra /normal /dark /light /model /undo\n/dscodeisnice 🤫');return;} - if(cmd==='/dark'){isDark=false;toggleTheme();$('input-box').value='';updateSB();return;} - if(cmd==='/light'){isDark=true;toggleTheme();$('input-box').value='';updateSB();return;} - // dynamic /model