dsh-plugin-image-workbench 1.1.5 → 1.1.7

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 (173) 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 +1371 -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/server/static/index.html +15 -0
  25. package/vendor/chatgpt2api/source/.env.example +82 -82
  26. package/vendor/chatgpt2api/source/.python-version +1 -1
  27. package/vendor/chatgpt2api/source/LICENSE +21 -21
  28. package/vendor/chatgpt2api/source/README.md +391 -391
  29. package/vendor/chatgpt2api/source/VERSION +1 -1
  30. package/vendor/chatgpt2api/source/api/__init__.py +2 -2
  31. package/vendor/chatgpt2api/source/api/accounts.py +537 -537
  32. package/vendor/chatgpt2api/source/api/ai.py +217 -217
  33. package/vendor/chatgpt2api/source/api/app.py +62 -62
  34. package/vendor/chatgpt2api/source/api/errors.py +46 -46
  35. package/vendor/chatgpt2api/source/api/image_inputs.py +309 -309
  36. package/vendor/chatgpt2api/source/api/image_tasks.py +120 -120
  37. package/vendor/chatgpt2api/source/api/support.py +134 -134
  38. package/vendor/chatgpt2api/source/api/system.py +365 -365
  39. package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -50
  40. package/vendor/chatgpt2api/source/config.default.json +108 -108
  41. package/vendor/chatgpt2api/source/main.py +9 -9
  42. package/vendor/chatgpt2api/source/pyproject.toml +27 -27
  43. package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -186
  44. package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -163
  45. package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -13
  46. package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -385
  47. package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -129
  48. package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -92
  49. package/vendor/chatgpt2api/source/services/account_service.py +1757 -1757
  50. package/vendor/chatgpt2api/source/services/auth_service.py +253 -253
  51. package/vendor/chatgpt2api/source/services/backup_service.py +671 -671
  52. package/vendor/chatgpt2api/source/services/config.py +667 -667
  53. package/vendor/chatgpt2api/source/services/content_filter.py +242 -242
  54. package/vendor/chatgpt2api/source/services/cpa_service.py +315 -315
  55. package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -260
  56. package/vendor/chatgpt2api/source/services/image_service.py +376 -376
  57. package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -405
  58. package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -76
  59. package/vendor/chatgpt2api/source/services/image_task_service.py +550 -550
  60. package/vendor/chatgpt2api/source/services/log_service.py +337 -337
  61. package/vendor/chatgpt2api/source/services/model_service.py +182 -182
  62. package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -267
  63. package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -2763
  64. package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -32
  65. package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -2
  66. package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -307
  67. package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -238
  68. package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -1644
  69. package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -124
  70. package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -19
  71. package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -316
  72. package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -86
  73. package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -42
  74. package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -53
  75. package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -453
  76. package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -160
  77. package/vendor/chatgpt2api/source/services/proxy_service.py +648 -648
  78. package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -5
  79. package/vendor/chatgpt2api/source/services/storage/base.py +38 -38
  80. package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -194
  81. package/vendor/chatgpt2api/source/services/storage/factory.py +104 -104
  82. package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -183
  83. package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -95
  84. package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -531
  85. package/vendor/chatgpt2api/source/utils/helper.py +469 -469
  86. package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -346
  87. package/vendor/chatgpt2api/source/utils/log.py +110 -110
  88. package/vendor/chatgpt2api/source/utils/pkce.py +19 -19
  89. package/vendor/chatgpt2api/source/utils/pow.py +207 -207
  90. package/vendor/chatgpt2api/source/utils/sentinel.py +159 -159
  91. package/vendor/chatgpt2api/source/utils/turnstile.py +184 -184
  92. package/vendor/chatgpt2api/source/uv.lock +904 -904
  93. package/vendor/chatgpt2api/source/web/bun.lock +1501 -1501
  94. package/vendor/chatgpt2api/source/web/components.json +24 -24
  95. package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -17
  96. package/vendor/chatgpt2api/source/web/next.config.ts +40 -40
  97. package/vendor/chatgpt2api/source/web/package.json +58 -58
  98. package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -5
  99. package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -925
  100. package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -1307
  101. package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -224
  102. package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -444
  103. package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -9
  104. package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -9
  105. package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -172
  106. package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -169
  107. package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -39
  108. package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -59
  109. package/vendor/chatgpt2api/source/web/src/app/globals.css +230 -230
  110. package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -542
  111. package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -554
  112. package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -176
  113. package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -1799
  114. package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -732
  115. package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -50
  116. package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -101
  117. package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -356
  118. package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -30
  119. package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -341
  120. package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -447
  121. package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -71
  122. package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -491
  123. package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -108
  124. package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -169
  125. package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -185
  126. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -379
  127. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -127
  128. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -200
  129. package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -12
  130. package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -886
  131. package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -81
  132. package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -422
  133. package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -135
  134. package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -1069
  135. package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -49
  136. package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -24
  137. package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -434
  138. package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -46
  139. package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -18
  140. package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -14
  141. package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -263
  142. package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -256
  143. package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -41
  144. package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -59
  145. package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -46
  146. package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -61
  147. package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -27
  148. package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -267
  149. package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -132
  150. package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -13
  151. package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -19
  152. package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -24
  153. package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -149
  154. package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -38
  155. package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -175
  156. package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -143
  157. package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -29
  158. package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -91
  159. package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -269
  160. package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -18
  161. package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -127
  162. package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -6
  163. package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -85
  164. package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -926
  165. package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -26
  166. package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -25
  167. package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -93
  168. package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -89
  169. package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -6
  170. package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -112
  171. package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -33
  172. package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -318
  173. 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.7",
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"}