dsh-plugin-image-workbench 0.0.0-stage → 1.1.5

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 (185) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +256 -0
  3. package/README.md +256 -2
  4. package/client.js +241 -0
  5. package/cordis.patch.yml +14 -0
  6. package/host.js +641 -0
  7. package/install.mjs +387 -0
  8. package/package.json +57 -4
  9. package/scripts/vendor-snapshot.py +154 -0
  10. package/server/access.py +26 -0
  11. package/server/app.py +1329 -0
  12. package/server/catalog.json +1 -0
  13. package/server/catalog.py +147 -0
  14. package/server/composer.py +391 -0
  15. package/server/drafts.py +158 -0
  16. package/server/history.py +575 -0
  17. package/server/imaging.py +172 -0
  18. package/server/pool.py +1335 -0
  19. package/server/pool_admin.py +921 -0
  20. package/server/pool_service.py +1073 -0
  21. package/server/requirements.txt +6 -0
  22. package/server/smoke_check.py +402 -0
  23. package/server/static/access.html +202 -0
  24. package/server/static/index.html +4725 -0
  25. package/vendor/chatgpt2api/README.md +13 -0
  26. package/vendor/chatgpt2api/SOURCE.json +11 -0
  27. package/vendor/chatgpt2api/runtime/uv.exe +0 -0
  28. package/vendor/chatgpt2api/source/.env.example +82 -0
  29. package/vendor/chatgpt2api/source/.python-version +1 -0
  30. package/vendor/chatgpt2api/source/LICENSE +21 -0
  31. package/vendor/chatgpt2api/source/README.md +391 -0
  32. package/vendor/chatgpt2api/source/VERSION +1 -0
  33. package/vendor/chatgpt2api/source/api/__init__.py +2 -0
  34. package/vendor/chatgpt2api/source/api/accounts.py +537 -0
  35. package/vendor/chatgpt2api/source/api/ai.py +217 -0
  36. package/vendor/chatgpt2api/source/api/app.py +62 -0
  37. package/vendor/chatgpt2api/source/api/errors.py +46 -0
  38. package/vendor/chatgpt2api/source/api/image_inputs.py +309 -0
  39. package/vendor/chatgpt2api/source/api/image_tasks.py +120 -0
  40. package/vendor/chatgpt2api/source/api/support.py +134 -0
  41. package/vendor/chatgpt2api/source/api/system.py +365 -0
  42. package/vendor/chatgpt2api/source/assets/atlascloud.svg +1 -0
  43. package/vendor/chatgpt2api/source/assets/ipwo.png +0 -0
  44. package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -0
  45. package/vendor/chatgpt2api/source/config.default.json +108 -0
  46. package/vendor/chatgpt2api/source/main.py +9 -0
  47. package/vendor/chatgpt2api/source/pyproject.toml +27 -0
  48. package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -0
  49. package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -0
  50. package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -0
  51. package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -0
  52. package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -0
  53. package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -0
  54. package/vendor/chatgpt2api/source/services/__init__.py +0 -0
  55. package/vendor/chatgpt2api/source/services/account_service.py +1757 -0
  56. package/vendor/chatgpt2api/source/services/auth_service.py +253 -0
  57. package/vendor/chatgpt2api/source/services/backup_service.py +671 -0
  58. package/vendor/chatgpt2api/source/services/config.py +667 -0
  59. package/vendor/chatgpt2api/source/services/content_filter.py +242 -0
  60. package/vendor/chatgpt2api/source/services/cpa_service.py +315 -0
  61. package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -0
  62. package/vendor/chatgpt2api/source/services/image_service.py +376 -0
  63. package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -0
  64. package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -0
  65. package/vendor/chatgpt2api/source/services/image_task_service.py +550 -0
  66. package/vendor/chatgpt2api/source/services/log_service.py +337 -0
  67. package/vendor/chatgpt2api/source/services/model_service.py +182 -0
  68. package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -0
  69. package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -0
  70. package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -0
  71. package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -0
  72. package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -0
  73. package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -0
  74. package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -0
  75. package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -0
  76. package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -0
  77. package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -0
  78. package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -0
  79. package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -0
  80. package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -0
  81. package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -0
  82. package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -0
  83. package/vendor/chatgpt2api/source/services/proxy_service.py +648 -0
  84. package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -0
  85. package/vendor/chatgpt2api/source/services/storage/base.py +38 -0
  86. package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -0
  87. package/vendor/chatgpt2api/source/services/storage/factory.py +104 -0
  88. package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -0
  89. package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -0
  90. package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -0
  91. package/vendor/chatgpt2api/source/utils/__init__.py +0 -0
  92. package/vendor/chatgpt2api/source/utils/helper.py +469 -0
  93. package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -0
  94. package/vendor/chatgpt2api/source/utils/log.py +110 -0
  95. package/vendor/chatgpt2api/source/utils/pkce.py +19 -0
  96. package/vendor/chatgpt2api/source/utils/pow.py +207 -0
  97. package/vendor/chatgpt2api/source/utils/sentinel.py +159 -0
  98. package/vendor/chatgpt2api/source/utils/turnstile.py +184 -0
  99. package/vendor/chatgpt2api/source/uv.lock +904 -0
  100. package/vendor/chatgpt2api/source/web/bun.lock +1501 -0
  101. package/vendor/chatgpt2api/source/web/components.json +24 -0
  102. package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -0
  103. package/vendor/chatgpt2api/source/web/next.config.ts +40 -0
  104. package/vendor/chatgpt2api/source/web/package.json +58 -0
  105. package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -0
  106. package/vendor/chatgpt2api/source/web/public/github.svg +1 -0
  107. package/vendor/chatgpt2api/source/web/public/next.svg +1 -0
  108. package/vendor/chatgpt2api/source/web/public/openai.svg +1 -0
  109. package/vendor/chatgpt2api/source/web/public/vercel.svg +1 -0
  110. package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -0
  111. package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -0
  112. package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -0
  113. package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -0
  114. package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -0
  115. package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -0
  116. package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -0
  117. package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -0
  118. package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -0
  119. package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -0
  120. package/vendor/chatgpt2api/source/web/src/app/favicon.ico +0 -0
  121. package/vendor/chatgpt2api/source/web/src/app/globals.css +231 -0
  122. package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -0
  123. package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -0
  124. package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -0
  125. package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -0
  126. package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -0
  127. package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -0
  128. package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -0
  129. package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -0
  130. package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -0
  131. package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -0
  132. package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -0
  133. package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -0
  134. package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -0
  135. package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -0
  136. package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -0
  137. package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -0
  138. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -0
  139. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -0
  140. package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -0
  141. package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -0
  142. package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -0
  143. package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -0
  144. package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -0
  145. package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -0
  146. package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -0
  147. package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -0
  148. package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -0
  149. package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -0
  150. package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -0
  151. package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -0
  152. package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -0
  153. package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -0
  154. package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -0
  155. package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -0
  156. package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -0
  157. package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -0
  158. package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -0
  159. package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -0
  160. package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -0
  161. package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -0
  162. package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -0
  163. package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -0
  164. package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -0
  165. package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -0
  166. package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -0
  167. package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -0
  168. package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -0
  169. package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -0
  170. package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -0
  171. package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -0
  172. package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -0
  173. package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -0
  174. package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -0
  175. package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -0
  176. package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -0
  177. package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -0
  178. package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -0
  179. package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -0
  180. package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -0
  181. package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -0
  182. package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -0
  183. package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -0
  184. package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -0
  185. package/vendor/chatgpt2api/source/web/tsconfig.json +42 -0
