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.
- package/LICENSE +21 -0
- package/README.en.md +256 -0
- package/README.md +256 -2
- package/client.js +241 -0
- package/cordis.patch.yml +14 -0
- package/host.js +641 -0
- package/install.mjs +387 -0
- package/package.json +57 -4
- package/scripts/vendor-snapshot.py +154 -0
- package/server/access.py +26 -0
- package/server/app.py +1329 -0
- package/server/catalog.json +1 -0
- package/server/catalog.py +147 -0
- package/server/composer.py +391 -0
- package/server/drafts.py +158 -0
- package/server/history.py +575 -0
- package/server/imaging.py +172 -0
- package/server/pool.py +1335 -0
- package/server/pool_admin.py +921 -0
- package/server/pool_service.py +1073 -0
- package/server/requirements.txt +6 -0
- package/server/smoke_check.py +402 -0
- package/server/static/access.html +202 -0
- package/server/static/index.html +4725 -0
- package/vendor/chatgpt2api/README.md +13 -0
- package/vendor/chatgpt2api/SOURCE.json +11 -0
- package/vendor/chatgpt2api/runtime/uv.exe +0 -0
- package/vendor/chatgpt2api/source/.env.example +82 -0
- package/vendor/chatgpt2api/source/.python-version +1 -0
- package/vendor/chatgpt2api/source/LICENSE +21 -0
- package/vendor/chatgpt2api/source/README.md +391 -0
- package/vendor/chatgpt2api/source/VERSION +1 -0
- package/vendor/chatgpt2api/source/api/__init__.py +2 -0
- package/vendor/chatgpt2api/source/api/accounts.py +537 -0
- package/vendor/chatgpt2api/source/api/ai.py +217 -0
- package/vendor/chatgpt2api/source/api/app.py +62 -0
- package/vendor/chatgpt2api/source/api/errors.py +46 -0
- package/vendor/chatgpt2api/source/api/image_inputs.py +309 -0
- package/vendor/chatgpt2api/source/api/image_tasks.py +120 -0
- package/vendor/chatgpt2api/source/api/support.py +134 -0
- package/vendor/chatgpt2api/source/api/system.py +365 -0
- package/vendor/chatgpt2api/source/assets/atlascloud.svg +1 -0
- package/vendor/chatgpt2api/source/assets/ipwo.png +0 -0
- package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -0
- package/vendor/chatgpt2api/source/config.default.json +108 -0
- package/vendor/chatgpt2api/source/main.py +9 -0
- package/vendor/chatgpt2api/source/pyproject.toml +27 -0
- package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -0
- package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -0
- package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -0
- package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -0
- package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -0
- package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -0
- package/vendor/chatgpt2api/source/services/__init__.py +0 -0
- package/vendor/chatgpt2api/source/services/account_service.py +1757 -0
- package/vendor/chatgpt2api/source/services/auth_service.py +253 -0
- package/vendor/chatgpt2api/source/services/backup_service.py +671 -0
- package/vendor/chatgpt2api/source/services/config.py +667 -0
- package/vendor/chatgpt2api/source/services/content_filter.py +242 -0
- package/vendor/chatgpt2api/source/services/cpa_service.py +315 -0
- package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -0
- package/vendor/chatgpt2api/source/services/image_service.py +376 -0
- package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -0
- package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -0
- package/vendor/chatgpt2api/source/services/image_task_service.py +550 -0
- package/vendor/chatgpt2api/source/services/log_service.py +337 -0
- package/vendor/chatgpt2api/source/services/model_service.py +182 -0
- package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -0
- package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -0
- package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -0
- package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -0
- package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -0
- package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -0
- package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -0
- package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -0
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -0
- package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -0
- package/vendor/chatgpt2api/source/services/proxy_service.py +648 -0
- package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -0
- package/vendor/chatgpt2api/source/services/storage/base.py +38 -0
- package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -0
- package/vendor/chatgpt2api/source/services/storage/factory.py +104 -0
- package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -0
- package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -0
- package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -0
- package/vendor/chatgpt2api/source/utils/__init__.py +0 -0
- package/vendor/chatgpt2api/source/utils/helper.py +469 -0
- package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -0
- package/vendor/chatgpt2api/source/utils/log.py +110 -0
- package/vendor/chatgpt2api/source/utils/pkce.py +19 -0
- package/vendor/chatgpt2api/source/utils/pow.py +207 -0
- package/vendor/chatgpt2api/source/utils/sentinel.py +159 -0
- package/vendor/chatgpt2api/source/utils/turnstile.py +184 -0
- package/vendor/chatgpt2api/source/uv.lock +904 -0
- package/vendor/chatgpt2api/source/web/bun.lock +1501 -0
- package/vendor/chatgpt2api/source/web/components.json +24 -0
- package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -0
- package/vendor/chatgpt2api/source/web/next.config.ts +40 -0
- package/vendor/chatgpt2api/source/web/package.json +58 -0
- package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -0
- package/vendor/chatgpt2api/source/web/public/github.svg +1 -0
- package/vendor/chatgpt2api/source/web/public/next.svg +1 -0
- package/vendor/chatgpt2api/source/web/public/openai.svg +1 -0
- package/vendor/chatgpt2api/source/web/public/vercel.svg +1 -0
- package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -0
- package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -0
- package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -0
- package/vendor/chatgpt2api/source/web/src/app/favicon.ico +0 -0
- package/vendor/chatgpt2api/source/web/src/app/globals.css +231 -0
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -0
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -0
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -0
- package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -0
- package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -0
- package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -0
- package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -0
- package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -0
- package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -0
- package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -0
- package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -0
- package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -0
- package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -0
- package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -0
- package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -0
- package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -0
- package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -0
- package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -0
- package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -0
- package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -0
- package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -0
- package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -0
- package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -0
- package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -0
- package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -0
- package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -0
- package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -0
- package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -0
- package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -0
- package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -0
- package/vendor/chatgpt2api/source/web/tsconfig.json +42 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { AlertTriangle, Cookie, LoaderCircle, PlugZap, Save, ShieldCheck } 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 {
|
|
14
|
+
testProxy,
|
|
15
|
+
testProxyClearance,
|
|
16
|
+
type ClearanceTestResult,
|
|
17
|
+
type ProxyRuntimeClearanceMode,
|
|
18
|
+
type ProxyRuntimeEgressMode,
|
|
19
|
+
type ProxyTestResult,
|
|
20
|
+
} from "@/lib/api";
|
|
21
|
+
|
|
22
|
+
import { useSettingsStore } from "../store";
|
|
23
|
+
|
|
24
|
+
export function ProxyRuntimeCard() {
|
|
25
|
+
const [isTestingProxy, setIsTestingProxy] = useState(false);
|
|
26
|
+
const [isTestingClearance, setIsTestingClearance] = useState(false);
|
|
27
|
+
const [proxyResult, setProxyResult] = useState<ProxyTestResult | null>(null);
|
|
28
|
+
const [clearanceResult, setClearanceResult] = useState<ClearanceTestResult | null>(null);
|
|
29
|
+
const [targetUrl, setTargetUrl] = useState("https://chatgpt.com");
|
|
30
|
+
const config = useSettingsStore((state) => state.config);
|
|
31
|
+
const isLoadingConfig = useSettingsStore((state) => state.isLoadingConfig);
|
|
32
|
+
const isSavingConfig = useSettingsStore((state) => state.isSavingConfig);
|
|
33
|
+
const saveConfig = useSettingsStore((state) => state.saveConfig);
|
|
34
|
+
const setProxyRuntimeField = useSettingsStore((state) => state.setProxyRuntimeField);
|
|
35
|
+
const setProxyRuntimeClearanceField = useSettingsStore((state) => state.setProxyRuntimeClearanceField);
|
|
36
|
+
const setProxyRuntimeStatusCodesText = useSettingsStore((state) => state.setProxyRuntimeStatusCodesText);
|
|
37
|
+
|
|
38
|
+
if (isLoadingConfig || !config?.proxy_runtime) {
|
|
39
|
+
return (
|
|
40
|
+
<Card className="rounded-2xl border-white/80 bg-white/90 shadow-sm">
|
|
41
|
+
<CardContent className="flex items-center justify-center p-10">
|
|
42
|
+
<LoaderCircle className="size-5 animate-spin text-stone-400" />
|
|
43
|
+
</CardContent>
|
|
44
|
+
</Card>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const runtime = config.proxy_runtime;
|
|
49
|
+
const clearance = runtime.clearance;
|
|
50
|
+
const runtimeEnabled = Boolean(runtime.enabled);
|
|
51
|
+
const clearanceMode = clearance.mode;
|
|
52
|
+
const hasStoredClearance = Boolean(clearance.has_cf_cookies || clearance.has_cf_clearance);
|
|
53
|
+
|
|
54
|
+
const handleTestRuntimeProxy = async () => {
|
|
55
|
+
setIsTestingProxy(true);
|
|
56
|
+
setProxyResult(null);
|
|
57
|
+
try {
|
|
58
|
+
const saved = await saveConfig();
|
|
59
|
+
if (!saved) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const data = await testProxy();
|
|
63
|
+
setProxyResult(data.result);
|
|
64
|
+
if (data.result.ok) {
|
|
65
|
+
toast.success(`清障代理可用(${data.result.latency_ms} ms,HTTP ${data.result.status})`);
|
|
66
|
+
} else {
|
|
67
|
+
toast.error(`清障代理不可用:${data.result.error ?? "未知错误"}`);
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
toast.error(error instanceof Error ? error.message : "测试清障代理失败");
|
|
71
|
+
} finally {
|
|
72
|
+
setIsTestingProxy(false);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleTestClearance = async () => {
|
|
77
|
+
setIsTestingClearance(true);
|
|
78
|
+
setClearanceResult(null);
|
|
79
|
+
try {
|
|
80
|
+
const saved = await saveConfig();
|
|
81
|
+
if (!saved) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const data = await testProxyClearance(targetUrl.trim() || "https://chatgpt.com");
|
|
85
|
+
setClearanceResult(data.result);
|
|
86
|
+
if (data.result.ok) {
|
|
87
|
+
toast.success(`Clearance 获取成功(${data.result.latency_ms} ms)`);
|
|
88
|
+
} else {
|
|
89
|
+
toast.error(`Clearance 获取失败:${data.result.error ?? data.result.status}`);
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
toast.error(error instanceof Error ? error.message : "测试 Clearance 失败");
|
|
93
|
+
} finally {
|
|
94
|
+
setIsTestingClearance(false);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Card className="rounded-2xl border-white/80 bg-white/90 shadow-sm">
|
|
100
|
+
<CardContent className="space-y-5 p-6">
|
|
101
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
102
|
+
<div>
|
|
103
|
+
<div className="flex items-center gap-2 text-base font-semibold text-stone-900">
|
|
104
|
+
<PlugZap className="size-5 text-stone-500" />
|
|
105
|
+
FlareSolverr 清障
|
|
106
|
+
</div>
|
|
107
|
+
<p className="mt-1 text-xs leading-6 text-stone-500">
|
|
108
|
+
默认关闭。用于上游请求遇到 Cloudflare 拦截后获取 clearance,可配合 WARP / Privoxy 代理链路重试。
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
<span className={`rounded-full px-3 py-1 text-xs ${runtimeEnabled ? "bg-emerald-50 text-emerald-700" : "bg-stone-100 text-stone-500"}`}>
|
|
112
|
+
{runtimeEnabled ? "已启用" : "未启用"}
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div className="rounded-xl border border-stone-200 bg-stone-50 px-4 py-3 text-xs leading-6 text-stone-600">
|
|
117
|
+
代理优先级:账号代理 > FlareSolverr 代理链路 > 显式代理 > 全局代理。Cookie / cf_clearance 不会在接口响应中明文返回。
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div className="flex items-start gap-2 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-xs leading-6 text-amber-800">
|
|
121
|
+
<AlertTriangle className="mt-1 size-4 shrink-0" />
|
|
122
|
+
<span>使用 FlareSolverr 模式前,请先通过 Docker 启动 flaresolverr、privoxy、warp-proxy 等相关容器;容器内 URL 通常填写 http://flaresolverr:8191。</span>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
126
|
+
<label className="flex items-center gap-3 rounded-xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-700 md:col-span-2">
|
|
127
|
+
<Checkbox
|
|
128
|
+
checked={runtimeEnabled}
|
|
129
|
+
onCheckedChange={(checked) => setProxyRuntimeField("enabled", Boolean(checked))}
|
|
130
|
+
/>
|
|
131
|
+
启用 FlareSolverr 清障
|
|
132
|
+
</label>
|
|
133
|
+
|
|
134
|
+
<div className="space-y-2">
|
|
135
|
+
<label className="text-sm text-stone-700">出站模式</label>
|
|
136
|
+
<Select
|
|
137
|
+
value={runtime.egress_mode}
|
|
138
|
+
onValueChange={(value) => setProxyRuntimeField("egress_mode", value as ProxyRuntimeEgressMode)}
|
|
139
|
+
disabled={!runtimeEnabled}
|
|
140
|
+
>
|
|
141
|
+
<SelectTrigger className="h-10 rounded-xl border-stone-200 bg-white shadow-none">
|
|
142
|
+
<SelectValue />
|
|
143
|
+
</SelectTrigger>
|
|
144
|
+
<SelectContent>
|
|
145
|
+
<SelectItem value="direct">直连</SelectItem>
|
|
146
|
+
<SelectItem value="single_proxy">单代理/WARP</SelectItem>
|
|
147
|
+
</SelectContent>
|
|
148
|
+
</Select>
|
|
149
|
+
<p className="text-xs text-stone-500">WARP compose 默认使用 single_proxy。</p>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div className="space-y-2">
|
|
153
|
+
<label className="text-sm text-stone-700">清障代理 URL</label>
|
|
154
|
+
<Input
|
|
155
|
+
value={runtime.proxy_url}
|
|
156
|
+
onChange={(event) => setProxyRuntimeField("proxy_url", event.target.value)}
|
|
157
|
+
placeholder="http://privoxy:8118"
|
|
158
|
+
className="h-10 rounded-xl border-stone-200 bg-white"
|
|
159
|
+
disabled={!runtimeEnabled || runtime.egress_mode !== "single_proxy"}
|
|
160
|
+
/>
|
|
161
|
+
<p className="text-xs leading-5 text-stone-500">
|
|
162
|
+
支持 http/https/socks5/socks5h,socks5 会转为 socks5h。带认证格式:协议://账号:密码@主机:端口,也可直接粘贴 主机:端口:账号:密码。
|
|
163
|
+
</p>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<div className="space-y-2">
|
|
167
|
+
<label className="text-sm text-stone-700">资源代理 URL</label>
|
|
168
|
+
<Input
|
|
169
|
+
value={runtime.resource_proxy_url}
|
|
170
|
+
onChange={(event) => setProxyRuntimeField("resource_proxy_url", event.target.value)}
|
|
171
|
+
placeholder="留空则复用清障代理"
|
|
172
|
+
className="h-10 rounded-xl border-stone-200 bg-white"
|
|
173
|
+
disabled={!runtimeEnabled || runtime.egress_mode !== "single_proxy"}
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div className="space-y-2">
|
|
178
|
+
<label className="text-sm text-stone-700">重置会话状态码</label>
|
|
179
|
+
<Input
|
|
180
|
+
value={runtime.reset_session_status_codes.join(",")}
|
|
181
|
+
onChange={(event) => setProxyRuntimeStatusCodesText(event.target.value)}
|
|
182
|
+
placeholder="403"
|
|
183
|
+
className="h-10 rounded-xl border-stone-200 bg-white"
|
|
184
|
+
disabled={!runtimeEnabled}
|
|
185
|
+
/>
|
|
186
|
+
<p className="text-xs text-stone-500">默认 403,只对 Cloudflare/挑战类错误触发。</p>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<label className="flex items-center gap-3 rounded-xl border border-stone-200 bg-white px-4 py-3 text-sm text-stone-700">
|
|
190
|
+
<Checkbox
|
|
191
|
+
checked={Boolean(runtime.skip_ssl_verify)}
|
|
192
|
+
onCheckedChange={(checked) => setProxyRuntimeField("skip_ssl_verify", Boolean(checked))}
|
|
193
|
+
disabled={!runtimeEnabled}
|
|
194
|
+
/>
|
|
195
|
+
跳过 SSL 校验
|
|
196
|
+
</label>
|
|
197
|
+
|
|
198
|
+
<div className="flex items-end justify-end">
|
|
199
|
+
<Button
|
|
200
|
+
type="button"
|
|
201
|
+
variant="outline"
|
|
202
|
+
className="h-9 rounded-xl border-stone-200 bg-white px-4 text-stone-700"
|
|
203
|
+
onClick={() => void handleTestRuntimeProxy()}
|
|
204
|
+
disabled={isTestingProxy || !runtimeEnabled}
|
|
205
|
+
>
|
|
206
|
+
{isTestingProxy ? <LoaderCircle className="size-4 animate-spin" /> : <PlugZap className="size-4" />}
|
|
207
|
+
测试当前清障代理
|
|
208
|
+
</Button>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
{proxyResult ? (
|
|
212
|
+
<div className={`rounded-xl border px-3 py-2 text-xs leading-6 md:col-span-2 ${proxyResult.ok ? "border-emerald-200 bg-emerald-50 text-emerald-800" : "border-rose-200 bg-rose-50 text-rose-800"}`}>
|
|
213
|
+
{proxyResult.ok
|
|
214
|
+
? `代理可用:HTTP ${proxyResult.status},用时 ${proxyResult.latency_ms} ms,来源 ${proxyResult.proxy_source ?? "unknown"}`
|
|
215
|
+
: `代理不可用:${proxyResult.error ?? "未知错误"}(用时 ${proxyResult.latency_ms} ms)`}
|
|
216
|
+
</div>
|
|
217
|
+
) : null}
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div className="space-y-4 rounded-xl border border-stone-200 bg-white px-4 py-3">
|
|
221
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
222
|
+
<div className="flex items-center gap-2 text-sm font-medium text-stone-800">
|
|
223
|
+
<Cookie className="size-4 text-stone-500" />
|
|
224
|
+
Cloudflare Clearance
|
|
225
|
+
</div>
|
|
226
|
+
<span className={`rounded-full px-3 py-1 text-xs ${clearance.enabled ? "bg-emerald-50 text-emerald-700" : "bg-stone-100 text-stone-500"}`}>
|
|
227
|
+
{clearance.enabled ? clearanceMode : "disabled"}
|
|
228
|
+
</span>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
232
|
+
<div className="space-y-2">
|
|
233
|
+
<label className="text-sm text-stone-700">Clearance 模式</label>
|
|
234
|
+
<Select
|
|
235
|
+
value={clearanceMode}
|
|
236
|
+
onValueChange={(value) => {
|
|
237
|
+
const mode = value as ProxyRuntimeClearanceMode;
|
|
238
|
+
setProxyRuntimeClearanceField("mode", mode);
|
|
239
|
+
setProxyRuntimeClearanceField("enabled", mode !== "none");
|
|
240
|
+
}}
|
|
241
|
+
disabled={!runtimeEnabled}
|
|
242
|
+
>
|
|
243
|
+
<SelectTrigger className="h-10 rounded-xl border-stone-200 bg-white shadow-none">
|
|
244
|
+
<SelectValue />
|
|
245
|
+
</SelectTrigger>
|
|
246
|
+
<SelectContent>
|
|
247
|
+
<SelectItem value="none">不启用</SelectItem>
|
|
248
|
+
<SelectItem value="manual">手动 Cookie</SelectItem>
|
|
249
|
+
<SelectItem value="flaresolverr">FlareSolverr</SelectItem>
|
|
250
|
+
</SelectContent>
|
|
251
|
+
</Select>
|
|
252
|
+
</div>
|
|
253
|
+
|
|
254
|
+
<div className="space-y-2">
|
|
255
|
+
<label className="text-sm text-stone-700">FlareSolverr URL</label>
|
|
256
|
+
<Input
|
|
257
|
+
value={clearance.flaresolverr_url}
|
|
258
|
+
onChange={(event) => setProxyRuntimeClearanceField("flaresolverr_url", event.target.value)}
|
|
259
|
+
placeholder="http://flaresolverr:8191"
|
|
260
|
+
className="h-10 rounded-xl border-stone-200 bg-white"
|
|
261
|
+
disabled={!runtimeEnabled || clearanceMode !== "flaresolverr"}
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<div className="space-y-2 md:col-span-2">
|
|
266
|
+
<label className="text-sm text-stone-700">User-Agent</label>
|
|
267
|
+
<Input
|
|
268
|
+
value={clearance.user_agent}
|
|
269
|
+
onChange={(event) => setProxyRuntimeClearanceField("user_agent", event.target.value)}
|
|
270
|
+
className="h-10 rounded-xl border-stone-200 bg-white font-mono text-xs"
|
|
271
|
+
disabled={!runtimeEnabled || clearanceMode === "none"}
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<div className="space-y-2">
|
|
276
|
+
<label className="text-sm text-stone-700">超时秒数</label>
|
|
277
|
+
<Input
|
|
278
|
+
value={String(clearance.timeout_sec)}
|
|
279
|
+
onChange={(event) => setProxyRuntimeClearanceField("timeout_sec", event.target.value)}
|
|
280
|
+
placeholder="60"
|
|
281
|
+
className="h-10 rounded-xl border-stone-200 bg-white"
|
|
282
|
+
disabled={!runtimeEnabled || clearanceMode === "none"}
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<div className="space-y-2">
|
|
287
|
+
<label className="text-sm text-stone-700">刷新间隔秒数</label>
|
|
288
|
+
<Input
|
|
289
|
+
value={String(clearance.refresh_interval)}
|
|
290
|
+
onChange={(event) => setProxyRuntimeClearanceField("refresh_interval", event.target.value)}
|
|
291
|
+
placeholder="3600"
|
|
292
|
+
className="h-10 rounded-xl border-stone-200 bg-white"
|
|
293
|
+
disabled={!runtimeEnabled || clearanceMode === "none"}
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<div className="space-y-2 md:col-span-2">
|
|
298
|
+
<label className="text-sm text-stone-700">手动 Cookie</label>
|
|
299
|
+
<Textarea
|
|
300
|
+
value={clearance.cf_cookies}
|
|
301
|
+
onChange={(event) => setProxyRuntimeClearanceField("cf_cookies", event.target.value)}
|
|
302
|
+
placeholder="可选:foo=bar; cf_clearance=..."
|
|
303
|
+
className="min-h-24 rounded-xl border-stone-200 bg-white font-mono text-xs shadow-none"
|
|
304
|
+
disabled={!runtimeEnabled || clearanceMode !== "manual"}
|
|
305
|
+
/>
|
|
306
|
+
<p className="text-xs text-stone-500">
|
|
307
|
+
{hasStoredClearance ? "服务端已保存过 Cookie/clearance;留空保存不会清空已有值。" : "留空表示不使用手动 Cookie。"}
|
|
308
|
+
</p>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div className="space-y-2 md:col-span-2">
|
|
312
|
+
<label className="text-sm text-stone-700">单独 cf_clearance</label>
|
|
313
|
+
<Input
|
|
314
|
+
value={clearance.cf_clearance}
|
|
315
|
+
onChange={(event) => setProxyRuntimeClearanceField("cf_clearance", event.target.value)}
|
|
316
|
+
placeholder="可选:只填写 cf_clearance 值"
|
|
317
|
+
className="h-10 rounded-xl border-stone-200 bg-white font-mono text-xs"
|
|
318
|
+
disabled={!runtimeEnabled || clearanceMode !== "manual"}
|
|
319
|
+
/>
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
<label className="flex items-center gap-3 rounded-xl border border-stone-200 bg-stone-50 px-4 py-3 text-sm text-stone-700">
|
|
323
|
+
<Checkbox
|
|
324
|
+
checked={Boolean(clearance.warm_up_on_start)}
|
|
325
|
+
onCheckedChange={(checked) => setProxyRuntimeClearanceField("warm_up_on_start", Boolean(checked))}
|
|
326
|
+
disabled={!runtimeEnabled || clearanceMode === "none"}
|
|
327
|
+
/>
|
|
328
|
+
启动时预热 Clearance
|
|
329
|
+
</label>
|
|
330
|
+
|
|
331
|
+
<div className="space-y-2">
|
|
332
|
+
<label className="text-sm text-stone-700">测试目标 URL</label>
|
|
333
|
+
<Input
|
|
334
|
+
value={targetUrl}
|
|
335
|
+
onChange={(event) => setTargetUrl(event.target.value)}
|
|
336
|
+
placeholder="https://chatgpt.com"
|
|
337
|
+
className="h-10 rounded-xl border-stone-200 bg-white"
|
|
338
|
+
disabled={!runtimeEnabled || clearanceMode === "none"}
|
|
339
|
+
/>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div className="flex justify-end md:col-span-2">
|
|
343
|
+
<Button
|
|
344
|
+
type="button"
|
|
345
|
+
variant="outline"
|
|
346
|
+
className="h-9 rounded-xl border-stone-200 bg-white px-4 text-stone-700"
|
|
347
|
+
onClick={() => void handleTestClearance()}
|
|
348
|
+
disabled={isTestingClearance || !runtimeEnabled || clearanceMode === "none"}
|
|
349
|
+
>
|
|
350
|
+
{isTestingClearance ? <LoaderCircle className="size-4 animate-spin" /> : <ShieldCheck className="size-4" />}
|
|
351
|
+
测试 Clearance
|
|
352
|
+
</Button>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
{clearanceResult ? (
|
|
356
|
+
<div className={`rounded-xl border px-3 py-2 text-xs leading-6 md:col-span-2 ${clearanceResult.ok ? "border-emerald-200 bg-emerald-50 text-emerald-800" : "border-rose-200 bg-rose-50 text-rose-800"}`}>
|
|
357
|
+
{clearanceResult.ok
|
|
358
|
+
? `Clearance 可用:${clearanceResult.has_cookies ? "已获取 Cookie" : "无 Cookie"},用时 ${clearanceResult.latency_ms} ms`
|
|
359
|
+
: `Clearance 不可用:${clearanceResult.error ?? clearanceResult.status}(用时 ${clearanceResult.latency_ms} ms)`}
|
|
360
|
+
</div>
|
|
361
|
+
) : null}
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
|
|
365
|
+
<div className="flex justify-end">
|
|
366
|
+
<Button
|
|
367
|
+
type="button"
|
|
368
|
+
className="h-10 rounded-xl bg-stone-950 px-5 text-white hover:bg-stone-800"
|
|
369
|
+
onClick={() => void saveConfig()}
|
|
370
|
+
disabled={isSavingConfig}
|
|
371
|
+
>
|
|
372
|
+
{isSavingConfig ? <LoaderCircle className="size-4 animate-spin" /> : <Save className="size-4" />}
|
|
373
|
+
保存配置
|
|
374
|
+
</Button>
|
|
375
|
+
</div>
|
|
376
|
+
</CardContent>
|
|
377
|
+
</Card>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Link2, LoaderCircle, PlugZap, Save } from "lucide-react";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
|
|
7
|
+
import { Badge } from "@/components/ui/badge";
|
|
8
|
+
import { Button } from "@/components/ui/button";
|
|
9
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
10
|
+
import { Input } from "@/components/ui/input";
|
|
11
|
+
import { testProxy, type ProxyTestResult } from "@/lib/api";
|
|
12
|
+
|
|
13
|
+
import { useSettingsStore } from "../store";
|
|
14
|
+
|
|
15
|
+
export function ProxySettingsCard() {
|
|
16
|
+
const [isTesting, setIsTesting] = useState(false);
|
|
17
|
+
const [testResult, setTestResult] = useState<ProxyTestResult | null>(null);
|
|
18
|
+
const config = useSettingsStore((state) => state.config);
|
|
19
|
+
const isLoadingConfig = useSettingsStore((state) => state.isLoadingConfig);
|
|
20
|
+
const isSavingConfig = useSettingsStore((state) => state.isSavingConfig);
|
|
21
|
+
const setProxy = useSettingsStore((state) => state.setProxy);
|
|
22
|
+
const saveConfig = useSettingsStore((state) => state.saveConfig);
|
|
23
|
+
|
|
24
|
+
const proxy = config?.proxy ?? "";
|
|
25
|
+
|
|
26
|
+
const handleTest = async () => {
|
|
27
|
+
const candidate = proxy.trim();
|
|
28
|
+
if (!candidate) {
|
|
29
|
+
toast.error("请先填写代理地址");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
setIsTesting(true);
|
|
33
|
+
setTestResult(null);
|
|
34
|
+
try {
|
|
35
|
+
const data = await testProxy(candidate);
|
|
36
|
+
setTestResult(data.result);
|
|
37
|
+
if (data.result.ok) {
|
|
38
|
+
toast.success(`代理可用(${data.result.latency_ms} ms,HTTP ${data.result.status})`);
|
|
39
|
+
} else {
|
|
40
|
+
toast.error(`代理不可用:${data.result.error ?? "未知错误"}`);
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
toast.error(error instanceof Error ? error.message : "测试代理失败");
|
|
44
|
+
} finally {
|
|
45
|
+
setIsTesting(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Card className="rounded-2xl border-white/80 bg-white/90 shadow-sm">
|
|
51
|
+
<CardContent className="space-y-6 p-6">
|
|
52
|
+
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
53
|
+
<div className="flex items-center gap-3">
|
|
54
|
+
<div className="flex size-10 items-center justify-center rounded-xl bg-stone-100">
|
|
55
|
+
<Link2 className="size-5 text-stone-600" />
|
|
56
|
+
</div>
|
|
57
|
+
<div>
|
|
58
|
+
<h2 className="text-lg font-semibold tracking-tight">全局代理</h2>
|
|
59
|
+
<p className="text-sm text-stone-500">为系统中的出站请求配置统一代理,保存后会立即生效。</p>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<Badge variant={proxy.trim() ? "success" : "secondary"} className="w-fit rounded-md px-2.5 py-1">
|
|
63
|
+
{proxy.trim() ? "已配置" : "未配置"}
|
|
64
|
+
</Badge>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{isLoadingConfig ? (
|
|
68
|
+
<div className="flex items-center justify-center py-10">
|
|
69
|
+
<LoaderCircle className="size-5 animate-spin text-stone-400" />
|
|
70
|
+
</div>
|
|
71
|
+
) : (
|
|
72
|
+
<>
|
|
73
|
+
<div className="space-y-2">
|
|
74
|
+
<label className="text-sm font-medium text-stone-700">代理地址</label>
|
|
75
|
+
<Input
|
|
76
|
+
value={proxy}
|
|
77
|
+
onChange={(event) => {
|
|
78
|
+
setProxy(event.target.value);
|
|
79
|
+
setTestResult(null);
|
|
80
|
+
}}
|
|
81
|
+
placeholder="http://user:pass@127.0.0.1:7890"
|
|
82
|
+
className="h-11 rounded-xl border-stone-200 bg-white"
|
|
83
|
+
/>
|
|
84
|
+
<p className="text-sm text-stone-500">
|
|
85
|
+
留空表示不使用代理。请按完整地址填写,例如 `http://127.0.0.1:7890`、`http://用户名:密码@127.0.0.1:7890` 或 `socks5://127.0.0.1:7890`。
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{testResult ? (
|
|
90
|
+
<div
|
|
91
|
+
className={`rounded-xl border px-4 py-3 text-sm leading-6 ${
|
|
92
|
+
testResult.ok
|
|
93
|
+
? "border-emerald-200 bg-emerald-50 text-emerald-800"
|
|
94
|
+
: "border-rose-200 bg-rose-50 text-rose-800"
|
|
95
|
+
}`}
|
|
96
|
+
>
|
|
97
|
+
{testResult.ok
|
|
98
|
+
? `代理可用:HTTP ${testResult.status},用时 ${testResult.latency_ms} ms`
|
|
99
|
+
: `代理不可用:${testResult.error ?? "未知错误"}(用时 ${testResult.latency_ms} ms)`}
|
|
100
|
+
</div>
|
|
101
|
+
) : null}
|
|
102
|
+
|
|
103
|
+
<div className="flex justify-end gap-2">
|
|
104
|
+
<Button
|
|
105
|
+
variant="outline"
|
|
106
|
+
className="h-10 rounded-xl border-stone-200 bg-white px-5 text-stone-700"
|
|
107
|
+
onClick={() => void handleTest()}
|
|
108
|
+
disabled={isTesting || isLoadingConfig}
|
|
109
|
+
>
|
|
110
|
+
{isTesting ? <LoaderCircle className="size-4 animate-spin" /> : <PlugZap className="size-4" />}
|
|
111
|
+
测试代理
|
|
112
|
+
</Button>
|
|
113
|
+
<Button
|
|
114
|
+
className="h-10 rounded-xl bg-stone-950 px-5 text-white hover:bg-stone-800"
|
|
115
|
+
onClick={() => void saveConfig()}
|
|
116
|
+
disabled={isSavingConfig}
|
|
117
|
+
>
|
|
118
|
+
{isSavingConfig ? <LoaderCircle className="size-4 animate-spin" /> : <Save className="size-4" />}
|
|
119
|
+
保存配置
|
|
120
|
+
</Button>
|
|
121
|
+
</div>
|
|
122
|
+
</>
|
|
123
|
+
)}
|
|
124
|
+
</CardContent>
|
|
125
|
+
</Card>
|
|
126
|
+
);
|
|
127
|
+
}
|