Files
ttstd-infinite-canvas/web/src/services/api/auth.ts

625 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ModelChannel } from "@/stores/use-config-store";
import type { BillingOrder, CreditLedgerEntry } from "@/services/api/wallet";
import type { GenerationTask, TaskStatus } from "@/services/api/task-center";
import type { CanvasDrawingEngineSetting } from "@/lib/canvas/canvas-drawing-engine";
import type { FeatureAvailability } from "@/stores/use-user-store";
import { http, apiBaseURL } from "@/services/api/request";
import type { PublicLogicalModel } from "@/services/api/logical-models";
import type { OSSConnectionTestInput, OSSConnectionTestResult, OSSProvider, S3Preset } from "@/lib/oss-settings";
let authSessionRequest: Promise<AuthSessionPayload> | null = null;
let authSessionCache: { payload: AuthSessionPayload; expiresAt: number } | null = null;
function invalidateAuthSessionCache() {
authSessionCache = null;
}
export type LocalUser = {
id: string;
username: string;
email?: string;
displayName: string;
avatarUrl?: string;
identityProvider?: string;
identityId?: string;
identityUsername?: string;
role: "admin" | "user";
status: "active" | "disabled";
lastLoginAt?: string;
createdAt: string;
updatedAt: string;
};
export type AdminUser = LocalUser & {
availableMicrocredits: number;
reservedMicrocredits: number;
};
export type AuthSessionPayload = {
user: LocalUser | null;
logicalModels?: PublicLogicalModel[];
runtimeLimits?: RuntimeLimits;
drawingEngine?: CanvasDrawingEngineSetting;
features?: FeatureAvailability;
};
export type RuntimeLimits = {
activeTaskLimit: number;
resourceUploadMB: number;
recycleBinRetentionDays?: number;
};
export type ApiCallLog = {
id: string;
userId: string;
userDisplayName?: string;
userAccount?: string;
channelId: string;
channelName: string;
taskId?: string;
taskStatus?: TaskStatus;
mediaStage?: GenerationTask["mediaStage"];
billingOrderId?: string;
billingStatus?: BillingOrder["status"];
billingAmountMicrocredits: number;
billingAvailable: boolean;
source: string;
capability: "text" | "image" | "video" | "audio" | "";
operation?: string;
requestKind: "create" | "poll" | "download" | "upload" | "local_save" | "register" | "repair" | "";
billable: boolean;
apiFormat: string;
method: string;
path: string;
model: string;
status: "succeeded" | "failed";
statusCode: number;
durationMs: number;
pollCount: number;
providerStatus?: string;
inputTokens: number;
outputTokens: number;
cachedTokens: number;
usageAvailable: boolean;
mediaCount: number;
mediaPreviewUrl?: string;
mediaPreviewKind?: "image" | "video";
videoSeconds: number;
providerRequestId?: string;
estimatedCostMicros: number;
creditCostConfigured?: boolean;
creditCostMicrocredits?: number;
costAvailable: boolean;
currency?: string;
errorCode?: string;
error?: string;
concurrencyLimit: number;
upstreamUrl: string;
requestContentType?: string;
requestBody?: string;
responseBody?: string;
startedAt?: string;
createdAt: string;
};
export type AdminProviderTaskQueryResult = {
task: GenerationTask;
providerStatus: string;
recovered: boolean;
billingSettled: boolean;
};
export type AdminAuditEvent = {
id: string;
actorUserId: string;
action: string;
targetType: string;
targetId: string;
summary: string;
metadataJson?: string;
createdAt: string;
};
export type AdminUserDetail = {
user: LocalUser;
account: { userId: string; availableMicrocredits: number; reservedMicrocredits: number; version: number };
counts: { ledgerEntries: number; tasks: number; apiCalls: number; auditEvents: number };
storageUsage: {
assetCount: number;
assetBytes: number;
canvasCount: number;
canvasBytes: number;
taskCount: number;
taskBytes: number;
apiCallCount: number;
};
storedFileBytes: number;
dailyUploadBytes: number;
quota: RuntimeResourcePolicy;
};
export type AdminUserTask = {
id: string;
type: string;
status: "queued" | "running" | "succeeded" | "failed" | "cancelled";
stage: string;
progress: number;
model?: string;
providerRequestId?: string;
createdAt: string;
};
export type AnalyticsFilters = {
from?: string;
to?: string;
userId?: string;
model?: string;
channelId?: string;
capability?: string;
};
export type AdminReferenceData = {
users: Array<{ id: string; username: string; displayName: string }>;
channels: Array<{ id: string; name: string; enabled: boolean; models: string[]; modelDisplayNames?: string[] }>;
};
export type AnalyticsFinance = {
settledOrders: number;
costedOrders: number;
revenueMicrocredits: number;
costMicrocredits: number;
profitMicrocredits: number | null;
profitMargin: number | null;
};
export type AdminAnalytics = {
from: string;
to: string;
kpi: {
activeUsers: number;
dau: number;
wau: number;
mau: number;
generationTasks: number;
upstreamRequests: number;
successRate: number;
p95DurationMs: number;
currentQueuedTasks: number;
finance?: AnalyticsFinance | null;
};
trend: Array<{ day: string; tasks: number; requests: number; activeUsers: number; requestSuccessRate: number }>;
models: Array<{
model: string;
capability: string;
tasks: number;
requests: number;
uniqueUsers: number;
taskSuccessRate: number;
requestSuccessRate: number;
p50DurationMs: number;
p95DurationMs: number;
inputTokens: number;
outputTokens: number;
cachedTokens: number;
usageAvailable: boolean;
mediaCount: number;
videoSeconds: number;
finance?: AnalyticsFinance | null;
}>;
users: Array<{ userId: string; name: string; activeDays: number; tasks: number; agentMessages: number; canvasDays: number; assets: number; resources: number; commonModel?: string }>;
failures: Array<{ type: string; model: string; count: number; lastError?: string; lastSeenAt: string }>;
};
export type ModelPricing = {
id: string;
channelId?: string;
model: string;
capability: "text" | "image" | "video" | "audio";
currency: string;
inputPerMillionMicros: number;
outputPerMillionMicros: number;
cachedPerMillionMicros: number;
perRequestMicros: number;
perMediaMicros: number;
perVideoSecondMicros: number;
createdAt: string;
updatedAt: string;
};
export type PromptTemplate = {
id: string;
operation: string;
name: string;
version: number;
content: string;
outputType: "json" | "text";
enabled: boolean;
createdBy?: string;
createdAt: string;
updatedAt: string;
};
export type PromptTemplateVariable = {
label: string;
placeholder: string;
};
export type PromptOperationDefinition = {
operation: string;
label: string;
category: string;
description: string;
outputType: "json" | "text";
schemaKey?: string;
variables: PromptTemplateVariable[];
outputContract: string;
};
export type UserPromptCustomization = {
id: string;
operation: string;
mode: "inherit" | "append" | "rewrite";
content: string;
baseTemplateId: string;
updatedAt: string;
};
export type UserPromptPreference = {
definition: PromptOperationDefinition;
template: PromptTemplate | null;
customization?: UserPromptCustomization;
outdated: boolean;
};
export type AdminOSSSetting = {
enabled: boolean;
provider: OSSProvider;
s3Preset: S3Preset;
region: string;
endpoint: string;
cdnBaseUrl: string;
cdnAuthMode: "" | "public" | "qiniu" | string;
requireCDN: boolean;
allowPrivateProxy: boolean;
bucket: string;
accessKeyId: string;
accessKeySecret?: string;
hasAccessKeySecret: boolean;
sessionToken?: string;
hasSessionToken: boolean;
pathStyle: boolean;
allowUserS3: boolean;
publicBaseUrl: string;
pathPrefix: string;
testedAt?: string;
testedDigest?: string;
historyCount?: number;
referencedResourceCount?: number;
updatedBy?: string;
createdAt?: string;
updatedAt?: string;
};
export type AdminArkPrivateAssetSetting = {
enabled: boolean;
region: string;
projectName: string;
accessKeyId: string;
accessKeySecret?: string;
hasAccessKeySecret: boolean;
updatedBy?: string;
createdAt?: string;
updatedAt?: string;
};
export type RuntimeResourcePolicy = {
resourceUploadMB: number;
generatedFileMB: number;
dailyUploadMB: number;
storedFileGB: number;
structuredDataMB: number;
taskDataGB: number;
assetCount: number;
canvasCount: number;
taskCount: number;
apiCallLogCount: number;
recycleBinRetentionDays?: number;
};
export type RuntimeTaskPolicy = {
workerConcurrency: number;
channelConcurrency: number;
activeTaskLimit: number;
imageTimeoutMinutes: number;
textTimeoutMinutes: number;
audioTimeoutMinutes: number;
videoTimeoutMinutes: number;
storyboardTimeoutMinutes: number;
defaultTimeoutMinutes: number;
/** 画布 Agent 单步模型调用的输出上限(思考 + 正文 + 工具参数);0 表示不限制。 */
agentStepMaxOutputTokens: number;
/** 画布 Agent 单步模型调用的秒级墙钟;0 表示沿用文本任务超时。 */
agentStepTimeoutSeconds: number;
};
export type RuntimeRequestPolicy = {
taskCreatePerMinute: number;
resourceUploadPerMinute: number;
resourceImportPerMinute: number;
assetWritePerMinute: number;
canvasWritePerMinute: number;
registerPerHour: number;
emailCodePerHour: number;
loginIPPerTenMinutes: number;
loginAccountPerTenMinutes: number;
systemRelayPerMinute: number;
customRelayPerMinute: number;
customRelayConcurrency: number;
customRelayRequestMB: number;
customRelayResponseMB: number;
customRelayTimeoutMinutes: number;
systemRelayRequestMB: number;
systemRelayResponseMB: number;
channelCircuitFailureCount: number;
channelCircuitOpenSeconds: number;
};
export type RuntimePolicySetting = {
resource: RuntimeResourcePolicy;
task: RuntimeTaskPolicy;
request: RuntimeRequestPolicy;
configured?: boolean;
updatedBy?: string;
createdAt?: string;
updatedAt?: string;
};
export function getAuthSettings() {
return http.get<{ firstUser: boolean; registrationEnabled: boolean; linuxdoEnabled: boolean; emailEnabled: boolean; emailCodeRequired: boolean }>("/auth/settings");
}
export function linuxDOLoginURL(next: string, acceptedTerms?: boolean) {
const base = String(apiBaseURL).replace(/\/$/, "");
const termsQuery = acceptedTerms === undefined ? "" : `&acceptedTerms=${acceptedTerms ? "true" : "false"}`;
return `${base}/auth/linuxdo/start?next=${encodeURIComponent(next)}${termsQuery}`;
}
export function getAuthSession() {
const now = Date.now();
if (authSessionCache && authSessionCache.expiresAt > now) return Promise.resolve(authSessionCache.payload);
if (authSessionRequest) return authSessionRequest;
authSessionRequest = http.get<AuthSessionPayload>("/auth/session")
.then((payload) => {
authSessionCache = { payload, expiresAt: Date.now() + 5_000 };
return payload;
})
.finally(() => {
authSessionRequest = null;
});
return authSessionRequest;
}
export function getSystemChannels() {
return http.get<{ channels: ModelChannel[] }>("/channels/system");
}
export function getFeatureAvailability() {
return http.get<{ features: FeatureAvailability }>("/features");
}
export function getAdminFeatureAvailability() {
return http.get<{ features: FeatureAvailability }>("/admin/settings/features");
}
export function updateAdminFeatureAvailability(features: Partial<Pick<FeatureAvailability, "welcomeEnabled" | "shortDramaEnabled" | "taskCenterEnabled" | "creditsEnabled" | "customChannelsEnabled" | "frontendModelsEnabled" | "pluginCenterEnabled" | "systemPluginsVisibleToUsers">>) {
return http.patch<{ features: FeatureAvailability }>("/admin/settings/features", features);
}
export async function login(input: { username: string; password: string }) {
const result = await http.post<{ user: LocalUser }>("/auth/login", input);
// 登录会改变服务端会话身份,不能让登录前缓存的游客 session 污染后续恢复。
invalidateAuthSessionCache();
return result;
}
export function sendRegistrationEmailCode(email: string) {
return http.post<{ sent: boolean }>("/auth/email-code", { email });
}
export function sendPasswordResetEmailCode(email: string) {
return http.post<{ sent: boolean }>("/auth/password-reset-code", { email });
}
export function resetPassword(input: { email: string; emailCode: string; password: string }) {
return http.post<{ reset: boolean }>("/auth/password-reset", input);
}
export function register(input: { username: string; email?: string; emailCode?: string; displayName?: string; password: string; acceptedTerms: boolean }) {
return http.post<{ user: LocalUser }>("/auth/register", input);
}
export async function logout() {
const result = await http.post<{ ok: boolean }>("/auth/logout");
invalidateAuthSessionCache();
return result;
}
export type AdminListParams = { keyword?: string; status?: string; role?: string; page?: number; pageSize?: number };
export function listAdminUsers(params: AdminListParams = {}) {
return http.get<{ users: AdminUser[]; total: number; page: number; pageSize: number }>("/admin/users", { params });
}
export function createAdminUser(input: { username: string; displayName: string; email?: string; password: string; role: LocalUser["role"]; status: LocalUser["status"] }) {
return http.post<{ user: AdminUser }>("/admin/users", input);
}
export function getAdminReferences() {
return http.get<AdminReferenceData>("/admin/references");
}
export function getAdminUserDetail(id: string) {
return http.get<AdminUserDetail>(`/admin/users/${encodeURIComponent(id)}/detail`);
}
export function listAdminUserLedger(id: string, params: { page?: number; pageSize?: number; type?: string } = {}) {
return http.get<{ entries: CreditLedgerEntry[]; total: number; page: number; pageSize: number }>(`/admin/users/${encodeURIComponent(id)}/ledger`, { params });
}
export function listAdminUserTasks(id: string, params: { page?: number; pageSize?: number } = {}) {
return http.get<{ tasks: AdminUserTask[]; total: number; page: number; pageSize: number }>(`/admin/users/${encodeURIComponent(id)}/tasks`, { params });
}
export function listAdminUserAuditEvents(id: string, params: { page?: number; pageSize?: number } = {}) {
return http.get<{ events: AdminAuditEvent[]; total: number; page: number; pageSize: number }>(`/admin/users/${encodeURIComponent(id)}/audit-events`, { params });
}
export function updateAdminUser(id: string, input: Partial<Pick<LocalUser, "displayName" | "email" | "role" | "status">> & { password?: string }) {
return http.patch<{ user: LocalUser }>(`/admin/users/${encodeURIComponent(id)}`, input);
}
export function deleteAdminUser(id: string) {
return http.delete<{ ok: boolean }>(`/admin/users/${encodeURIComponent(id)}`);
}
export function bulkDisableAdminUsers(userIds: string[]) {
return http.post<{ users: LocalUser[]; disabledCount: number }>("/admin/users/bulk-disable", { userIds });
}
export function listAdminChannels(params: AdminListParams = {}) {
return http.get<{ channels: ModelChannel[]; total: number; page: number; pageSize: number }>("/admin/channels", { params });
}
export function createAdminChannel(input: Partial<ModelChannel> & { useGlobalConcurrency?: boolean }) {
return http.post<{ channel: ModelChannel }>("/admin/channels", input);
}
export function duplicateAdminChannel(id: string) {
return http.post<{ channel: ModelChannel }>(`/admin/channels/${encodeURIComponent(id)}/duplicate`);
}
export function updateAdminChannel(id: string, input: Partial<ModelChannel> & { useGlobalConcurrency?: boolean }) {
return http.patch<{ channel: ModelChannel }>(`/admin/channels/${encodeURIComponent(id)}`, input);
}
export function deleteAdminChannel(id: string) {
return http.delete<{ ok: boolean }>(`/admin/channels/${encodeURIComponent(id)}`);
}
export function listAdminPromptTemplates() {
return http.get<{ templates: PromptTemplate[]; definitions: PromptOperationDefinition[] }>("/admin/prompt-templates");
}
export function createAdminPromptTemplate(input: Pick<PromptTemplate, "operation" | "name" | "content"> & { enabled?: boolean }) {
return http.post<{ template: PromptTemplate }>("/admin/prompt-templates", input);
}
export function updateAdminPromptTemplate(id: string, input: Pick<PromptTemplate, "operation" | "name" | "content"> & { enabled?: boolean }) {
return http.patch<{ template: PromptTemplate }>(`/admin/prompt-templates/${encodeURIComponent(id)}`, input);
}
export function deleteAdminPromptTemplate(id: string) {
return http.delete<{ ok: boolean }>(`/admin/prompt-templates/${encodeURIComponent(id)}`);
}
export function listUserPromptPreferences() {
return http.get<{ preferences: UserPromptPreference[] }>("/settings/prompt-templates");
}
export function updateUserPromptCustomization(operation: string, input: Pick<UserPromptCustomization, "mode" | "content">) {
return http.patch<{ customization: UserPromptCustomization }>(`/settings/prompt-templates/${encodeURIComponent(operation)}`, input);
}
export function resetUserPromptCustomization(operation: string) {
return http.delete<{ ok: boolean }>(`/settings/prompt-templates/${encodeURIComponent(operation)}`);
}
export function getAdminOSSSetting() {
return http.get<{ setting: AdminOSSSetting }>("/admin/settings/oss");
}
export function updateAdminOSSSetting(input: Partial<AdminOSSSetting>) {
return http.patch<{ setting: AdminOSSSetting }>("/admin/settings/oss", input);
}
export function testAdminOSSConnection(input: OSSConnectionTestInput) {
return http.post<OSSConnectionTestResult>("/admin/settings/oss/test", input);
}
export function getAdminArkPrivateAssetSetting() {
return http.get<{ setting: AdminArkPrivateAssetSetting }>("/admin/settings/ark-private-assets");
}
export function updateAdminArkPrivateAssetSetting(input: Partial<AdminArkPrivateAssetSetting>) {
return http.patch<{ setting: AdminArkPrivateAssetSetting }>("/admin/settings/ark-private-assets", input);
}
export function getAdminRuntimePolicySetting() {
return http.get<{ setting: RuntimePolicySetting }>("/admin/settings/runtime-policy");
}
export function getAdminSelfUseRuntimePolicy() {
return http.get<{ setting: RuntimePolicySetting }>("/admin/settings/runtime-policy/self-use");
}
export function updateAdminRuntimePolicySetting(input: Pick<RuntimePolicySetting, "resource" | "task" | "request">) {
return http.put<{ setting: RuntimePolicySetting }>("/admin/settings/runtime-policy", input);
}
export function resetAdminRuntimePolicySetting() {
return http.delete<{ setting: RuntimePolicySetting }>("/admin/settings/runtime-policy");
}
export function getAdminDrawingEngineSetting() {
return http.get<{ setting: CanvasDrawingEngineSetting }>("/admin/settings/drawing-engine");
}
export function updateAdminDrawingEngineSetting(input: Pick<CanvasDrawingEngineSetting, "defaultEngine" | "tldrawLicenseKey">) {
return http.patch<{ setting: CanvasDrawingEngineSetting }>("/admin/settings/drawing-engine", input);
}
export type AdminApiLogParams = AdminListParams & { recordType?: "request" | "download" | "all" };
export function listAdminApiLogs(params: AdminApiLogParams = {}) {
return http.get<{ logs: ApiCallLog[]; total: number; page: number; pageSize: number }>("/admin/api-logs", { params });
}
export function getAdminApiLog(id: string) {
return http.get<{ log: ApiCallLog }>(`/admin/api-logs/${encodeURIComponent(id)}`);
}
export function queryAdminApiLogTask(id: string) {
return http.post<AdminProviderTaskQueryResult>(`/admin/api-logs/${encodeURIComponent(id)}/query-task`);
}
export async function exportAdminApiLogs(params: AdminApiLogParams & { ids?: string[] } = {}) {
const response = await http.raw<Blob>({ method: "get", url: "/admin/api-logs-export.csv", params: { ...params, ids: params.ids?.join(",") }, responseType: "blob" });
return response.data;
}
export function getAdminAnalytics(params: AnalyticsFilters) {
return http.get<AdminAnalytics>("/admin/analytics/overview", { params });
}
export async function exportAdminAnalytics(params: AnalyticsFilters) {
const response = await http.raw<Blob>({ method: "get", url: "/admin/analytics/export.csv", params, responseType: "blob" });
return response.data;
}
export function listAdminModelPricings() {
return http.get<{ pricings: ModelPricing[] }>("/admin/model-pricings");
}
export function createAdminModelPricing(input: Omit<ModelPricing, "id" | "createdAt" | "updatedAt">) {
return http.post<{ pricing: ModelPricing }>("/admin/model-pricings", input);
}
export function updateAdminModelPricing(id: string, input: Omit<ModelPricing, "id" | "createdAt" | "updatedAt">) {
return http.patch<{ pricing: ModelPricing }>(`/admin/model-pricings/${encodeURIComponent(id)}`, input);
}
export function deleteAdminModelPricing(id: string) {
return http.delete<{ ok: boolean }>(`/admin/model-pricings/${encodeURIComponent(id)}`);
}