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

609 lines
20 KiB
TypeScript

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;
billingOrderId?: string;
billingStatus?: BillingOrder["status"];
billingAmountMicrocredits: number;
billingAvailable: boolean;
source: string;
capability: "text" | "image" | "video" | "audio" | "";
operation?: string;
requestKind: "create" | "poll" | "download" | "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;
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[] }>;
};
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;
estimatedCostMicros: number;
costAvailable: boolean;
currency?: string;
};
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;
estimatedCostMicros: number;
costAvailable: boolean;
currency?: string;
}>;
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;
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;
};
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) {
const base = String(apiBaseURL).replace(/\/$/, "");
return `${base}/auth/linuxdo/start?next=${encodeURIComponent(next)}`;
}
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 }) {
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)}`);
}