li / frontend /src /features /settings /settings-api.ts
hf-deploy
Deploy Grok2API v3.0.7 to Hugging Face Space
0208fd2
Raw
History Blame Contribute Delete
8.66 kB
import { apiRequest } from "@/shared/api/client";
import { createObjectDecoder, decodeBooleanResult, hasShape, isArrayOf, isBoolean, isNumber, isOneOf, isOptional, isString } from "@/shared/api/decoder";
import type { SortOrder } from "@/shared/lib/table-sort";
export type SettingsConfigDTO = {
server: { maxConcurrentRequests: number };
providerBuild: { baseURL: string; fallbackBaseURL: string; clientVersion: string; clientIdentifier: string; tokenAuth: string; tokenAuthConfigured: boolean; userAgent: string };
providerWeb: {
baseURL: string; quotaTimeout: string; chatTimeout: string; imageTimeout: string; videoTimeout: string;
statsigMode: "manual" | "url"; statsigManualValue?: string; statsigManualConfigured: boolean; statsigSignerURL: string;
clearanceMode: "manual" | "flaresolverr"; flareSolverrURL: string; clearanceTimeout: string; clearanceRefresh: string;
mediaConcurrency: number; allowNSFW: boolean;
recoveryBackoffBase: string; recoveryBackoffMax: string;
};
providerConsole: { baseURL: string; chatTimeout: string };
batch: { importConcurrency: number; conversionConcurrency: number; syncConcurrency: number; refreshConcurrency: number; randomDelay: string };
media: {
maxImageBytes: number; maxTotalBytes: number; cleanupThresholdPercent: number;
cleanupInterval: string;
};
frontend: { publicApiBaseURL: string };
routing: { stickyTTL: string; cooldownBase: string; cooldownMax: string; capacityWait: string; maxAttempts: number; preferFreeBuild: boolean };
audit: { bufferSize: number; batchSize: number; flushInterval: string };
clientKeyDefaults: { rpmLimit: number; maxConcurrent: number };
accounts: {
autoCleanReauthEnabled: boolean;
autoCleanReauthInterval: string;
autoCleanReauthMinAge: string;
autoCleanIncludeDisabled: boolean;
};
};
export type EgressNodeDTO = {
id: string; name: string; scope: EgressScope; enabled: boolean;
proxyConfigured: boolean; userAgent: string; cookieConfigured: boolean;
accountBoundProxy: boolean; proxyPool: boolean;
health: number; failureCount: number; cooldownUntil?: string; lastError?: string;
};
export type EgressNodeInput = {
name: string; scope: EgressScope; enabled: boolean; proxyPool: boolean; proxyURL?: string;
clearProxyURL?: boolean; userAgent: string; cloudflareCookies?: string; clearCookies?: boolean;
};
export type EgressScope = "grok_build" | "grok_web" | "grok_console" | "grok_web_asset";
export type EgressNodeListDTO = { items: EgressNodeDTO[]; defaultUserAgents: Record<EgressScope, string> };
export type SettingsSnapshotDTO = {
config: SettingsConfigDTO;
recommendedProviderBuild: { clientVersion: string; userAgent: string };
updatedAt: string;
revision: string;
restartRequired: string[];
};
const settingsConfigValidator = hasShape({
server: hasShape({ maxConcurrentRequests: isNumber }),
providerBuild: hasShape({ baseURL: isString, fallbackBaseURL: isString, clientVersion: isString, clientIdentifier: isString, tokenAuth: isString, tokenAuthConfigured: isBoolean, userAgent: isString }),
providerWeb: hasShape({
baseURL: isString, quotaTimeout: isString, chatTimeout: isString, imageTimeout: isString, videoTimeout: isString,
statsigMode: isOneOf("manual", "url"), statsigManualValue: isOptional(isString), statsigManualConfigured: isBoolean,
statsigSignerURL: isString, clearanceMode: isOneOf("manual", "flaresolverr"), flareSolverrURL: isString,
clearanceTimeout: isString, clearanceRefresh: isString, mediaConcurrency: isNumber, allowNSFW: isBoolean, recoveryBackoffBase: isString, recoveryBackoffMax: isString,
}),
providerConsole: hasShape({ baseURL: isString, chatTimeout: isString }),
batch: hasShape({ importConcurrency: isNumber, conversionConcurrency: isNumber, syncConcurrency: isNumber, refreshConcurrency: isNumber, randomDelay: isString }),
media: hasShape({ maxImageBytes: isNumber, maxTotalBytes: isNumber, cleanupThresholdPercent: isNumber, cleanupInterval: isString }),
frontend: hasShape({ publicApiBaseURL: isString }),
routing: hasShape({ stickyTTL: isString, cooldownBase: isString, cooldownMax: isString, capacityWait: isString, maxAttempts: isNumber, preferFreeBuild: isBoolean }),
audit: hasShape({ bufferSize: isNumber, batchSize: isNumber, flushInterval: isString }),
clientKeyDefaults: hasShape({ rpmLimit: isNumber, maxConcurrent: isNumber }),
// 旧后端可无 accounts;decode 后由 withAccountsDefaults 补默认关闭策略。
accounts: isOptional(hasShape({
autoCleanReauthEnabled: isBoolean,
autoCleanReauthInterval: isString,
autoCleanReauthMinAge: isString,
autoCleanIncludeDisabled: isBoolean,
})),
});
const defaultAccountsConfig = (): SettingsConfigDTO["accounts"] => ({
autoCleanReauthEnabled: false,
autoCleanReauthInterval: "10m",
autoCleanReauthMinAge: "1h",
autoCleanIncludeDisabled: false,
});
function withAccountsDefaults(snapshot: SettingsSnapshotDTO): SettingsSnapshotDTO {
const accounts = snapshot.config.accounts ?? defaultAccountsConfig();
return {
...snapshot,
config: {
...snapshot.config,
accounts: {
autoCleanReauthEnabled: accounts.autoCleanReauthEnabled ?? false,
autoCleanReauthInterval: accounts.autoCleanReauthInterval || "10m",
autoCleanReauthMinAge: accounts.autoCleanReauthMinAge || "1h",
autoCleanIncludeDisabled: accounts.autoCleanIncludeDisabled ?? false,
},
},
};
}
const decodeSettingsSnapshotRaw = createObjectDecoder<SettingsSnapshotDTO>("settings", {
config: settingsConfigValidator,
recommendedProviderBuild: hasShape({ clientVersion: isString, userAgent: isString }),
updatedAt: isString,
revision: isString,
restartRequired: isArrayOf(isString),
});
const decodeSettingsSnapshot = (value: unknown) => withAccountsDefaults(decodeSettingsSnapshotRaw(value));
const egressNodeValidator = hasShape({
id: isString, name: isString, scope: isOneOf("grok_build", "grok_web", "grok_console", "grok_web_asset"), enabled: isBoolean,
proxyConfigured: isBoolean, userAgent: isString, cookieConfigured: isBoolean, accountBoundProxy: isBoolean, proxyPool: isBoolean, health: isNumber, failureCount: isNumber,
cooldownUntil: isOptional(isString), lastError: isOptional(isString),
});
const decodeEgressNode = createObjectDecoder<EgressNodeDTO>("egress node", {
id: isString, name: isString, scope: isOneOf("grok_build", "grok_web", "grok_console", "grok_web_asset"), enabled: isBoolean,
proxyConfigured: isBoolean, userAgent: isString, cookieConfigured: isBoolean, accountBoundProxy: isBoolean, proxyPool: isBoolean, health: isNumber, failureCount: isNumber,
cooldownUntil: isOptional(isString), lastError: isOptional(isString),
});
const decodeEgressNodeList = createObjectDecoder<EgressNodeListDTO>("egress node list", {
items: isArrayOf(egressNodeValidator),
defaultUserAgents: hasShape({ grok_build: isString, grok_web: isString, grok_console: isString, grok_web_asset: isString }),
});
export function getSettings(): Promise<SettingsSnapshotDTO> {
return apiRequest("/api/admin/v1/settings", {}, decodeSettingsSnapshot);
}
export function updateSettings(revision: string, config: SettingsConfigDTO): Promise<SettingsSnapshotDTO> {
return apiRequest("/api/admin/v1/settings", { method: "PUT", body: { revision, config } }, decodeSettingsSnapshot);
}
export function listEgressNodes(input?: { sortBy?: string; sortOrder?: SortOrder }): Promise<EgressNodeListDTO> {
const query = new URLSearchParams();
if (input?.sortBy && input.sortOrder) {
query.set("sortBy", input.sortBy);
query.set("sortOrder", input.sortOrder);
}
const suffix = query.size > 0 ? `?${query}` : "";
return apiRequest(`/api/admin/v1/egress-nodes${suffix}`, {}, decodeEgressNodeList);
}
export function createEgressNode(input: EgressNodeInput): Promise<EgressNodeDTO> {
return apiRequest("/api/admin/v1/egress-nodes", { method: "POST", body: input }, decodeEgressNode);
}
export function updateEgressNode(id: string, input: EgressNodeInput): Promise<EgressNodeDTO> {
return apiRequest(`/api/admin/v1/egress-nodes/${id}`, { method: "PUT", body: input }, decodeEgressNode);
}
export function deleteEgressNode(id: string): Promise<{ deleted: boolean }> {
return apiRequest(`/api/admin/v1/egress-nodes/${id}`, { method: "DELETE" }, decodeBooleanResult<{ deleted: boolean }>("deleted"));
}
export function refreshEgressClearance(id: string): Promise<{ refreshed: boolean }> {
return apiRequest(`/api/admin/v1/egress-nodes/${id}/refresh-clearance`, { method: "POST" }, decodeBooleanResult<{ refreshed: boolean }>("refreshed"));
}