dsh-plugin-image-workbench 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +5 -3
  3. package/README.md +7 -3
  4. package/client.js +241 -241
  5. package/cordis.patch.yml +14 -14
  6. package/host.js +51 -0
  7. package/install.mjs +333 -387
  8. package/package.json +3 -2
  9. package/scripts/fetch-style-images.mjs +104 -0
  10. package/scripts/vendor-snapshot.py +154 -154
  11. package/server/access.py +26 -26
  12. package/server/app.py +1329 -1329
  13. package/server/catalog.py +147 -147
  14. package/server/composer.py +391 -391
  15. package/server/drafts.py +158 -158
  16. package/server/history.py +575 -575
  17. package/server/imaging.py +172 -172
  18. package/server/pool.py +1335 -1335
  19. package/server/pool_admin.py +921 -921
  20. package/server/pool_service.py +1073 -1073
  21. package/server/requirements.txt +6 -6
  22. package/server/smoke_check.py +402 -402
  23. package/server/static/access.html +202 -202
  24. package/vendor/chatgpt2api/source/.env.example +82 -82
  25. package/vendor/chatgpt2api/source/.python-version +1 -1
  26. package/vendor/chatgpt2api/source/LICENSE +21 -21
  27. package/vendor/chatgpt2api/source/README.md +391 -391
  28. package/vendor/chatgpt2api/source/VERSION +1 -1
  29. package/vendor/chatgpt2api/source/api/__init__.py +2 -2
  30. package/vendor/chatgpt2api/source/api/accounts.py +537 -537
  31. package/vendor/chatgpt2api/source/api/ai.py +217 -217
  32. package/vendor/chatgpt2api/source/api/app.py +62 -62
  33. package/vendor/chatgpt2api/source/api/errors.py +46 -46
  34. package/vendor/chatgpt2api/source/api/image_inputs.py +309 -309
  35. package/vendor/chatgpt2api/source/api/image_tasks.py +120 -120
  36. package/vendor/chatgpt2api/source/api/support.py +134 -134
  37. package/vendor/chatgpt2api/source/api/system.py +365 -365
  38. package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -50
  39. package/vendor/chatgpt2api/source/config.default.json +108 -108
  40. package/vendor/chatgpt2api/source/main.py +9 -9
  41. package/vendor/chatgpt2api/source/pyproject.toml +27 -27
  42. package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -186
  43. package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -163
  44. package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -13
  45. package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -385
  46. package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -129
  47. package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -92
  48. package/vendor/chatgpt2api/source/services/account_service.py +1757 -1757
  49. package/vendor/chatgpt2api/source/services/auth_service.py +253 -253
  50. package/vendor/chatgpt2api/source/services/backup_service.py +671 -671
  51. package/vendor/chatgpt2api/source/services/config.py +667 -667
  52. package/vendor/chatgpt2api/source/services/content_filter.py +242 -242
  53. package/vendor/chatgpt2api/source/services/cpa_service.py +315 -315
  54. package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -260
  55. package/vendor/chatgpt2api/source/services/image_service.py +376 -376
  56. package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -405
  57. package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -76
  58. package/vendor/chatgpt2api/source/services/image_task_service.py +550 -550
  59. package/vendor/chatgpt2api/source/services/log_service.py +337 -337
  60. package/vendor/chatgpt2api/source/services/model_service.py +182 -182
  61. package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -267
  62. package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -2763
  63. package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -32
  64. package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -2
  65. package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -307
  66. package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -238
  67. package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -1644
  68. package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -124
  69. package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -19
  70. package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -316
  71. package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -86
  72. package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -42
  73. package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -53
  74. package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -453
  75. package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -160
  76. package/vendor/chatgpt2api/source/services/proxy_service.py +648 -648
  77. package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -5
  78. package/vendor/chatgpt2api/source/services/storage/base.py +38 -38
  79. package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -194
  80. package/vendor/chatgpt2api/source/services/storage/factory.py +104 -104
  81. package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -183
  82. package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -95
  83. package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -531
  84. package/vendor/chatgpt2api/source/utils/helper.py +469 -469
  85. package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -346
  86. package/vendor/chatgpt2api/source/utils/log.py +110 -110
  87. package/vendor/chatgpt2api/source/utils/pkce.py +19 -19
  88. package/vendor/chatgpt2api/source/utils/pow.py +207 -207
  89. package/vendor/chatgpt2api/source/utils/sentinel.py +159 -159
  90. package/vendor/chatgpt2api/source/utils/turnstile.py +184 -184
  91. package/vendor/chatgpt2api/source/uv.lock +904 -904
  92. package/vendor/chatgpt2api/source/web/bun.lock +1501 -1501
  93. package/vendor/chatgpt2api/source/web/components.json +24 -24
  94. package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -17
  95. package/vendor/chatgpt2api/source/web/next.config.ts +40 -40
  96. package/vendor/chatgpt2api/source/web/package.json +58 -58
  97. package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -5
  98. package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -925
  99. package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -1307
  100. package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -224
  101. package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -444
  102. package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -9
  103. package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -9
  104. package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -172
  105. package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -169
  106. package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -39
  107. package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -59
  108. package/vendor/chatgpt2api/source/web/src/app/globals.css +230 -230
  109. package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -542
  110. package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -554
  111. package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -176
  112. package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -1799
  113. package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -732
  114. package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -50
  115. package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -101
  116. package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -356
  117. package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -30
  118. package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -341
  119. package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -447
  120. package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -71
  121. package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -491
  122. package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -108
  123. package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -169
  124. package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -185
  125. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -379
  126. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -127
  127. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -200
  128. package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -12
  129. package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -886
  130. package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -81
  131. package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -422
  132. package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -135
  133. package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -1069
  134. package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -49
  135. package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -24
  136. package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -434
  137. package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -46
  138. package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -18
  139. package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -14
  140. package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -263
  141. package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -256
  142. package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -41
  143. package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -59
  144. package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -46
  145. package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -61
  146. package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -27
  147. package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -267
  148. package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -132
  149. package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -13
  150. package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -19
  151. package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -24
  152. package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -149
  153. package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -38
  154. package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -175
  155. package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -143
  156. package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -29
  157. package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -91
  158. package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -269
  159. package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -18
  160. package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -127
  161. package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -6
  162. package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -85
  163. package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -926
  164. package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -26
  165. package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -25
  166. package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -93
  167. package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -89
  168. package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -6
  169. package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -112
  170. package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -33
  171. package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -318
  172. package/vendor/chatgpt2api/source/web/tsconfig.json +42 -42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-image-workbench",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "type": "module",
