dsh-plugin-image-workbench 1.1.5 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.en.md +5 -3
- package/README.md +7 -3
- package/client.js +241 -241
- package/cordis.patch.yml +14 -14
- package/host.js +51 -0
- package/install.mjs +333 -387
- package/package.json +3 -2
- package/scripts/fetch-style-images.mjs +104 -0
- package/scripts/vendor-snapshot.py +154 -154
- package/server/access.py +26 -26
- package/server/app.py +1329 -1329
- package/server/catalog.py +147 -147
- package/server/composer.py +391 -391
- package/server/drafts.py +158 -158
- package/server/history.py +575 -575
- package/server/imaging.py +172 -172
- package/server/pool.py +1335 -1335
- package/server/pool_admin.py +921 -921
- package/server/pool_service.py +1073 -1073
- package/server/requirements.txt +6 -6
- package/server/smoke_check.py +402 -402
- package/server/static/access.html +202 -202
- package/vendor/chatgpt2api/source/.env.example +82 -82
- package/vendor/chatgpt2api/source/.python-version +1 -1
- package/vendor/chatgpt2api/source/LICENSE +21 -21
- package/vendor/chatgpt2api/source/README.md +391 -391
- package/vendor/chatgpt2api/source/VERSION +1 -1
- package/vendor/chatgpt2api/source/api/__init__.py +2 -2
- package/vendor/chatgpt2api/source/api/accounts.py +537 -537
- package/vendor/chatgpt2api/source/api/ai.py +217 -217
- package/vendor/chatgpt2api/source/api/app.py +62 -62
- package/vendor/chatgpt2api/source/api/errors.py +46 -46
- package/vendor/chatgpt2api/source/api/image_inputs.py +309 -309
- package/vendor/chatgpt2api/source/api/image_tasks.py +120 -120
- package/vendor/chatgpt2api/source/api/support.py +134 -134
- package/vendor/chatgpt2api/source/api/system.py +365 -365
- package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -50
- package/vendor/chatgpt2api/source/config.default.json +108 -108
- package/vendor/chatgpt2api/source/main.py +9 -9
- package/vendor/chatgpt2api/source/pyproject.toml +27 -27
- package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -186
- package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -163
- package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -13
- package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -385
- package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -129
- package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -92
- package/vendor/chatgpt2api/source/services/account_service.py +1757 -1757
- package/vendor/chatgpt2api/source/services/auth_service.py +253 -253
- package/vendor/chatgpt2api/source/services/backup_service.py +671 -671
- package/vendor/chatgpt2api/source/services/config.py +667 -667
- package/vendor/chatgpt2api/source/services/content_filter.py +242 -242
- package/vendor/chatgpt2api/source/services/cpa_service.py +315 -315
- package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -260
- package/vendor/chatgpt2api/source/services/image_service.py +376 -376
- package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -405
- package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -76
- package/vendor/chatgpt2api/source/services/image_task_service.py +550 -550
- package/vendor/chatgpt2api/source/services/log_service.py +337 -337
- package/vendor/chatgpt2api/source/services/model_service.py +182 -182
- package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -267
- package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -2763
- package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -32
- package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -2
- package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -307
- package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -238
- package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -1644
- package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -124
- package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -19
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -316
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -86
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -42
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -53
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -453
- package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -160
- package/vendor/chatgpt2api/source/services/proxy_service.py +648 -648
- package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -5
- package/vendor/chatgpt2api/source/services/storage/base.py +38 -38
- package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -194
- package/vendor/chatgpt2api/source/services/storage/factory.py +104 -104
- package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -183
- package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -95
- package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -531
- package/vendor/chatgpt2api/source/utils/helper.py +469 -469
- package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -346
- package/vendor/chatgpt2api/source/utils/log.py +110 -110
- package/vendor/chatgpt2api/source/utils/pkce.py +19 -19
- package/vendor/chatgpt2api/source/utils/pow.py +207 -207
- package/vendor/chatgpt2api/source/utils/sentinel.py +159 -159
- package/vendor/chatgpt2api/source/utils/turnstile.py +184 -184
- package/vendor/chatgpt2api/source/uv.lock +904 -904
- package/vendor/chatgpt2api/source/web/bun.lock +1501 -1501
- package/vendor/chatgpt2api/source/web/components.json +24 -24
- package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -17
- package/vendor/chatgpt2api/source/web/next.config.ts +40 -40
- package/vendor/chatgpt2api/source/web/package.json +58 -58
- package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -5
- package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -925
- package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -1307
- package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -224
- package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -444
- package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -9
- package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -9
- package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -172
- package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -169
- package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -39
- package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -59
- package/vendor/chatgpt2api/source/web/src/app/globals.css +230 -230
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -542
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -554
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -176
- package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -1799
- package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -732
- package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -50
- package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -101
- package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -356
- package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -30
- package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -341
- package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -447
- package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -71
- package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -491
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -108
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -169
- package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -185
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -379
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -127
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -200
- package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -12
- package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -886
- package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -81
- package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -422
- package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -135
- package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -1069
- package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -49
- package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -24
- package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -434
- package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -46
- package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -18
- package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -14
- package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -263
- package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -256
- package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -41
- package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -59
- package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -46
- package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -61
- package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -27
- package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -267
- package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -132
- package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -13
- package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -19
- package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -24
- package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -149
- package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -38
- package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -175
- package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -143
- package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -29
- package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -91
- package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -269
- package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -18
- package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -127
- package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -6
- package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -85
- package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -926
- package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -26
- package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -25
- package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -93
- package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -89
- package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -6
- package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -112
- package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -33
- package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -318
- package/vendor/chatgpt2api/source/web/tsconfig.json +42 -42
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { login } from "@/lib/api";
|
|
4
|
-
import { clearStoredAuthSession, getStoredAuthSession, setStoredAuthSession, type StoredAuthSession } from "@/store/auth";
|
|
5
|
-
|
|
6
|
-
export async function getValidatedAuthSession(): Promise<StoredAuthSession | null> {
|
|
7
|
-
const storedSession = await getStoredAuthSession();
|
|
8
|
-
if (!storedSession) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const data = await login(storedSession.key);
|
|
14
|
-
const nextSession: StoredAuthSession = {
|
|
15
|
-
key: storedSession.key,
|
|
16
|
-
role: data.role,
|
|
17
|
-
subjectId: data.subject_id,
|
|
18
|
-
name: data.name,
|
|
19
|
-
};
|
|
20
|
-
await setStoredAuthSession(nextSession);
|
|
21
|
-
return nextSession;
|
|
22
|
-
} catch {
|
|
23
|
-
await clearStoredAuthSession();
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { login } from "@/lib/api";
|
|
4
|
+
import { clearStoredAuthSession, getStoredAuthSession, setStoredAuthSession, type StoredAuthSession } from "@/store/auth";
|
|
5
|
+
|
|
6
|
+
export async function getValidatedAuthSession(): Promise<StoredAuthSession | null> {
|
|
7
|
+
const storedSession = await getStoredAuthSession();
|
|
8
|
+
if (!storedSession) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const data = await login(storedSession.key);
|
|
14
|
+
const nextSession: StoredAuthSession = {
|
|
15
|
+
key: storedSession.key,
|
|
16
|
+
role: data.role,
|
|
17
|
+
subjectId: data.subject_id,
|
|
18
|
+
name: data.name,
|
|
19
|
+
};
|
|
20
|
+
await setStoredAuthSession(nextSession);
|
|
21
|
+
return nextSession;
|
|
22
|
+
} catch {
|
|
23
|
+
await clearStoredAuthSession();
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
export type ReleaseInfo = {
|
|
2
|
-
version: string;
|
|
3
|
-
date: string;
|
|
4
|
-
items: { type: string; content: string }[];
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export function parseChangelog(content: string): ReleaseInfo[] {
|
|
8
|
-
return content
|
|
9
|
-
.split(/^## /m)
|
|
10
|
-
.slice(1)
|
|
11
|
-
.map((block) => {
|
|
12
|
-
const [title = "", ...lines] = block.trim().split("\n");
|
|
13
|
-
const [, version = title.trim(), date = ""] =
|
|
14
|
-
title.match(/^(.+?)(?:\s+-\s+(.+))?$/) || [];
|
|
15
|
-
return {
|
|
16
|
-
version: version.trim(),
|
|
17
|
-
date: date.trim(),
|
|
18
|
-
items: lines
|
|
19
|
-
.map((line) => line.trim().match(/^\+\s+\[(.+?)\]\s+(.+)$/))
|
|
20
|
-
.filter((match): match is RegExpMatchArray => Boolean(match))
|
|
21
|
-
.map((match) => ({ type: match[1], content: match[2] })),
|
|
22
|
-
};
|
|
23
|
-
})
|
|
24
|
-
.filter((release) => release.items.length);
|
|
25
|
-
}
|
|
1
|
+
export type ReleaseInfo = {
|
|
2
|
+
version: string;
|
|
3
|
+
date: string;
|
|
4
|
+
items: { type: string; content: string }[];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function parseChangelog(content: string): ReleaseInfo[] {
|
|
8
|
+
return content
|
|
9
|
+
.split(/^## /m)
|
|
10
|
+
.slice(1)
|
|
11
|
+
.map((block) => {
|
|
12
|
+
const [title = "", ...lines] = block.trim().split("\n");
|
|
13
|
+
const [, version = title.trim(), date = ""] =
|
|
14
|
+
title.match(/^(.+?)(?:\s+-\s+(.+))?$/) || [];
|
|
15
|
+
return {
|
|
16
|
+
version: version.trim(),
|
|
17
|
+
date: date.trim(),
|
|
18
|
+
items: lines
|
|
19
|
+
.map((line) => line.trim().match(/^\+\s+\[(.+?)\]\s+(.+)$/))
|
|
20
|
+
.filter((match): match is RegExpMatchArray => Boolean(match))
|
|
21
|
+
.map((match) => ({ type: match[1], content: match[2] })),
|
|
22
|
+
};
|
|
23
|
+
})
|
|
24
|
+
.filter((release) => release.items.length);
|
|
25
|
+
}
|
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
import axios, {AxiosError, type AxiosRequestConfig} from "axios";
|
|
2
|
-
|
|
3
|
-
import webConfig from "@/constants/common-env";
|
|
4
|
-
import {clearStoredAuthSession, getStoredAuthKey} from "@/store/auth";
|
|
5
|
-
|
|
6
|
-
type RequestConfig = AxiosRequestConfig & {
|
|
7
|
-
redirectOnUnauthorized?: boolean;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
type ErrorPayload = {
|
|
11
|
-
detail?: string | { error?: string | { message?: string } };
|
|
12
|
-
error?: string | { message?: string };
|
|
13
|
-
message?: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
function errorMessageFromValue(value: unknown): string {
|
|
17
|
-
if (typeof value === "string") {
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
if (!value || typeof value !== "object") {
|
|
21
|
-
return "";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const item = value as { error?: unknown; message?: unknown };
|
|
25
|
-
if (typeof item.message === "string") {
|
|
26
|
-
return item.message;
|
|
27
|
-
}
|
|
28
|
-
return errorMessageFromValue(item.error);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const request = axios.create({
|
|
32
|
-
baseURL: webConfig.apiUrl.replace(/\/$/, ""),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
request.interceptors.request.use(async (config) => {
|
|
36
|
-
const nextConfig = {...config};
|
|
37
|
-
const authKey = await getStoredAuthKey();
|
|
38
|
-
const headers = {...(nextConfig.headers || {})} as Record<string, string>;
|
|
39
|
-
if (authKey && !headers.Authorization) {
|
|
40
|
-
headers.Authorization = `Bearer ${authKey}`;
|
|
41
|
-
}
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
43
|
-
// @ts-expect-error
|
|
44
|
-
nextConfig.headers = headers;
|
|
45
|
-
return nextConfig;
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
request.interceptors.response.use(
|
|
49
|
-
(response) => response,
|
|
50
|
-
async (error: AxiosError<ErrorPayload>) => {
|
|
51
|
-
const status = error.response?.status;
|
|
52
|
-
const shouldRedirect = (error.config as RequestConfig | undefined)?.redirectOnUnauthorized !== false;
|
|
53
|
-
if (status === 401 && shouldRedirect && typeof window !== "undefined") {
|
|
54
|
-
// Avoid redirect loop — only redirect if not already on /login
|
|
55
|
-
if (!window.location.pathname.startsWith("/login")) {
|
|
56
|
-
await clearStoredAuthSession();
|
|
57
|
-
window.location.replace("/login");
|
|
58
|
-
// Return a never-resolving promise to prevent further error handling
|
|
59
|
-
// while the browser navigates away
|
|
60
|
-
return new Promise(() => {});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const payload = error.response?.data;
|
|
65
|
-
const message =
|
|
66
|
-
errorMessageFromValue(payload?.detail) ||
|
|
67
|
-
errorMessageFromValue(payload?.error) ||
|
|
68
|
-
payload?.message ||
|
|
69
|
-
error.message ||
|
|
70
|
-
`请求失败 (${status || 500})`;
|
|
71
|
-
return Promise.reject(new Error(message));
|
|
72
|
-
},
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
type RequestOptions = {
|
|
76
|
-
method?: string;
|
|
77
|
-
body?: unknown;
|
|
78
|
-
headers?: Record<string, string>;
|
|
79
|
-
redirectOnUnauthorized?: boolean;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
export async function httpRequest<T>(path: string, options: RequestOptions = {}) {
|
|
83
|
-
const {method = "GET", body, headers, redirectOnUnauthorized = true} = options;
|
|
84
|
-
const config: RequestConfig = {
|
|
85
|
-
url: path,
|
|
86
|
-
method,
|
|
87
|
-
data: body,
|
|
88
|
-
headers,
|
|
89
|
-
redirectOnUnauthorized,
|
|
90
|
-
};
|
|
91
|
-
const response = await request.request<T>(config);
|
|
92
|
-
return response.data;
|
|
93
|
-
}
|
|
1
|
+
import axios, {AxiosError, type AxiosRequestConfig} from "axios";
|
|
2
|
+
|
|
3
|
+
import webConfig from "@/constants/common-env";
|
|
4
|
+
import {clearStoredAuthSession, getStoredAuthKey} from "@/store/auth";
|
|
5
|
+
|
|
6
|
+
type RequestConfig = AxiosRequestConfig & {
|
|
7
|
+
redirectOnUnauthorized?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type ErrorPayload = {
|
|
11
|
+
detail?: string | { error?: string | { message?: string } };
|
|
12
|
+
error?: string | { message?: string };
|
|
13
|
+
message?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function errorMessageFromValue(value: unknown): string {
|
|
17
|
+
if (typeof value === "string") {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
if (!value || typeof value !== "object") {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const item = value as { error?: unknown; message?: unknown };
|
|
25
|
+
if (typeof item.message === "string") {
|
|
26
|
+
return item.message;
|
|
27
|
+
}
|
|
28
|
+
return errorMessageFromValue(item.error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const request = axios.create({
|
|
32
|
+
baseURL: webConfig.apiUrl.replace(/\/$/, ""),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
request.interceptors.request.use(async (config) => {
|
|
36
|
+
const nextConfig = {...config};
|
|
37
|
+
const authKey = await getStoredAuthKey();
|
|
38
|
+
const headers = {...(nextConfig.headers || {})} as Record<string, string>;
|
|
39
|
+
if (authKey && !headers.Authorization) {
|
|
40
|
+
headers.Authorization = `Bearer ${authKey}`;
|
|
41
|
+
}
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
43
|
+
// @ts-expect-error
|
|
44
|
+
nextConfig.headers = headers;
|
|
45
|
+
return nextConfig;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
request.interceptors.response.use(
|
|
49
|
+
(response) => response,
|
|
50
|
+
async (error: AxiosError<ErrorPayload>) => {
|
|
51
|
+
const status = error.response?.status;
|
|
52
|
+
const shouldRedirect = (error.config as RequestConfig | undefined)?.redirectOnUnauthorized !== false;
|
|
53
|
+
if (status === 401 && shouldRedirect && typeof window !== "undefined") {
|
|
54
|
+
// Avoid redirect loop — only redirect if not already on /login
|
|
55
|
+
if (!window.location.pathname.startsWith("/login")) {
|
|
56
|
+
await clearStoredAuthSession();
|
|
57
|
+
window.location.replace("/login");
|
|
58
|
+
// Return a never-resolving promise to prevent further error handling
|
|
59
|
+
// while the browser navigates away
|
|
60
|
+
return new Promise(() => {});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const payload = error.response?.data;
|
|
65
|
+
const message =
|
|
66
|
+
errorMessageFromValue(payload?.detail) ||
|
|
67
|
+
errorMessageFromValue(payload?.error) ||
|
|
68
|
+
payload?.message ||
|
|
69
|
+
error.message ||
|
|
70
|
+
`请求失败 (${status || 500})`;
|
|
71
|
+
return Promise.reject(new Error(message));
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
type RequestOptions = {
|
|
76
|
+
method?: string;
|
|
77
|
+
body?: unknown;
|
|
78
|
+
headers?: Record<string, string>;
|
|
79
|
+
redirectOnUnauthorized?: boolean;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export async function httpRequest<T>(path: string, options: RequestOptions = {}) {
|
|
83
|
+
const {method = "GET", body, headers, redirectOnUnauthorized = true} = options;
|
|
84
|
+
const config: RequestConfig = {
|
|
85
|
+
url: path,
|
|
86
|
+
method,
|
|
87
|
+
data: body,
|
|
88
|
+
headers,
|
|
89
|
+
redirectOnUnauthorized,
|
|
90
|
+
};
|
|
91
|
+
const response = await request.request<T>(config);
|
|
92
|
+
return response.data;
|
|
93
|
+
}
|
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { useRouter } from "next/navigation";
|
|
5
|
-
|
|
6
|
-
import { getValidatedAuthSession } from "@/lib/auth-session";
|
|
7
|
-
import {
|
|
8
|
-
getDefaultRouteForRole,
|
|
9
|
-
type AuthRole,
|
|
10
|
-
type StoredAuthSession,
|
|
11
|
-
} from "@/store/auth";
|
|
12
|
-
|
|
13
|
-
type UseAuthGuardResult = {
|
|
14
|
-
isCheckingAuth: boolean;
|
|
15
|
-
session: StoredAuthSession | null;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export function useAuthGuard(allowedRoles?: AuthRole[]): UseAuthGuardResult {
|
|
19
|
-
const router = useRouter();
|
|
20
|
-
const [session, setSession] = useState<StoredAuthSession | null>(null);
|
|
21
|
-
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
|
22
|
-
const allowedRolesKey = (allowedRoles || []).join(",");
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
let active = true;
|
|
26
|
-
|
|
27
|
-
const load = async () => {
|
|
28
|
-
const roleList = allowedRolesKey ? (allowedRolesKey.split(",") as AuthRole[]) : [];
|
|
29
|
-
const storedSession = await getValidatedAuthSession();
|
|
30
|
-
if (!active) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!storedSession) {
|
|
35
|
-
setSession(null);
|
|
36
|
-
setIsCheckingAuth(false);
|
|
37
|
-
router.replace("/login");
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (roleList.length > 0 && !roleList.includes(storedSession.role)) {
|
|
42
|
-
setSession(storedSession);
|
|
43
|
-
setIsCheckingAuth(false);
|
|
44
|
-
router.replace(getDefaultRouteForRole(storedSession.role));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
setSession(storedSession);
|
|
49
|
-
setIsCheckingAuth(false);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
void load();
|
|
53
|
-
return () => {
|
|
54
|
-
active = false;
|
|
55
|
-
};
|
|
56
|
-
}, [allowedRolesKey, router]);
|
|
57
|
-
|
|
58
|
-
return { isCheckingAuth, session };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function useRedirectIfAuthenticated() {
|
|
62
|
-
const router = useRouter();
|
|
63
|
-
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
let active = true;
|
|
67
|
-
|
|
68
|
-
const load = async () => {
|
|
69
|
-
const storedSession = await getValidatedAuthSession();
|
|
70
|
-
if (!active) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (storedSession) {
|
|
75
|
-
router.replace(getDefaultRouteForRole(storedSession.role));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
setIsCheckingAuth(false);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
void load();
|
|
83
|
-
return () => {
|
|
84
|
-
active = false;
|
|
85
|
-
};
|
|
86
|
-
}, [router]);
|
|
87
|
-
|
|
88
|
-
return { isCheckingAuth };
|
|
89
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
|
|
6
|
+
import { getValidatedAuthSession } from "@/lib/auth-session";
|
|
7
|
+
import {
|
|
8
|
+
getDefaultRouteForRole,
|
|
9
|
+
type AuthRole,
|
|
10
|
+
type StoredAuthSession,
|
|
11
|
+
} from "@/store/auth";
|
|
12
|
+
|
|
13
|
+
type UseAuthGuardResult = {
|
|
14
|
+
isCheckingAuth: boolean;
|
|
15
|
+
session: StoredAuthSession | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function useAuthGuard(allowedRoles?: AuthRole[]): UseAuthGuardResult {
|
|
19
|
+
const router = useRouter();
|
|
20
|
+
const [session, setSession] = useState<StoredAuthSession | null>(null);
|
|
21
|
+
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
|
22
|
+
const allowedRolesKey = (allowedRoles || []).join(",");
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
let active = true;
|
|
26
|
+
|
|
27
|
+
const load = async () => {
|
|
28
|
+
const roleList = allowedRolesKey ? (allowedRolesKey.split(",") as AuthRole[]) : [];
|
|
29
|
+
const storedSession = await getValidatedAuthSession();
|
|
30
|
+
if (!active) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!storedSession) {
|
|
35
|
+
setSession(null);
|
|
36
|
+
setIsCheckingAuth(false);
|
|
37
|
+
router.replace("/login");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (roleList.length > 0 && !roleList.includes(storedSession.role)) {
|
|
42
|
+
setSession(storedSession);
|
|
43
|
+
setIsCheckingAuth(false);
|
|
44
|
+
router.replace(getDefaultRouteForRole(storedSession.role));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setSession(storedSession);
|
|
49
|
+
setIsCheckingAuth(false);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
void load();
|
|
53
|
+
return () => {
|
|
54
|
+
active = false;
|
|
55
|
+
};
|
|
56
|
+
}, [allowedRolesKey, router]);
|
|
57
|
+
|
|
58
|
+
return { isCheckingAuth, session };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function useRedirectIfAuthenticated() {
|
|
62
|
+
const router = useRouter();
|
|
63
|
+
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
let active = true;
|
|
67
|
+
|
|
68
|
+
const load = async () => {
|
|
69
|
+
const storedSession = await getValidatedAuthSession();
|
|
70
|
+
if (!active) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (storedSession) {
|
|
75
|
+
router.replace(getDefaultRouteForRole(storedSession.role));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setIsCheckingAuth(false);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
void load();
|
|
83
|
+
return () => {
|
|
84
|
+
active = false;
|
|
85
|
+
};
|
|
86
|
+
}, [router]);
|
|
87
|
+
|
|
88
|
+
return { isCheckingAuth };
|
|
89
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { clsx, type ClassValue } from "clsx";
|
|
2
|
-
import { twMerge } from "tailwind-merge";
|
|
3
|
-
|
|
4
|
-
export function cn(...inputs: ClassValue[]) {
|
|
5
|
-
return twMerge(clsx(inputs));
|
|
6
|
-
}
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
export function cn(...inputs: ClassValue[]) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|