import type { AdminAccountListResponse, AdminConfirmEditorShowcaseCampaignImageUploadRequest, AdminCreateAccountRequest, AdminCreateAccountResponse, AdminCreateEditorShowcaseCampaignImageUploadTicketRequest, AdminCreateEditorShowcaseCampaignImageUploadTicketResponse, AdminDashboardQuery, AdminDashboardResponse, AdminDatabaseTableListResponse, AdminDatabaseTableRowsQuery, AdminDatabaseTableRowsResponse, AdminDebugHttpRequest, AdminDebugHttpResponse, AdminDirectUploadTicketPayload, AdminDisableProfileRedeemCodeRequest, AdminDisableProfileTaskConfigRequest, AdminEditorAssetListQuery, AdminEditorAssetListResponse, AdminEditorShowcaseAssetResponse, AdminEditorShowcaseCampaignResponse, AdminEditorShowcaseDisplayRequest, AdminEditorShowcaseListQuery, AdminEditorShowcaseListResponse, AdminEditorShowcaseReviewRequest, AdminErrorReportDetail, AdminErrorReportEntry, AdminErrorReportListResponse, AdminExternalApiKeyListQuery, AdminExternalApiKeyListResponse, AdminFeatureGateConfigResponse, AdminLoginResponse, AdminMeResponse, AdminOverviewResponse, AdminRechargeOrderListQuery, AdminRechargeOrderListResponse, AdminRechargeRefundActionResponse, AdminRechargeRefundExecuteRequest, AdminRechargeRefundManualReviewResolveRequest, AdminRechargeRefundPreviewRequest, AdminRechargeRefundPreviewResponse, AdminRechargeRefundRegisterRequest, AdminTrackingEventKeyListResponse, AdminTrackingEventListQuery, AdminTrackingEventListResponse, AdminUpdateAccountRequest, AdminUpdateAccountResponse, AdminUploadedEditorShowcaseCampaignImage, AdminUpsertEditorShowcaseCampaignRequest, AdminUpsertFeatureGateConfigRequest, AdminUpsertProfileInviteCodeRequest, AdminUpsertProfileRechargeProductRequest, AdminUpsertProfileRedeemCodeRequest, AdminUpsertProfileTaskConfigRequest, AdminUpsertProfileWalletConfigRequest, AdminUserConsumptionReconcileRequest, AdminUserConsumptionReconcileResponse, AdminUserDetailQuery, AdminUserDetailResponse, AdminWalletRestrictionRequest, AdminWalletRestrictionResponse, ApiErrorEnvelope, ApiMeta, ApiSuccessEnvelope, EditorGenerationPricingConfigPayload, ProfileInviteCodeAdminListResponse, ProfileInviteCodeAdminResponse, ProfileRechargeProductConfigAdminListResponse, ProfileRechargeProductConfigAdminResponse, ProfileRedeemCodeAdminListResponse, ProfileRedeemCodeAdminResponse, ProfileTaskConfigAdminListResponse, ProfileTaskConfigAdminResponse, ProfileWalletConfigAdminResponse, } from './adminApiTypes'; const API_RESPONSE_ENVELOPE_HEADER = 'x-genarrative-response-envelope'; const ADMIN_API_BASE_URL = normalizeBaseUrl( import.meta.env.VITE_ADMIN_API_BASE_URL ?? '', ); interface AdminRequestOptions { method?: string; token?: string; body?: unknown; headers?: Record; signal?: AbortSignal; } interface AdminAssetReadUrlQuery { objectKey?: string | null; legacyPublicPath?: string | null; expireSeconds?: number | null; } export interface AdminAssetReadUrlResponse { read?: { objectKey?: string; signedUrl?: string; expiresAt?: string; }; signedUrl?: string; objectKey?: string; expiresAt?: string; } export class AdminApiError extends Error { status: number; code: string; details: Record | null; meta: ApiMeta | null; responseText: string; constructor(params: { message: string; status: number; code?: string; details?: Record | null; meta?: ApiMeta | null; responseText?: string; }) { super(params.message); this.name = 'AdminApiError'; this.status = params.status; this.code = params.code ?? 'ADMIN_API_ERROR'; this.details = params.details ?? null; this.meta = params.meta ?? null; this.responseText = params.responseText ?? ''; } } export function isAdminApiError(error: unknown): error is AdminApiError { return error instanceof AdminApiError; } export function formatAdminApiError(error: unknown) { if (isAdminApiError(error)) { return error.message; } if (error instanceof Error && error.message.trim()) { return error.message; } return '请求失败'; } export async function request( path: string, options: AdminRequestOptions = {}, ): Promise { const method = options.method ?? 'GET'; const headers: Record = { Accept: 'application/json', [API_RESPONSE_ENVELOPE_HEADER]: 'v1', ...(options.headers ?? {}), }; const token = options.token?.trim(); if (token) { headers.Authorization = `Bearer ${token}`; } const init: RequestInit = { method, headers, signal: options.signal, }; if (typeof options.body !== 'undefined') { headers['Content-Type'] = 'application/json'; init.body = JSON.stringify(options.body); } const response = await fetch(buildRequestUrl(path), init); const responseText = await response.text(); const payload = parseJsonResponse(responseText); if (!response.ok) { throw buildAdminApiError(response, payload, responseText); } return unwrapSuccessPayload(payload); } export function loginAdmin(username: string, password: string) { return request('/admin/api/login', { method: 'POST', body: { username, password }, }); } export function getAdminMe(token: string) { return request('/admin/api/me', { token }); } export function listAdminAccounts(token: string) { return request('/admin/api/accounts', { token }); } export function createAdminAccount( token: string, payload: AdminCreateAccountRequest, ) { return request('/admin/api/accounts', { method: 'POST', token, body: payload, }); } export function updateAdminAccount( token: string, accountId: string, payload: AdminUpdateAccountRequest, ) { return request( `/admin/api/accounts/${encodeURIComponent(accountId)}`, { method: 'PUT', token, body: payload }, ); } export function getAdminOverview(token: string) { return request('/admin/api/overview', { token }); } export function getAdminDashboard( token: string, query: AdminDashboardQuery = {}, ) { return request( `/admin/api/dashboard${buildDashboardQuery(query)}`, { token }, ); } export function getAdminDatabaseTables(token: string) { return request('/admin/api/database/tables', { token, }); } export function getAdminDatabaseTableRows( token: string, tableName: string, query: AdminDatabaseTableRowsQuery = {}, ) { return request( `/admin/api/database/tables/${encodeURIComponent(tableName)}/rows${buildDatabaseTableRowsQuery(query)}`, { token }, ); } export function getAdminExternalApiKeys( token: string, query: AdminExternalApiKeyListQuery = {}, ) { return request( `/admin/api/external-api-keys${buildExternalApiKeyQuery(query)}`, { token }, ); } export function debugAdminHttp(token: string, payload: AdminDebugHttpRequest) { return request('/admin/api/debug/http', { method: 'POST', token, body: payload, }); } export function listAdminTrackingEvents( token: string, query: AdminTrackingEventListQuery = {}, ) { return request( `/admin/api/tracking/events${buildQueryString((params) => { appendQueryParam(params, 'eventKey', query.eventKey); appendQueryParam(params, 'userId', query.userId); appendQueryParam(params, 'scopeKind', query.scopeKind); appendQueryParam(params, 'scopeId', query.scopeId); appendQueryParam(params, 'startDate', query.startDate); appendQueryParam(params, 'endDate', query.endDate); appendNumericQueryParam(params, 'limit', query.limit); if (query.exportAll) params.set('exportAll', 'true'); })}`, { token }, ); } export function listAdminTrackingEventKeys(token: string) { return request( '/admin/api/tracking/event-keys', { token }, ); } export function listAdminErrorReports( token: string, query: { status?: string; fingerprint?: string; source?: string; limit?: number; offset?: number; } = {}, ) { return request( `/admin/api/error-reports${buildErrorReportQueryString(query)}`, { token }, ); } export function getAdminErrorReport(token: string, batchId: string) { return request( `/admin/api/error-reports/${encodeURIComponent(batchId)}`, { token }, ); } export function updateAdminErrorReport( token: string, batchId: string, payload: { status: string; note?: string }, ) { return request( `/admin/api/error-reports/${encodeURIComponent(batchId)}`, { method: 'PATCH', token, body: payload }, ); } export function getAdminErrorReportDownloadUrl(batchId: string) { return `/admin/api/error-reports/${encodeURIComponent(batchId)}/download`; } export async function downloadAdminErrorReport(token: string, batchId: string) { const response = await fetch( buildRequestUrl(getAdminErrorReportDownloadUrl(batchId)), { headers: { Authorization: `Bearer ${token}` }, }, ); if (!response.ok) { throw new AdminApiError({ message: `下载失败(${response.status})`, status: response.status, }); } return response.blob(); } export function getAdminFeatureGateConfig(token: string) { return request('/admin/api/feature-gates', { token, }); } export function upsertAdminFeatureGateConfig( token: string, payload: AdminUpsertFeatureGateConfigRequest, ) { return request('/admin/api/feature-gates', { method: 'PUT', token, body: payload, }); } export function getAdminEditorGenerationPricing(token: string) { return request( '/admin/api/editor-generation-pricing', { token }, ); } export function upsertAdminEditorGenerationPricing( token: string, payload: EditorGenerationPricingConfigPayload, ) { return request( '/admin/api/editor-generation-pricing', { method: 'POST', token, body: payload, }, ); } export function getAdminAssetReadUrl( token: string, query: AdminAssetReadUrlQuery, ) { return request( `/admin/api/assets/read-url${buildAssetReadUrlQuery(query)}`, { token }, ); } export function listAdminEditorAssets( token: string, query: AdminEditorAssetListQuery = {}, signal?: AbortSignal, ) { return request( `/admin/api/editor-assets${buildEditorAssetListQuery(query)}`, { token, signal }, ); } export function listAdminEditorShowcaseAssets( token: string, query: AdminEditorShowcaseListQuery = {}, ) { return request( `/admin/api/editor-showcase/assets${buildEditorShowcaseListQuery(query)}`, { token }, ); } export function reviewAdminEditorShowcaseAsset( token: string, payload: AdminEditorShowcaseReviewRequest, ) { return request( '/admin/api/editor-showcase/assets/review', { method: 'POST', token, body: payload, }, ); } export function updateAdminEditorShowcaseDisplay( token: string, payload: AdminEditorShowcaseDisplayRequest, ) { return request( '/admin/api/editor-showcase/assets/display', { method: 'POST', token, body: payload, }, ); } export function getAdminEditorShowcaseCampaign(token: string) { return request( '/admin/api/editor-showcase/campaign', { token }, ); } export function upsertAdminEditorShowcaseCampaign( token: string, payload: AdminUpsertEditorShowcaseCampaignRequest, ) { return request( '/admin/api/editor-showcase/campaign', { method: 'POST', token, body: payload, }, ); } export async function uploadAdminEditorShowcaseCampaignImage( token: string, file: File, ): Promise { const contentType = resolveAdminImageContentType(file); const dimensions = await readAdminImageFileDimensions(file); const response = await request( '/admin/api/editor-showcase/campaign/image-upload-ticket', { method: 'POST', token, body: { fileName: file.name.trim() || 'showcase-campaign.png', contentType, contentLength: file.size, } satisfies AdminCreateEditorShowcaseCampaignImageUploadTicketRequest, }, ); await postAdminDirectUploadFile(response.upload, file); const objectKey = response.upload.objectKey.trim().replace(/^\/+/u, ''); await request( '/admin/api/editor-showcase/campaign/image-upload-confirm', { method: 'POST', token, body: { bucket: response.upload.bucket, objectKey, contentType, contentLength: file.size, } satisfies AdminConfirmEditorShowcaseCampaignImageUploadRequest, }, ); return { imageSrc: objectKey ? `/${objectKey}` : response.upload.legacyPublicPath, imageObjectKey: objectKey, imageWidth: dimensions.imageWidth, imageHeight: dimensions.imageHeight, legacyPublicPath: response.upload.legacyPublicPath, }; } export function listProfileRedeemCodes(token: string) { return request( '/admin/api/profile/redeem-codes', { token }, ); } export function upsertProfileRedeemCode( token: string, payload: AdminUpsertProfileRedeemCodeRequest, ) { return request( '/admin/api/profile/redeem-codes', { method: 'POST', token, body: payload, }, ); } export function listProfileInviteCodes(token: string) { return request( '/admin/api/profile/invite-codes', { token }, ); } export function upsertProfileInviteCode( token: string, payload: AdminUpsertProfileInviteCodeRequest, ) { return request( '/admin/api/profile/invite-codes', { method: 'POST', token, body: payload, }, ); } export function disableProfileRedeemCode( token: string, payload: AdminDisableProfileRedeemCodeRequest, ) { return request( '/admin/api/profile/redeem-codes/disable', { method: 'POST', token, body: payload, }, ); } export function listProfileTaskConfigs(token: string) { return request( '/admin/api/profile/tasks', { token }, ); } export function upsertProfileTaskConfig( token: string, payload: AdminUpsertProfileTaskConfigRequest, ) { return request('/admin/api/profile/tasks', { method: 'POST', token, body: payload, }); } export function disableProfileTaskConfig( token: string, payload: AdminDisableProfileTaskConfigRequest, ) { return request( '/admin/api/profile/tasks/disable', { method: 'POST', token, body: payload, }, ); } export function getProfileWalletConfig(token: string) { return request( '/admin/api/profile/wallet-config', { token }, ); } export function upsertProfileWalletConfig( token: string, payload: AdminUpsertProfileWalletConfigRequest, ) { return request( '/admin/api/profile/wallet-config', { method: 'POST', token, body: payload, }, ); } export function listProfileRechargeProducts(token: string) { return request( '/admin/api/profile/recharge-products', { token }, ); } export function upsertProfileRechargeProduct( token: string, payload: AdminUpsertProfileRechargeProductRequest, ) { return request( '/admin/api/profile/recharge-products', { method: 'POST', token, body: payload, }, ); } export function listAdminRechargeOrders( token: string, query: AdminRechargeOrderListQuery = {}, ) { return request( `/admin/api/profile/recharge-orders${buildAdminRechargeOrderListQuery(query)}`, { token }, ); } export function getAdminUserDetail(token: string, query: AdminUserDetailQuery) { return request( `/admin/api/profile/users/detail${buildAdminUserDetailQuery(query)}`, { token }, ); } export function reconcileAdminUserConsumption( token: string, payload: AdminUserConsumptionReconcileRequest, ) { return request( '/admin/api/profile/users/reconcile-consumption', { method: 'POST', token, body: payload }, ); } export function previewAdminRechargeRefund( token: string, payload: AdminRechargeRefundPreviewRequest, ) { return request( '/admin/api/profile/recharge-refunds/preview', { method: 'POST', token, body: payload }, ); } export function executeAdminRechargeRefund( token: string, payload: AdminRechargeRefundExecuteRequest, ) { return request( '/admin/api/profile/recharge-refunds/execute', { method: 'POST', token, body: payload }, ); } export function registerAdminRechargeRefund( token: string, payload: AdminRechargeRefundRegisterRequest, ) { return request( '/admin/api/profile/recharge-refunds/register', { method: 'POST', token, body: payload }, ); } export function resolveAdminRechargeRefundManualReview( token: string, payload: AdminRechargeRefundManualReviewResolveRequest, ) { return request( '/admin/api/profile/recharge-refunds/manual-review/resolve', { method: 'POST', token, body: payload }, ); } export function updateAdminWalletRestriction( token: string, payload: AdminWalletRestrictionRequest, ) { return request( '/admin/api/profile/wallet-restriction', { method: 'POST', token, body: payload }, ); } function normalizeBaseUrl(value: string) { return value.trim().replace(/\/+$/, ''); } function buildRequestUrl(path: string) { const normalizedPath = path.startsWith('/') ? path : `/${path}`; return `${ADMIN_API_BASE_URL}${normalizedPath}`; } function buildAssetReadUrlQuery(query: AdminAssetReadUrlQuery) { const params = new URLSearchParams(); const objectKey = query.objectKey?.trim().replace(/^\/+/u, '') ?? ''; const legacyPublicPath = query.legacyPublicPath?.trim() ?? ''; if (objectKey) { params.set('objectKey', objectKey); } else if (legacyPublicPath) { params.set('legacyPublicPath', `/${legacyPublicPath.replace(/^\/+/u, '')}`); } if ( typeof query.expireSeconds === 'number' && Number.isFinite(query.expireSeconds) && query.expireSeconds > 0 ) { params.set('expireSeconds', String(Math.floor(query.expireSeconds))); } const queryString = params.toString(); return queryString ? `?${queryString}` : ''; } function resolveAdminImageContentType(file: File) { const declaredType = file.type.trim(); if (declaredType.startsWith('image/')) { return declaredType; } const extension = file.name .trim() .toLowerCase() .match(/\.([a-z0-9]+)$/u)?.[1]; if (extension === 'jpg' || extension === 'jpeg') { return 'image/jpeg'; } if (extension === 'png') { return 'image/png'; } if (extension === 'webp') { return 'image/webp'; } if (extension === 'gif') { return 'image/gif'; } return declaredType || 'application/octet-stream'; } function normalizeAdminImageDimensions(width: number, height: number) { const imageWidth = Math.round(width); const imageHeight = Math.round(height); if ( !Number.isFinite(imageWidth) || !Number.isFinite(imageHeight) || imageWidth <= 0 || imageHeight <= 0 ) { return null; } return { imageWidth, imageHeight }; } async function readAdminImageFileDimensions(file: File) { if (typeof createImageBitmap === 'function') { try { const bitmap = await createImageBitmap(file); const dimensions = normalizeAdminImageDimensions( bitmap.width, bitmap.height, ); bitmap.close(); if (dimensions) { return dimensions; } } catch { // Fall back to HTMLImageElement decoding below. } } if ( typeof Image === 'undefined' || typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function' ) { throw new Error('读取活动卡图片尺寸失败,请重新选择图片'); } return new Promise<{ imageWidth: number; imageHeight: number }>( (resolve, reject) => { const objectUrl = URL.createObjectURL(file); const image = new Image(); const cleanup = () => { if (typeof URL.revokeObjectURL === 'function') { URL.revokeObjectURL(objectUrl); } }; image.onload = () => { cleanup(); const dimensions = normalizeAdminImageDimensions( image.naturalWidth || image.width, image.naturalHeight || image.height, ); if (dimensions) { resolve(dimensions); return; } reject(new Error('读取活动卡图片尺寸失败,请重新选择图片')); }; image.onerror = () => { cleanup(); reject(new Error('读取活动卡图片尺寸失败,请重新选择图片')); }; image.src = objectUrl; }, ); } function buildAdminDirectUploadFormData( upload: AdminDirectUploadTicketPayload, file: File, ) { const formData = new FormData(); Object.entries(upload.formFields).forEach(([key, value]) => { if (value !== null && value !== undefined) { formData.append(key, value); } }); formData.append('file', file, file.name); return formData; } async function postAdminDirectUploadFile( upload: AdminDirectUploadTicketPayload, file: File, ) { const response = await fetch(upload.host, { method: 'POST', body: buildAdminDirectUploadFormData(upload, file), }); if (!response.ok) { throw new Error(`上传活动卡图片失败:HTTP ${response.status}`); } } function buildQueryString(append: (params: URLSearchParams) => void) { const params = new URLSearchParams(); append(params); return formatQueryString(params); } function buildErrorReportQueryString(query: { status?: string; fingerprint?: string; source?: string; limit?: number; offset?: number; }) { return buildQueryString((params) => { appendQueryParam(params, 'status', query.status); appendQueryParam(params, 'fingerprint', query.fingerprint); appendQueryParam(params, 'source', query.source); appendNumericQueryParam(params, 'limit', query.limit); appendNumericQueryParam(params, 'offset', query.offset); }); } function buildAdminRechargeOrderListQuery(query: AdminRechargeOrderListQuery) { return buildQueryString((params) => { appendQueryParam(params, 'orderId', query.orderId); appendQueryParam( params, 'providerTransactionId', query.providerTransactionId, ); appendQueryParam(params, 'userId', query.userId); appendQueryParam(params, 'publicUserCode', query.publicUserCode); appendQueryParam(params, 'paymentChannel', query.paymentChannel); appendQueryParam(params, 'status', query.status); appendQueryParam(params, 'createdAfter', query.createdAfter); appendQueryParam(params, 'createdBefore', query.createdBefore); appendNumericQueryParam(params, 'limit', query.limit); }); } function buildAdminUserDetailQuery(query: AdminUserDetailQuery) { const params = new URLSearchParams(); appendQueryParam(params, 'userId', query.userId); appendQueryParam(params, 'publicUserCode', query.publicUserCode); const queryString = params.toString(); return queryString ? `?${queryString}` : ''; } function buildDashboardQuery(query: AdminDashboardQuery) { const params = new URLSearchParams(); appendQueryParam(params, 'granularity', query.granularity); appendQueryParam(params, 'anchor', query.anchor); appendQueryParam(params, 'startDate', query.startDate); appendQueryParam(params, 'endDate', query.endDate); const queryString = params.toString(); return queryString ? `?${queryString}` : ''; } function buildDatabaseTableRowsQuery(query: AdminDatabaseTableRowsQuery) { const params = new URLSearchParams(); appendQueryParam(params, 'search', query.search); appendQueryParam(params, 'filters', query.filters); if (typeof query.limit === 'number' && Number.isFinite(query.limit)) { params.set('limit', String(query.limit)); } if (typeof query.page === 'number' && Number.isFinite(query.page)) { params.set('page', String(query.page)); } appendQueryParam(params, 'sortColumn', query.sortColumn); appendQueryParam(params, 'sortDirection', query.sortDirection); const queryString = params.toString(); return queryString ? `?${queryString}` : ''; } function buildExternalApiKeyQuery(query: AdminExternalApiKeyListQuery) { const params = new URLSearchParams(); appendQueryParam(params, 'ownerUserId', query.ownerUserId); appendQueryParam(params, 'publicUserCode', query.publicUserCode); appendQueryParam(params, 'keyId', query.keyId); appendQueryParam(params, 'name', query.name); appendQueryParam(params, 'keyPrefix', query.keyPrefix); appendQueryParam(params, 'createdAfter', query.createdAfter); appendQueryParam(params, 'createdBefore', query.createdBefore); appendQueryParam(params, 'status', query.status); if (typeof query.limit === 'number' && Number.isFinite(query.limit)) { params.set('limit', String(Math.floor(query.limit))); } if (typeof query.offset === 'number' && Number.isFinite(query.offset)) { params.set('offset', String(Math.floor(query.offset))); } appendQueryParam(params, 'sortColumn', query.sortColumn); appendQueryParam(params, 'sortDirection', query.sortDirection); const queryString = params.toString(); return queryString ? `?${queryString}` : ''; } function buildEditorAssetListQuery(query: AdminEditorAssetListQuery) { const params = new URLSearchParams(); appendQueryParam(params, 'cursor', query.cursor); appendQueryParam(params, 'ownerUserId', query.ownerUserId); appendQueryParam(params, 'keyword', query.keyword); appendQueryParam(params, 'createdAfter', query.createdAfter); appendQueryParam(params, 'createdBefore', query.createdBefore); if (typeof query.limit === 'number' && Number.isFinite(query.limit)) { params.set('limit', String(query.limit)); } const queryString = params.toString(); return queryString ? `?${queryString}` : ''; } function buildEditorShowcaseListQuery(query: AdminEditorShowcaseListQuery) { const params = new URLSearchParams(); appendQueryParam(params, 'cursor', query.cursor); appendQueryParam(params, 'ownerUserId', query.ownerUserId); appendQueryParam(params, 'reviewStatus', query.reviewStatus); appendQueryParam(params, 'submittedAfter', query.submittedAfter); appendQueryParam(params, 'submittedBefore', query.submittedBefore); if (typeof query.limit === 'number' && Number.isFinite(query.limit)) { params.set('limit', String(query.limit)); } const queryString = params.toString(); return queryString ? `?${queryString}` : ''; } function appendQueryParam( params: URLSearchParams, key: string, value: string | null | undefined, ) { const trimmed = value?.trim(); if (trimmed) { params.set(key, trimmed); } } function appendNumericQueryParam( params: URLSearchParams, key: string, value: number | null | undefined, ) { if (typeof value === 'number' && Number.isFinite(value)) { params.set(key, String(value)); } } function formatQueryString(params: URLSearchParams) { const queryString = params.toString(); return queryString ? `?${queryString}` : ''; } function parseJsonResponse(responseText: string): unknown { if (!responseText.trim()) { return null; } try { return JSON.parse(responseText) as unknown; } catch { return null; } } function unwrapSuccessPayload(payload: unknown): T { if (isRecord(payload) && 'data' in payload) { return (payload as ApiSuccessEnvelope).data as T; } return payload as T; } function buildAdminApiError( response: Response, payload: unknown, responseText: string, ) { const envelope = isRecord(payload) ? (payload as ApiErrorEnvelope) : null; const errorPayload = envelope?.error; const details = isRecord(errorPayload?.details) ? errorPayload.details : null; const detailsMessage = typeof details?.message === 'string' ? details.message.trim() : ''; const payloadMessage = typeof errorPayload?.message === 'string' ? errorPayload.message.trim() : ''; const topLevelMessage = typeof envelope?.message === 'string' ? envelope.message.trim() : ''; const message = detailsMessage || payloadMessage || topLevelMessage || response.statusText || `HTTP ${response.status}`; return new AdminApiError({ message, status: response.status, code: errorPayload?.code, details, meta: envelope?.meta ?? null, responseText, }); } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } export function getAgcModelCatalog(token: string) { return request( '/admin/api/agc-models', { token }, ); } export function saveAgcModelCatalog( token: string, body: import('./adminApiTypes').AdminAgcModelCatalog, ) { return request( '/admin/api/agc-models', { token, method: 'PUT', body }, ); }