5
5
  "description": "Bluxe手绘工坊 · 生图工作台(图文 / 摄影):DSH 侧栏图像生成工作台,默认使用内置本地账号池,亦支持任意 OpenAI 兼容接口(Base URL / API Key / 模型);可自定义工作台名称与图片存储目录。",
6
6
  "main": "./host.js",
@@ -31,7 +31,8 @@
31
31
  "server/requirements.txt",
32
32
  "server/static",
33
33
  "vendor",
34
- "scripts/vendor-snapshot.py"
34
+ "scripts/vendor-snapshot.py",
35
+ "scripts/fetch-style-images.mjs"
35
36
  ],
36
37
  "engines": {
37
38
  "node": ">=20"
@@ -0,0 +1,104 @@
1
+ /**
2
+ * 样图下载器(风格 / 图型 / 主题色三类预览图)。
3
+ *
4
+ * 为什么要有这个文件:repo_images/ 里的 574 张样例图不进 npm 包(包体积会从
5
+ * 几百 KB 涨到十几 MB,而且图片本来就该放 CDN)。但工作台后端的 /img/{path}
6
+ * 在文件缺失时会返回一张渐变 SVG 占位图,于是全新安装打开「配色库 / 风格库」
7
+ * 全是色块 —— 看起来像装坏了。安装包里必须自带一次「把样图拉下来」的动作,
8
+ * 光靠 README 让用户手跑 `node install.mjs` 是不够的。
9
+ *
10
+ * 约定:
11
+ * - 由 host.js 在插件启动时后台调用(不阻塞工作台启动),失败只写日志;
12
+ * - install.mjs 也复用它,安装完顺手补齐,两处逻辑只有这一份;
13
+ * - 幂等:已存在且非空的文件直接跳过,重跑只补缺失项;
14
+ * - 只接受 image/* 响应,避免把 Gitee 的 302 登录页 HTML 写成 .webp 坏图。
15
+ */
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+
19
+ /** 样图源,按「CDN 单文件 → Gitee 镜像 → GitHub raw」顺序尝试。 */
20
+ export const IMAGE_SOURCES = [
21
+ 'https://img.handraw-style.com',
22
+ 'https://gitee.com/yang02010/handraw-style/raw/master',
23
+ 'https://raw.githubusercontent.com/yang0/handraw-style/main',
24
+ ];
25
+
26
+ /** catalog.json 里 img 字段形如 images/individual/FA/FA-001.webp,落到 repo_images/ 时去掉 images/ 前缀。 */
27
+ export function localPathOf(repoImages, rel) {
28
+ return path.join(repoImages, String(rel).replace(/^images\//, ''));
29
+ }
30
+
31
+ /** 从 catalog.json 收集需要下载的样图相对路径(去重)。 */
32
+ export function collectRels(serverDir) {
33
+ const file = path.join(serverDir, 'catalog.json');
34
+ const rels = [];
35
+ const cat = JSON.parse(fs.readFileSync(file, 'utf8'));
36
+ for (const key of ['styles', 'layouts', 'colors']) {
37
+ for (const item of cat[key] || []) if (item && item.img) rels.push(item.img);
38
+ }
39
+ return [...new Set(rels)];
40
+ }
41
+
42
+ /**
43
+ * 下载缺失样图。
44
+ * @param {{serverDir:string, log?:Function, concurrency?:number, timeoutMs?:number, sources?:string[]}} opts
45
+ * @returns {Promise<{ok:boolean, total:number, done:number, skip:number, fail:number, error?:string}>}
46
+ */
47
+ export async function fetchStyleImages(opts = {}) {
48
+ const serverDir = opts.serverDir;
49
+ const log = opts.log || (() => {});
50
+ const concurrency = Math.max(1, Math.min(24, opts.concurrency || 12));
51
+ const timeoutMs = opts.timeoutMs || 15000;
52
+ const sources = opts.sources || IMAGE_SOURCES;
53
+ if (!serverDir) return { ok: false, total: 0, done: 0, skip: 0, fail: 0, error: '缺少 serverDir' };
54
+
55
+ const repoImages = path.join(serverDir, 'repo_images');
56
+ let rels = [];
57
+ try {
58
+ rels = collectRels(serverDir);
59
+ } catch (e) {
60
+ return { ok: false, total: 0, done: 0, skip: 0, fail: 0,
61
+ error: '读取 catalog.json 失败:' + (e && e.message || e) };
62
+ }
63
+ if (!rels.length) return { ok: true, total: 0, done: 0, skip: 0, fail: 0 };
64
+
65
+ fs.mkdirSync(repoImages, { recursive: true });
66
+ const exists = p => { try { return fs.statSync(p).size > 0; } catch { return false; } };
67
+
68
+ const missing = rels.filter(rel => !exists(localPathOf(repoImages, rel)));
69
+ const skip = rels.length - missing.length;
70
+ if (!missing.length) return { ok: true, total: rels.length, done: 0, skip, fail: 0 };
71
+
72
+ log(`正在下载缺失样图 ${missing.length} 张(共 ${rels.length} 张,已存在 ${skip} 张)…`);
73
+ let done = 0, fail = 0;
74
+
75
+ const getOne = async rel => {
76
+ const dst = localPathOf(repoImages, rel);
77
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
78
+ for (const base of sources) {
79
+ const url = base.replace(/\/$/, '') + '/' + String(rel).replace(/^\/+/, '');
80
+ try {
81
+ const ac = new AbortController();
82
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
83
+ const res = await fetch(url, { signal: ac.signal, redirect: 'follow' });
84
+ clearTimeout(timer);
85
+ if (!res.ok) continue;
86
+ const type = (res.headers.get('content-type') || '').toLowerCase();
87
+ if (!type.startsWith('image/')) continue;
88
+ const buf = Buffer.from(await res.arrayBuffer());
89
+ if (buf.length < 200) continue;
90
+ fs.writeFileSync(dst, buf);
91
+ done += 1;
92
+ return;
93
+ } catch { /* 换下一个源 */ }
94
+ }
95
+ fail += 1;
96
+ };
97
+
98
+ for (let i = 0; i < missing.length; i += concurrency) {
99
+ await Promise.all(missing.slice(i, i + concurrency).map(getOne));
100
+ }
101
+
102
+ log(`样图下载结束:已下 ${done} 张,跳过 ${skip} 张,失败 ${fail} 张`);
103
+ return { ok: fail === 0, total: rels.length, done, skip, fail };
104
+ }
@@ -1,154 +1,154 @@
1
- """把本机一份 ChatGPT2API 号池源码,做成插件自带的 vendor 快照。
2
-
3
- 用途:工作台插件发布到别的机器时,不再要求对方先装任何第三方号池插件。
4
- 这个脚本只在「升级号池源码」时手动跑一次,日常运行完全不依赖它。
5
-
6
- 用法:
7
- python vendor-snapshot.py [源号池 home 目录]
8
-
9
- 默认源:~/.dsh/chatgpt2api-image
10
- 输出: <插件目录>/vendor/chatgpt2api/
11
-
12
- 严格排除:账号库 data/、虚拟环境 .venv/、日志、备份、缓存、明文密钥与代理配置。
13
- config.json 会被清洗成 config.default.json(auth-key / proxy / 云存储凭据一律置空)。
14
- """
15
- from __future__ import annotations
16
-
17
- import json
18
- import os
19
- import shutil
20
- import sys
21
- from pathlib import Path
22
-
23
- PLUGIN_DIR = Path(__file__).resolve().parent.parent
24
- OUT = PLUGIN_DIR / "vendor" / "chatgpt2api"
25
-
26
- # 只带运行需要的代码与静态资源,不带文档 / 测试 / 容器配置
27
- DIRS = ["api", "services", "utils", "web", "assets", "scripts"]
28
- FILES = ["main.py", "pyproject.toml", "uv.lock", ".python-version",
29
- ".env.example", "LICENSE", "README.md", "VERSION"]
30
-
31
- EXCLUDE_NAMES = {"__pycache__", ".git", ".codex", ".pytest_cache", ".mypy_cache"}
32
- EXCLUDE_SUFFIX = (".pyc", ".pyo", ".log", ".db", ".db-shm", ".db-wal", ".enc")
33
-
34
-
35
- def keep(p: Path) -> bool:
36
- n = p.name
37
- if n in EXCLUDE_NAMES or n.startswith(".dsh-"):
38
- return False
39
- if n.endswith(EXCLUDE_SUFFIX) or ".bak" in n or n.endswith(".tmp"):
40
- return False
41
- return True
42
-
43
-
44
- def clean_config(raw: dict) -> dict:
45
- """出厂默认配置:任何凭据、代理、本机状态都不带进插件包。"""
46
- d = json.loads(json.dumps(raw)) # 深拷贝
47
- d["auth-key"] = ""
48
- d["proxy"] = ""
49
- pr = d.get("proxy_runtime") or {}
50
- pr["enabled"] = False
51
- pr["proxy_url"] = ""
52
- pr["resource_proxy_url"] = ""
53
- cl = pr.get("clearance") or {}
54
- cl["cf_cookies"] = ""
55
- cl["cf_clearance"] = ""
56
- d["backup"] = {**(d.get("backup") or {}),
57
- "enabled": False, "access_key_id": "",
58
- "secret_access_key": "", "passphrase": ""}
59
- d["ai_review"] = {**(d.get("ai_review") or {}),
60
- "enabled": False, "base_url": "", "api_key": ""}
61
- img = d.get("image_storage") or {}
62
- img.update({"enabled": False, "webdav_username": "", "webdav_password": ""})
63
- return d
64
-
65
-
66
- def main() -> int:
67
- home = Path(sys.argv[1]) if len(sys.argv) > 1 else (
68
- Path(os.environ.get("DSH_HOME", Path.home() / ".dsh")) / "chatgpt2api-image")
69
- src = home / "source"
70
- if not (src / "main.py").is_file():
71
- print("找不到号池源码:" + str(src))
72
- return 1
73
- if OUT.resolve() == src.resolve():
74
- print("源目录不能是 vendor 输出目录")
75
- return 1
76
-
77
- if OUT.exists():
78
- shutil.rmtree(OUT)
79
- (OUT / "source").mkdir(parents=True)
80
- (OUT / "runtime").mkdir(parents=True)
81
-
82
- copied = 0
83
- for d in DIRS:
84
- s = src / d
85
- if not s.is_dir():
86
- continue
87
- shutil.copytree(s, OUT / "source" / d, ignore=lambda _, names: [
88
- n for n in names if not keep(Path(n))], dirs_exist_ok=True)
89
- copied += sum(1 for _ in (OUT / "source" / d).rglob("*") if _.is_file())
90
- for f in FILES:
91
- s = src / f
92
- if s.is_file() and keep(s):
93
- shutil.copy2(s, OUT / "source" / f)
94
- copied += 1
95
-
96
- cfg = src / "config.json"
97
- if cfg.is_file():
98
- try:
99
- data = clean_config(json.loads(cfg.read_text(encoding="utf-8")))
100
- (OUT / "source" / "config.default.json").write_text(
101
- json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
102
- copied += 1
103
- except Exception as e:
104
- print("警告:config.json 清洗失败,未生成默认配置:" + str(e))
105
-
106
- uv = ""
107
- for c in (home / "runtime" / "uv.exe", home / "runtime" / "uv",
108
- shutil.which("uv") or ""):
109
- if c and Path(c).is_file():
110
- uv = str(c)
111
- break
112
- if uv:
113
- shutil.copy2(uv, OUT / "runtime" / Path(uv).name)
114
- copied += 1
115
- else:
116
- print("警告:没找到 uv.exe,全新机器初始化时会退回系统 Python + pip")
117
-
118
- note = """# 内置号池运行时
119
-
120
- 本目录是 ChatGPT2API 号池服务端的**代码快照**,随「Bluxe手绘工坊 · 生图工作台」插件一起分发,
121
- 安装后由工作台后端(pool_service.py)释放到本机数据目录并自建虚拟环境。
122
-
123
- * 后端上游:[basketikun/chatgpt2api](https://github.com/basketikun/chatgpt2api),`source/VERSION` 与 `SOURCE.json` 记录快照版本;DSH 插件封装:[yang0/chatgpt2api-dsh](https://github.com/yang0/chatgpt2api-dsh)。
124
- * 许可:MIT License,Copyright (c) 2026 kunkun,LICENSE 原文见 `source/LICENSE`。本目录仅快照后端源码,不含 DSH 插件外壳与任何账号数据。
125
- * 已包含本工作台需要的两处补丁:round-robin 游标持久化、限流号排除;首次初始化时还会幂等复打一次。
126
- * **不含**任何账号、token、密钥、代理配置或图片数据。`source/config.default.json` 是清洗后的出厂默认配置。
127
- * 升级方式:改完本机号池源码后,重跑 `scripts/vendor-snapshot.py`,再重新发布插件。
128
-
129
- 分工:工作台插件负责「拉起并守护」号池进程;本目录只提供「能跑起来的代码」。
130
- 两者都不需要安装任何第三方 DSH 插件。
131
- """
132
- (OUT / "README.md").write_text(note, encoding="utf-8")
133
- (OUT / "SOURCE.json").write_text(json.dumps({
134
- "name": "chatgpt2api", "license": "MIT",
135
- "version": (src / "VERSION").read_text(encoding="utf-8").strip()
136
- if (src / "VERSION").is_file() else "",
137
- "python": (src / ".python-version").read_text(encoding="utf-8").strip()
138
- if (src / ".python-version").is_file() else "",
139
- "files": copied,
140
- "version_source": "source/VERSION",
141
- "copyright": "Copyright (c) 2026 kunkun",
142
- "upstream_backend": "https://github.com/basketikun/chatgpt2api",
143
- "upstream_dsh_plugin": "https://github.com/yang0/chatgpt2api-dsh",
144
- }, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
145
-
146
- size = sum(f.stat().st_size for f in OUT.rglob("*") if f.is_file())
147
- print("vendor 已生成:" + str(OUT))
148
- print("文件数 %d,体积 %.1f MB" % (copied, size / 1024 / 1024))
149
- print("含 uv 运行时:" + ("是" if uv else "否"))
150
- return 0
151
-
152
-
153
- if __name__ == "__main__":
154
- raise SystemExit(main())
1
+ """把本机一份 ChatGPT2API 号池源码,做成插件自带的 vendor 快照。
2
+
3
+ 用途:工作台插件发布到别的机器时,不再要求对方先装任何第三方号池插件。
4
+ 这个脚本只在「升级号池源码」时手动跑一次,日常运行完全不依赖它。
5
+
6
+ 用法:
7
+ python vendor-snapshot.py [源号池 home 目录]
8
+
9
+ 默认源:~/.dsh/chatgpt2api-image
10
+ 输出: <插件目录>/vendor/chatgpt2api/
11
+
12
+ 严格排除:账号库 data/、虚拟环境 .venv/、日志、备份、缓存、明文密钥与代理配置。
13
+ config.json 会被清洗成 config.default.json(auth-key / proxy / 云存储凭据一律置空)。
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import os
19
+ import shutil
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ PLUGIN_DIR = Path(__file__).resolve().parent.parent
24
+ OUT = PLUGIN_DIR / "vendor" / "chatgpt2api"
25
+
26
+ # 只带运行需要的代码与静态资源,不带文档 / 测试 / 容器配置
27
+ DIRS = ["api", "services", "utils", "web", "assets", "scripts"]
28
+ FILES = ["main.py", "pyproject.toml", "uv.lock", ".python-version",
29
+ ".env.example", "LICENSE", "README.md", "VERSION"]
30
+
31
+ EXCLUDE_NAMES = {"__pycache__", ".git", ".codex", ".pytest_cache", ".mypy_cache"}
32
+ EXCLUDE_SUFFIX = (".pyc", ".pyo", ".log", ".db", ".db-shm", ".db-wal", ".enc")
33
+
34
+
35
+ def keep(p: Path) -> bool:
36
+ n = p.name
37
+ if n in EXCLUDE_NAMES or n.startswith(".dsh-"):
38
+ return False
39
+ if n.endswith(EXCLUDE_SUFFIX) or ".bak" in n or n.endswith(".tmp"):
40
+ return False
41
+ return True
42
+
43
+
44
+ def clean_config(raw: dict) -> dict:
45
+ """出厂默认配置:任何凭据、代理、本机状态都不带进插件包。"""
46
+ d = json.loads(json.dumps(raw)) # 深拷贝
47
+ d["auth-key"] = ""
48
+ d["proxy"] = ""
49
+ pr = d.get("proxy_runtime") or {}
50
+ pr["enabled"] = False
51
+ pr["proxy_url"] = ""
52
+ pr["resource_proxy_url"] = ""
53
+ cl = pr.get("clearance") or {}
54
+ cl["cf_cookies"] = ""
55
+ cl["cf_clearance"] = ""
56
+ d["backup"] = {**(d.get("backup") or {}),
57
+ "enabled": False, "access_key_id": "",
58
+ "secret_access_key": "", "passphrase": ""}
59
+ d["ai_review"] = {**(d.get("ai_review") or {}),
60
+ "enabled": False, "base_url": "", "api_key": ""}
61
+ img = d.get("image_storage") or {}
62
+ img.update({"enabled": False, "webdav_username": "", "webdav_password": ""})
63
+ return d
64
+
65
+
66
+ def main() -> int:
67
+ home = Path(sys.argv[1]) if len(sys.argv) > 1 else (
68
+ Path(os.environ.get("DSH_HOME", Path.home() / ".dsh")) / "chatgpt2api-image")
69
+ src = home / "source"
70
+ if not (src / "main.py").is_file():
71
+ print("找不到号池源码:" + str(src))
72
+ return 1
73
+ if OUT.resolve() == src.resolve():
74
+ print("源目录不能是 vendor 输出目录")
75
+ return 1
76
+
77
+ if OUT.exists():
78
+ shutil.rmtree(OUT)
79
+ (OUT / "source").mkdir(parents=True)
80
+ (OUT / "runtime").mkdir(parents=True)
81
+
82
+ copied = 0
83
+ for d in DIRS:
84
+ s = src / d
85
+ if not s.is_dir():
86
+ continue
87
+ shutil.copytree(s, OUT / "source" / d, ignore=lambda _, names: [
88
+ n for n in names if not keep(Path(n))], dirs_exist_ok=True)
89
+ copied += sum(1 for _ in (OUT / "source" / d).rglob("*") if _.is_file())
90
+ for f in FILES:
91
+ s = src / f
92
+ if s.is_file() and keep(s):
93
+ shutil.copy2(s, OUT / "source" / f)
94
+ copied += 1
95
+
96
+ cfg = src / "config.json"
97
+ if cfg.is_file():
98
+ try:
99
+ data = clean_config(json.loads(cfg.read_text(encoding="utf-8")))
100
+ (OUT / "source" / "config.default.json").write_text(
101
+ json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
102
+ copied += 1
103
+ except Exception as e:
104
+ print("警告:config.json 清洗失败,未生成默认配置:" + str(e))
105
+
106
+ uv = ""
107
+ for c in (home / "runtime" / "uv.exe", home / "runtime" / "uv",
108
+ shutil.which("uv") or ""):
109
+ if c and Path(c).is_file():
110
+ uv = str(c)
111
+ break
112
+ if uv:
113
+ shutil.copy2(uv, OUT / "runtime" / Path(uv).name)
114
+ copied += 1
115
+ else:
116
+ print("警告:没找到 uv.exe,全新机器初始化时会退回系统 Python + pip")
117
+
118
+ note = """# 内置号池运行时
119
+
120
+ 本目录是 ChatGPT2API 号池服务端的**代码快照**,随「Bluxe手绘工坊 · 生图工作台」插件一起分发,
121
+ 安装后由工作台后端(pool_service.py)释放到本机数据目录并自建虚拟环境。
122
+
123
+ * 后端上游:[basketikun/chatgpt2api](https://github.com/basketikun/chatgpt2api),`source/VERSION` 与 `SOURCE.json` 记录快照版本;DSH 插件封装:[yang0/chatgpt2api-dsh](https://github.com/yang0/chatgpt2api-dsh)。
124
+ * 许可:MIT License,Copyright (c) 2026 kunkun,LICENSE 原文见 `source/LICENSE`。本目录仅快照后端源码,不含 DSH 插件外壳与任何账号数据。
125
+ * 已包含本工作台需要的两处补丁:round-robin 游标持久化、限流号排除;首次初始化时还会幂等复打一次。
126
+ * **不含**任何账号、token、密钥、代理配置或图片数据。`source/config.default.json` 是清洗后的出厂默认配置。
127
+ * 升级方式:改完本机号池源码后,重跑 `scripts/vendor-snapshot.py`,再重新发布插件。
128
+
129
+ 分工:工作台插件负责「拉起并守护」号池进程;本目录只提供「能跑起来的代码」。
130
+ 两者都不需要安装任何第三方 DSH 插件。
131
+ """
132
+ (OUT / "README.md").write_text(note, encoding="utf-8")
133
+ (OUT / "SOURCE.json").write_text(json.dumps({
134
+ "name": "chatgpt2api", "license": "MIT",
135
+ "version": (src / "VERSION").read_text(encoding="utf-8").strip()
136
+ if (src / "VERSION").is_file() else "",
137
+ "python": (src / ".python-version").read_text(encoding="utf-8").strip()
138
+ if (src / ".python-version").is_file() else "",
139
+ "files": copied,
140
+ "version_source": "source/VERSION",
141
+ "copyright": "Copyright (c) 2026 kunkun",
142
+ "upstream_backend": "https://github.com/basketikun/chatgpt2api",
143
+ "upstream_dsh_plugin": "https://github.com/yang0/chatgpt2api-dsh",
144
+ }, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
145
+
146
+ size = sum(f.stat().st_size for f in OUT.rglob("*") if f.is_file())
147
+ print("vendor 已生成:" + str(OUT))
148
+ print("文件数 %d,体积 %.1f MB" % (copied, size / 1024 / 1024))
149
+ print("含 uv 运行时:" + ("是" if uv else "否"))
150
+ return 0
151
+
152
+
153
+ if __name__ == "__main__":
154
+ raise SystemExit(main())
package/server/access.py CHANGED
@@ -1,26 +1,26 @@
1
- """本地版访问控制:单机 127.0.0.1 单人使用,不需要验证码审批。
2
-
3
- 保留与 VPS 版相同的接口签名,让 app.py 一行都不用改就能加载。
4
- VPS 原件已备份为 access.py.vps_original(其顶层 import fcntl 在 Windows 不存在)。
5
- """
6
- from __future__ import annotations
7
-
8
- SESSION_TTL = 7 * 86400
9
- LOCAL_MODE = True
10
-
11
-
12
- def check_session(token: str) -> bool:
13
- """本地版没有会话概念,永远放行。"""
14
- return True
15
-
16
-
17
- def request_code(email: str, ip: str, ua: str = "") -> dict:
18
- return {"ok": True, "msg": "本地版无需申请,请直接返回工作台"}
19
-
20
-
21
- def verify_code(email: str, code: str, ip: str) -> dict:
22
- return {"ok": True, "msg": "本地版无需验证码", "token": "local"}
23
-
24
-
25
- def master_login(password: str, ip: str) -> dict:
26
- return {"ok": True, "msg": "本地版无需口令", "token": "local"}
1
+ """本地版访问控制:单机 127.0.0.1 单人使用,不需要验证码审批。
2
+
3
+ 保留与 VPS 版相同的接口签名,让 app.py 一行都不用改就能加载。
4
+ VPS 原件已备份为 access.py.vps_original(其顶层 import fcntl 在 Windows 不存在)。
5
+ """
6
+ from __future__ import annotations
7
+
8
+ SESSION_TTL = 7 * 86400
9
+ LOCAL_MODE = True
10
+
11
+
12
+ def check_session(token: str) -> bool:
13
+ """本地版没有会话概念,永远放行。"""
14
+ return True
15
+
16
+
17
+ def request_code(email: str, ip: str, ua: str = "") -> dict:
18
+ return {"ok": True, "msg": "本地版无需申请,请直接返回工作台"}
19
+
20
+
21
+ def verify_code(email: str, code: str, ip: str) -> dict:
22
+ return {"ok": True, "msg": "本地版无需验证码", "token": "local"}
23
+
24
+
25
+ def master_login(password: str, ip: str) -> dict:
26
+ return {"ok": True, "msg": "本地版无需口令", "token": "local"}