@@ -0,0 +1,491 @@
1
+ "use client";
2
+
3
+ import { Cloud, LoaderCircle, PlugZap, RefreshCw, Save } from "lucide-react";
4
+ import { useState } from "react";
5
+ import { toast } from "sonner";
6
+
7
+ import { Button } from "@/components/ui/button";
8
+ import { Card, CardContent } from "@/components/ui/card";
9
+ import { Checkbox } from "@/components/ui/checkbox";
10
+ import { Input } from "@/components/ui/input";
11
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
12
+ import { Textarea } from "@/components/ui/textarea";
13
+ import type { ImageStorageMode } from "@/lib/api";
14
+ import { testProxy, type ProxyTestResult } from "@/lib/api";
15
+
16
+ import { useSettingsStore } from "../store";
17
+
18
+ export function ConfigCard() {
19
+ const [isTestingProxy, setIsTestingProxy] = useState(false);
20
+ const [proxyTestResult, setProxyTestResult] = useState<ProxyTestResult | null>(null);
21
+ const logLevelOptions = ["debug", "info", "warning", "error"];
22
+ const config = useSettingsStore((state) => state.config);
23
+ const isLoadingConfig = useSettingsStore((state) => state.isLoadingConfig);
24
+ const isSavingConfig = useSettingsStore((state) => state.isSavingConfig);
25
+ const setRefreshAccountIntervalMinute = useSettingsStore((state) => state.setRefreshAccountIntervalMinute);
26
+ const setImageRetentionDays = useSettingsStore((state) => state.setImageRetentionDays);
27
+ const setImagePollTimeoutSecs = useSettingsStore((state) => state.setImagePollTimeoutSecs);
28
+ const setImageAccountConcurrency = useSettingsStore((state) => state.setImageAccountConcurrency);
29
+ const setImageSettleEnabled = useSettingsStore((state) => state.setImageSettleEnabled);
30
+ const setImageRemoveConversationAfterResult = useSettingsStore((state) => state.setImageRemoveConversationAfterResult);
31
+ const setImageRemoveConversationAlways = useSettingsStore((state) => state.setImageRemoveConversationAlways);
32
+ const setImageSettleSecs = useSettingsStore((state) => state.setImageSettleSecs);
33
+ const setImageTimeoutRetrySecs = useSettingsStore((state) => state.setImageTimeoutRetrySecs);
34
+ const setAutoRemoveInvalidAccounts = useSettingsStore((state) => state.setAutoRemoveInvalidAccounts);
35
+ const setAutoRemoveRateLimitedAccounts = useSettingsStore((state) => state.setAutoRemoveRateLimitedAccounts);
36
+ const setAutoReloginAfterRefresh = useSettingsStore((state) => state.setAutoReloginAfterRefresh);
37
+ const setLogLevel = useSettingsStore((state) => state.setLogLevel);
38
+ const setProxy = useSettingsStore((state) => state.setProxy);
39
+ const setBaseUrl = useSettingsStore((state) => state.setBaseUrl);
40
+ const setGlobalSystemPrompt = useSettingsStore((state) => state.setGlobalSystemPrompt);
41
+ const setDefaultUpstreamModelName = useSettingsStore((state) => state.setDefaultUpstreamModelName);
42
+ const setDefaultThinkingEffort = useSettingsStore((state) => state.setDefaultThinkingEffort);
43
+ const setSensitiveWordsText = useSettingsStore((state) => state.setSensitiveWordsText);
44
+ const setAIReviewField = useSettingsStore((state) => state.setAIReviewField);
45
+ const setImageStorageField = useSettingsStore((state) => state.setImageStorageField);
46
+ const testImageStorage = useSettingsStore((state) => state.testImageStorage);
47
+ const syncImagesToWebDAV = useSettingsStore((state) => state.syncImagesToWebDAV);
48
+ const isTestingImageStorage = useSettingsStore((state) => state.isTestingImageStorage);
49
+ const isSyncingImageStorage = useSettingsStore((state) => state.isSyncingImageStorage);
50
+ const saveConfig = useSettingsStore((state) => state.saveConfig);
51
+
52
+ const handleTestProxy = async () => {
53
+ const candidate = String(config?.proxy || "").trim();
54
+ if (!candidate) {
55
+ toast.error("请先填写代理地址");
56
+ return;
57
+ }
58
+ setIsTestingProxy(true);
59
+ setProxyTestResult(null);
60
+ try {
61
+ const data = await testProxy(candidate);
62
+ setProxyTestResult(data.result);
63
+ if (data.result.ok) {
64
+ toast.success(`代理可用(${data.result.latency_ms} ms,HTTP ${data.result.status})`);
65
+ } else {
66
+ toast.error(`代理不可用:${data.result.error ?? "未知错误"}`);
67
+ }
68
+ } catch (error) {
69
+ toast.error(error instanceof Error ? error.message : "测试代理失败");
70
+ } finally {
71
+ setIsTestingProxy(false);
72
+ }
73
+ };
74
+
75
+ if (isLoadingConfig) {
76
+ return (
77
+ <Card className="rounded-2xl border-white/80 bg-white/90 shadow-sm">
78
+ <CardContent className="flex items-center justify-center p-10">
79
+ <LoaderCircle className="size-5 animate-spin text-stone-400" />
80
+ </CardContent>
81
+ </Card>
82
+ );
83
+ }
84
+
85
+ return (
86
+ <Card className="rounded-2xl border-white/80 bg-white/90 shadow-sm">
87
+ <CardContent className="space-y-4 p-6">
88
+ <div className="rounded-xl border border-stone-200 bg-stone-50 px-4 py-3 text-sm leading-6 text-stone-600">
89
+ 管理员登录密钥继续从部署配置读取,不再在此页面展示;如需分发给其他人,请在下方创建普通用户密钥。
90
+ </div>
91
+ <div className="grid gap-4 md:grid-cols-2">
92
+ <div className="space-y-2">
93
+ <label className="text-sm text-stone-700">账号刷新间隔</label>
94
+ <Input
95
+ value={String(config?.refresh_account_interval_minute || "")}
96
+ onChange={(event) => setRefreshAccountIntervalMinute(event.target.value)}
97
+ placeholder="分钟"
98
+ className="h-10 rounded-xl border-stone-200 bg-white"
99
+ />
100
+ <p className="text-xs text-stone-500">单位分钟,控制账号自动刷新频率。</p>
101
+ </div>
102
+ <div className="space-y-2">
103
+ <label className="text-sm text-stone-700">全局代理</label>
104
+ <Input
105
+ value={String(config?.proxy || "")}
106
+ onChange={(event) => {
107
+ setProxy(event.target.value);
108
+ setProxyTestResult(null);
109
+ }}
110
+ placeholder="http://127.0.0.1:7890"
111
+ className="h-10 rounded-xl border-stone-200 bg-white"
112
+ />
113
+ <p className="text-xs leading-5 text-stone-500">
114
+ 留空表示不使用代理。支持协议://账号:密码@主机:端口,也可直接粘贴代理商的 主机:端口:账号:密码;示例 http://user:pass@127.0.0.1:7890、127.0.0.1:7890:user:pass。账号密码含 @/: 等特殊字符时需 URL 编码。
115
+ </p>
116
+ {proxyTestResult ? (
117
+ <div
118
+ className={`rounded-xl border px-3 py-2 text-xs leading-6 ${
119
+ proxyTestResult.ok
120
+ ? "border-emerald-200 bg-emerald-50 text-emerald-800"
121
+ : "border-rose-200 bg-rose-50 text-rose-800"
122
+ }`}
123
+ >
124
+ {proxyTestResult.ok
125
+ ? `代理可用:HTTP ${proxyTestResult.status},用时 ${proxyTestResult.latency_ms} ms`
126
+ : `代理不可用:${proxyTestResult.error ?? "未知错误"}(用时 ${proxyTestResult.latency_ms} ms)`}
127
+ </div>
128
+ ) : null}
129
+ <div className="flex justify-end">
130
+ <Button
131
+ type="button"
132
+ variant="outline"
133
+ className="h-9 rounded-xl border-stone-200 bg-white px-4 text-stone-700"
134
+ onClick={() => void handleTestProxy()}
135
+ disabled={isTestingProxy}
136
+ >
137
+ {isTestingProxy ? <LoaderCircle className="size-4 animate-spin" /> : <PlugZap className="size-4" />}
138
+ 测试代理
139
+ </Button>
140
+ </div>
141
+ </div>
142
+ <div className="space-y-2">
143
+ <label className="text-sm text-stone-700">图片访问地址</label>
144
+ <Input
145
+ value={String(config?.base_url || "")}
146
+ onChange={(event) => setBaseUrl(event.target.value)}
147
+ placeholder="https://example.com"
148
+ className="h-10 rounded-xl border-stone-200 bg-white"
149
+ />
150
+ <p className="text-xs text-stone-500">用于生成图片结果的访问前缀地址。</p>
151
+ </div>
152
+ <div className="space-y-2">
153
+ <label className="text-sm text-stone-700">默认请求上游模型名称</label>
154
+ <Input
155
+ value={String(config?.default_upstream_model_name || "")}
156
+ onChange={(event) => setDefaultUpstreamModelName(event.target.value)}
157
+ placeholder="gpt-5-5"
158
+ className="h-10 rounded-xl border-stone-200 bg-white"
159
+ />
160
+ <p className="text-xs text-stone-500">gpt-image-2 发起图片请求时使用的上游模型名称,默认 gpt-5-5。</p>
161
+ </div>
162
+ <div className="space-y-2">
163
+ <label className="text-sm text-stone-700">默认思考强度</label>
164
+ <Select
165
+ value={String(config?.default_thinking_effort || "auto")}
166
+ onValueChange={(value) => setDefaultThinkingEffort(value as "auto" | "standard" | "extended" | "max")}
167
+ >
168
+ <SelectTrigger className="h-10 rounded-xl border-stone-200 bg-white">
169
+ <SelectValue />
170
+ </SelectTrigger>
171
+ <SelectContent>
172
+ <SelectItem value="auto">Auto(不传递)</SelectItem>
173
+ <SelectItem value="standard">Standard</SelectItem>
174
+ <SelectItem value="extended">Extended</SelectItem>
175
+ <SelectItem value="max">Max</SelectItem>
176
+ </SelectContent>
177
+ </Select>
178
+ <p className="text-xs text-stone-500">模型名称以 -standard、-extended 或 -max 结尾时,模型后缀优先。</p>
179
+ </div>
180
+ <div className="space-y-2">
181
+ <label className="text-sm text-stone-700">图片自动清理</label>
182
+ <Input
183
+ value={String(config?.image_retention_days || "")}
184
+ onChange={(event) => setImageRetentionDays(event.target.value)}
185
+ placeholder="30"
186
+ className="h-10 rounded-xl border-stone-200 bg-white"
187
+ />
188
+ <p className="text-xs text-stone-500">自动删除多少天前的本地图片。</p>
189
+ </div>
190
+ <div className="space-y-2">
191
+ <label className="text-sm text-stone-700">图片轮询超时</label>
192
+ <Input
193
+ value={String(config?.image_poll_timeout_secs || "")}
194
+ onChange={(event) => setImagePollTimeoutSecs(event.target.value)}
195
+ placeholder="120"
196
+ className="h-10 rounded-xl border-stone-200 bg-white"
197
+ />
198
+ <p className="text-xs text-stone-500">单位秒,等待上游图片结果的最长时间。</p>
199
+ </div>
200
+ <div className="space-y-2">
201
+ <label className="text-sm text-stone-700">单账号图片并发</label>
202
+ <Input
203
+ value={String(config?.image_account_concurrency || "")}
204
+ onChange={(event) => setImageAccountConcurrency(event.target.value)}
205
+ placeholder="1"
206
+ className="h-10 rounded-xl border-stone-200 bg-white"
207
+ />
208
+ <p className="text-xs text-stone-500">限制每个账号同时处理的图片请求数量,默认 3。</p>
209
+ </div>
210
+ <div className="space-y-2">
211
+ <label className="flex items-center gap-3 rounded-xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-700">
212
+ <Checkbox
213
+ checked={Boolean(config?.auto_remove_invalid_accounts)}
214
+ onCheckedChange={(checked) => setAutoRemoveInvalidAccounts(Boolean(checked))}
215
+ />
216
+ 自动移除异常账号
217
+ </label>
218
+ <p className="text-xs text-stone-500">刷新时检测并移除</p>
219
+ </div>
220
+ <div className="space-y-2">
221
+ <div className="flex items-center gap-3 rounded-xl border border-stone-200 bg-white px-4 py-3">
222
+ <Checkbox
223
+ checked={Boolean(config?.image_settle_enabled !== false)}
224
+ onCheckedChange={(checked) => setImageSettleEnabled(Boolean(checked))}
225
+ />
226
+ <span className="text-sm text-stone-700">图片二次确认机制</span>
227
+ </div>
228
+ <p className="text-xs text-stone-500">打开后能稍微提升获取图片的成功率。</p>
229
+ </div>
230
+ <div className="space-y-2">
231
+ <div className="flex items-center gap-3 rounded-xl border border-stone-200 bg-white px-4 py-3">
232
+ <Checkbox
233
+ checked={Boolean(config?.image_remove_conversation_after_result)}
234
+ onCheckedChange={(checked) => setImageRemoveConversationAfterResult(Boolean(checked))}
235
+ />
236
+ <span className="text-sm text-stone-700">出图后移除本地对话</span>
237
+ </div>
238
+ <p className="text-xs text-stone-500">成功拿到图片后,异步隐藏 ChatGPT 侧对应的本地对话记录。</p>
239
+ </div>
240
+ <div className="space-y-2">
241
+ <div className="flex items-center gap-3 rounded-xl border border-stone-200 bg-white px-4 py-3">
242
+ <Checkbox
243
+ checked={Boolean(config?.image_remove_conversation_always)}
244
+ onCheckedChange={(checked) => setImageRemoveConversationAlways(Boolean(checked))}
245
+ />
246
+ <span className="text-sm text-stone-700">没出图也移除本地对话</span>
247
+ </div>
248
+ <p className="text-xs text-stone-500">失败、超时或只返回文本时也一并隐藏对话记录(打开后包含出图成功的情况)。</p>
249
+ </div>
250
+ <div className="space-y-2">
251
+ <label className="text-sm text-stone-700">图片超时继续等待时间</label>
252
+ <Input
253
+ value={String(config?.image_timeout_retry_secs || "30")}
254
+ onChange={(event) => setImageTimeoutRetrySecs(event.target.value)}
255
+ placeholder="30"
256
+ className="h-10 rounded-xl border-stone-200 bg-white"
257
+ />
258
+ <p className="text-xs text-stone-500">单位秒,超时后点击"继续等待"额外等待的时间。</p>
259
+ </div>
260
+ <div className="space-y-2">
261
+ <label className="text-sm text-stone-700">图片二次确认等待时间</label>
262
+ <Input
263
+ value={String(config?.image_settle_secs || "2.0")}
264
+ onChange={(event) => setImageSettleSecs(event.target.value)}
265
+ placeholder="2.0"
266
+ className="h-10 rounded-xl border-stone-200 bg-white disabled:cursor-not-allowed disabled:opacity-50"
267
+ disabled={!config?.image_settle_enabled}
268
+ />
269
+ <p className="text-xs text-stone-500">单位秒,找到图片后等待多久再次确认。需配合图片二次确认机制使用。</p>
270
+ </div>
271
+ <div className="flex gap-4 md:col-span-2">
272
+ <div className="flex-1 space-y-2">
273
+ <label className="flex items-center gap-3 rounded-xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-700">
274
+ <Checkbox
275
+ checked={Boolean(config?.auto_relogin_after_refresh)}
276
+ onCheckedChange={(checked) => setAutoReloginAfterRefresh(Boolean(checked))}
277
+ />
278
+ 刷新后自动尝试移除异常状态
279
+ </label>
280
+ <p className="text-xs text-stone-500">开启后刷新时自动尝试密码登录恢复账号。</p>
281
+ </div>
282
+ <div className="flex-1" aria-hidden="true" />
283
+ </div>
284
+ <label className="flex items-center gap-3 rounded-xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-700">
285
+ <Checkbox
286
+ checked={Boolean(config?.auto_remove_rate_limited_accounts)}
287
+ onCheckedChange={(checked) => setAutoRemoveRateLimitedAccounts(Boolean(checked))}
288
+ />
289
+ 自动移除限流账号
290
+ </label>
291
+ <div className="space-y-3 rounded-xl border border-stone-200 bg-white px-4 py-3">
292
+ <div>
293
+ <label className="text-sm text-stone-700">控制台日志级别</label>
294
+ <p className="mt-1 text-xs text-stone-500">不选择时使用默认 info / warning / error。</p>
295
+ </div>
296
+ <div className="grid grid-cols-2 gap-2">
297
+ {logLevelOptions.map((level) => (
298
+ <label key={level} className="flex items-center gap-2 text-sm capitalize text-stone-700">
299
+ <Checkbox
300
+ checked={Boolean(config?.log_levels?.includes(level))}
301
+ onCheckedChange={(checked) => setLogLevel(level, Boolean(checked))}
302
+ />
303
+ {level}
304
+ </label>
305
+ ))}
306
+ </div>
307
+ </div>
308
+ <div className="space-y-2 md:col-span-2">
309
+ <label className="text-sm text-stone-700">全局附加指令</label>
310
+ <Textarea
311
+ value={String(config?.global_system_prompt || "")}
312
+ onChange={(event) => setGlobalSystemPrompt(event.target.value)}
313
+ placeholder="例如:先判断用户提示词是否合规;遇到违法、色情、暴力、仇恨等请求时拒绝回答。"
314
+ className="min-h-28 rounded-xl border-stone-200 bg-white font-mono text-xs shadow-none"
315
+ />
316
+ <p className="text-xs text-stone-500">每次请求都会作为 system 消息注入,可用于审核用户提示词、避免违规内容、统一约束模型行为或固定角色设定。</p>
317
+ </div>
318
+ <div className="space-y-2 md:col-span-2">
319
+ <label className="text-sm text-stone-700">敏感词</label>
320
+ <Textarea
321
+ value={(config?.sensitive_words || []).join("\n")}
322
+ onChange={(event) => setSensitiveWordsText(event.target.value)}
323
+ placeholder="一行一个,命中即拒绝"
324
+ className="min-h-28 rounded-xl border-stone-200 bg-white font-mono text-xs shadow-none"
325
+ />
326
+ <p className="text-xs text-stone-500">只要用户请求包含任意敏感词,就直接返回拒绝。</p>
327
+ </div>
328
+ <div className="space-y-4 rounded-xl border border-stone-200 bg-white px-4 py-3 md:col-span-2">
329
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
330
+ <label className="flex items-center gap-3 text-sm text-stone-700">
331
+ <Checkbox
332
+ checked={Boolean(config?.image_storage?.enabled)}
333
+ onCheckedChange={(checked) => setImageStorageField("enabled", Boolean(checked))}
334
+ />
335
+ 启用 WebDAV 图片存储
336
+ </label>
337
+ <div className="flex flex-wrap gap-2">
338
+ <Button
339
+ type="button"
340
+ variant="outline"
341
+ className="h-9 rounded-xl border-stone-200 bg-white px-4 text-stone-700"
342
+ onClick={() => void testImageStorage()}
343
+ disabled={isTestingImageStorage || !config?.image_storage?.enabled}
344
+ >
345
+ {isTestingImageStorage ? <LoaderCircle className="size-4 animate-spin" /> : <Cloud className="size-4" />}
346
+ 测试 WebDAV
347
+ </Button>
348
+ <Button
349
+ type="button"
350
+ variant="outline"
351
+ className="h-9 rounded-xl border-stone-200 bg-white px-4 text-stone-700"
352
+ onClick={() => void syncImagesToWebDAV()}
353
+ disabled={isSyncingImageStorage || !config?.image_storage?.enabled || config?.image_storage?.mode === "local"}
354
+ >
355
+ {isSyncingImageStorage ? <LoaderCircle className="size-4 animate-spin" /> : <RefreshCw className="size-4" />}
356
+ 全量同步
357
+ </Button>
358
+ </div>
359
+ </div>
360
+ <p className="text-xs leading-6 text-stone-500">
361
+ 生成时只处理本次新图片;全量同步用于把已有本地图片补传到 WebDAV。
362
+ </p>
363
+ <div className="rounded-lg border border-stone-100 bg-stone-50 px-3 py-2 text-xs text-stone-600">
364
+ 当前待保存模式:
365
+ <span className="ml-1 font-medium text-stone-900">
366
+ {config?.image_storage?.enabled
367
+ ? config.image_storage.mode === "both"
368
+ ? "本机 + WebDAV"
369
+ : config.image_storage.mode === "webdav"
370
+ ? "仅 WebDAV"
371
+ : "仅本机"
372
+ : "仅本机"}
373
+ </span>
374
+ <span className="ml-2 text-stone-400">修改后需要点保存,或通过测试/同步按钮自动保存。</span>
375
+ </div>
376
+ <div className="grid gap-4 md:grid-cols-3">
377
+ <div className="space-y-2">
378
+ <label className="text-sm text-stone-700">保存模式</label>
379
+ <Select
380
+ value={String(config?.image_storage?.mode || "local")}
381
+ onValueChange={(value) => setImageStorageField("mode", value as ImageStorageMode)}
382
+ disabled={!config?.image_storage?.enabled}
383
+ >
384
+ <SelectTrigger className="h-10 rounded-xl border-stone-200 bg-white shadow-none">
385
+ <SelectValue />
386
+ </SelectTrigger>
387
+ <SelectContent>
388
+ <SelectItem value="local">仅本机</SelectItem>
389
+ <SelectItem value="webdav">仅 WebDAV</SelectItem>
390
+ <SelectItem value="both">本机 + WebDAV</SelectItem>
391
+ </SelectContent>
392
+ </Select>
393
+ </div>
394
+ <div className="space-y-2 md:col-span-2">
395
+ <label className="text-sm text-stone-700">WebDAV URL</label>
396
+ <Input
397
+ value={String(config?.image_storage?.webdav_url || "")}
398
+ onChange={(event) => setImageStorageField("webdav_url", event.target.value)}
399
+ placeholder="https://example.com/dav"
400
+ className="h-10 rounded-xl border-stone-200 bg-white"
401
+ disabled={!config?.image_storage?.enabled}
402
+ />
403
+ </div>
404
+ <div className="space-y-2">
405
+ <label className="text-sm text-stone-700">用户名</label>
406
+ <Input
407
+ value={String(config?.image_storage?.webdav_username || "")}
408
+ onChange={(event) => setImageStorageField("webdav_username", event.target.value)}
409
+ className="h-10 rounded-xl border-stone-200 bg-white"
410
+ disabled={!config?.image_storage?.enabled}
411
+ />
412
+ </div>
413
+ <div className="space-y-2">
414
+ <label className="text-sm text-stone-700">密码</label>
415
+ <Input
416
+ type="password"
417
+ value={String(config?.image_storage?.webdav_password || "")}
418
+ onChange={(event) => setImageStorageField("webdav_password", event.target.value)}
419
+ className="h-10 rounded-xl border-stone-200 bg-white"
420
+ disabled={!config?.image_storage?.enabled}
421
+ />
422
+ </div>
423
+ <div className="space-y-2">
424
+ <label className="text-sm text-stone-700">远端目录</label>
425
+ <Input
426
+ value={String(config?.image_storage?.webdav_root_path || "")}
427
+ onChange={(event) => setImageStorageField("webdav_root_path", event.target.value)}
428
+ placeholder="chatgpt2api/images"
429
+ className="h-10 rounded-xl border-stone-200 bg-white"
430
+ disabled={!config?.image_storage?.enabled}
431
+ />
432
+ </div>
433
+ <div className="space-y-2 md:col-span-3">
434
+ <label className="text-sm text-stone-700">公开访问前缀</label>
435
+ <Input
436
+ value={String(config?.image_storage?.public_base_url || "")}
437
+ onChange={(event) => setImageStorageField("public_base_url", event.target.value)}
438
+ placeholder="https://cdn.example.com/chatgpt2api/images"
439
+ className="h-10 rounded-xl border-stone-200 bg-white"
440
+ disabled={!config?.image_storage?.enabled}
441
+ />
442
+ <p className="text-xs text-stone-500">留空时返回本应用 /images/... 代理地址;填入后直接返回公开图片地址。</p>
443
+ </div>
444
+ </div>
445
+ </div>
446
+ <div className="space-y-4 rounded-xl border border-stone-200 bg-white px-4 py-3 md:col-span-2">
447
+ <label className="flex items-center gap-3 text-sm text-stone-700">
448
+ <Checkbox
449
+ checked={Boolean(config?.ai_review?.enabled)}
450
+ onCheckedChange={(checked) => setAIReviewField("enabled", Boolean(checked))}
451
+ />
452
+ 启用 AI 审核
453
+ </label>
454
+ <p className="text-xs leading-6 text-stone-500">
455
+ 开启后会在请求进入生图账号前先调用审核模型,审核不通过会直接拒绝,减少违规提示词触达账号造成风控或封号的风险。
456
+ </p>
457
+ <div className="grid gap-4 md:grid-cols-3">
458
+ <div className="space-y-2">
459
+ <label className="text-sm text-stone-700">Base URL</label>
460
+ <Input value={String(config?.ai_review?.base_url || "")} onChange={(event) => setAIReviewField("base_url", event.target.value)} placeholder="https://api.openai.com" className="h-10 rounded-xl border-stone-200 bg-white" />
461
+ </div>
462
+ <div className="space-y-2">
463
+ <label className="text-sm text-stone-700">API Key</label>
464
+ <Input value={String(config?.ai_review?.api_key || "")} onChange={(event) => setAIReviewField("api_key", event.target.value)} placeholder="sk-..." className="h-10 rounded-xl border-stone-200 bg-white" />
465
+ </div>
466
+ <div className="space-y-2">
467
+ <label className="text-sm text-stone-700">Model</label>
468
+ <Input value={String(config?.ai_review?.model || "")} onChange={(event) => setAIReviewField("model", event.target.value)} placeholder="gpt-5.4-mini" className="h-10 rounded-xl border-stone-200 bg-white" />
469
+ </div>
470
+ </div>
471
+ <div className="space-y-2">
472
+ <label className="text-sm text-stone-700">审核提示词</label>
473
+ <Textarea value={String(config?.ai_review?.prompt || "")} onChange={(event) => setAIReviewField("prompt", event.target.value)} placeholder="判断用户请求是否允许。只回答 ALLOW 或 REJECT。" className="min-h-24 rounded-xl border-stone-200 bg-white text-xs shadow-none" />
474
+ </div>
475
+ </div>
476
+ </div>
477
+
478
+ <div className="flex justify-end">
479
+ <Button
480
+ className="h-10 rounded-xl bg-stone-950 px-5 text-white hover:bg-stone-800"
481
+ onClick={() => void saveConfig()}
482
+ disabled={isSavingConfig}
483
+ >
484
+ {isSavingConfig ? <LoaderCircle className="size-4 animate-spin" /> : <Save className="size-4" />}
485
+ 保存
486
+ </Button>
487
+ </div>
488
+ </CardContent>
489
+ </Card>
490
+ );
491
+ }
@@ -0,0 +1,108 @@
1
+ "use client";
2
+
3
+ import { Eye, EyeOff, Link2, LoaderCircle, Save, Unplug } from "lucide-react";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from "@/components/ui/dialog";
14
+ import { Input } from "@/components/ui/input";
15
+
16
+ import { useSettingsStore } from "../store";
17
+
18
+ export function CPAPoolDialog() {
19
+ const dialogOpen = useSettingsStore((state) => state.dialogOpen);
20
+ const editingPool = useSettingsStore((state) => state.editingPool);
21
+ const formName = useSettingsStore((state) => state.formName);
22
+ const formBaseUrl = useSettingsStore((state) => state.formBaseUrl);
23
+ const formSecretKey = useSettingsStore((state) => state.formSecretKey);
24
+ const showSecret = useSettingsStore((state) => state.showSecret);
25
+ const isSavingPool = useSettingsStore((state) => state.isSavingPool);
26
+ const setDialogOpen = useSettingsStore((state) => state.setDialogOpen);
27
+ const setFormName = useSettingsStore((state) => state.setFormName);
28
+ const setFormBaseUrl = useSettingsStore((state) => state.setFormBaseUrl);
29
+ const setFormSecretKey = useSettingsStore((state) => state.setFormSecretKey);
30
+ const setShowSecret = useSettingsStore((state) => state.setShowSecret);
31
+ const savePool = useSettingsStore((state) => state.savePool);
32
+
33
+ return (
34
+ <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
35
+ <DialogContent showCloseButton={false} className="rounded-2xl p-6">
36
+ <DialogHeader className="gap-2">
37
+ <DialogTitle>{editingPool ? "编辑连接" : "添加连接"}</DialogTitle>
38
+ <DialogDescription className="text-sm leading-6">
39
+ {editingPool ? "修改 CPA 连接信息" : "添加一个新的 CLIProxyAPI 连接"}
40
+ </DialogDescription>
41
+ </DialogHeader>
42
+ <div className="space-y-4">
43
+ <div className="space-y-2">
44
+ <label className="text-sm font-medium text-stone-700">名称(可选)</label>
45
+ <Input
46
+ value={formName}
47
+ onChange={(event) => setFormName(event.target.value)}
48
+ placeholder="例如:主号池、备用池"
49
+ className="h-11 rounded-xl border-stone-200 bg-white"
50
+ />
51
+ </div>
52
+ <div className="space-y-2">
53
+ <label className="flex items-center gap-1.5 text-sm font-medium text-stone-700">
54
+ <Link2 className="size-3.5" />
55
+ CPA 地址
56
+ </label>
57
+ <Input
58
+ value={formBaseUrl}
59
+ onChange={(event) => setFormBaseUrl(event.target.value)}
60
+ placeholder="http://your-cpa-host:8317"
61
+ className="h-11 rounded-xl border-stone-200 bg-white"
62
+ />
63
+ </div>
64
+ <div className="space-y-2">
65
+ <label className="flex items-center gap-1.5 text-sm font-medium text-stone-700">
66
+ <Unplug className="size-3.5" />
67
+ Management Secret Key
68
+ </label>
69
+ <div className="relative">
70
+ <Input
71
+ type={showSecret ? "text" : "password"}
72
+ value={formSecretKey}
73
+ onChange={(event) => setFormSecretKey(event.target.value)}
74
+ placeholder={editingPool ? "留空则不修改密钥" : "CPA 管理密钥"}
75
+ className="h-11 rounded-xl border-stone-200 bg-white pr-10"
76
+ />
77
+ <button
78
+ type="button"
79
+ className="absolute top-1/2 right-3 -translate-y-1/2 text-stone-400 transition hover:text-stone-600"
80
+ onClick={() => setShowSecret(!showSecret)}
81
+ >
82
+ {showSecret ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
83
+ </button>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ <DialogFooter className="pt-2">
88
+ <Button
89
+ variant="secondary"
90
+ className="h-10 rounded-xl bg-stone-100 px-5 text-stone-700 hover:bg-stone-200"
91
+ onClick={() => setDialogOpen(false)}
92
+ disabled={isSavingPool}
93
+ >
94
+ 取消
95
+ </Button>
96
+ <Button
97
+ className="h-10 rounded-xl bg-stone-950 px-5 text-white hover:bg-stone-800"
98
+ onClick={() => void savePool()}
99
+ disabled={isSavingPool}
100
+ >
101
+ {isSavingPool ? <LoaderCircle className="size-4 animate-spin" /> : <Save className="size-4" />}
102
+ {editingPool ? "保存修改" : "添加"}
103
+ </Button>
104
+ </DialogFooter>
105
+ </DialogContent>
106
+ </Dialog>
107
+ );
108
+ }