356 lines
11 KiB
TypeScript
356 lines
11 KiB
TypeScript
export const DEFAULT_BFL_MODEL = "flux-2-klein-4b";
|
|
export const DEFAULT_BFL_BASE_URL = "https://api.bfl.ai";
|
|
export const DEFAULT_REPLICATE_MODEL = "black-forest-labs/flux-2-klein-4b";
|
|
export const DEFAULT_REPLICATE_BASE_URL = "https://api.replicate.com/v1";
|
|
export const DEFAULT_FAL_BASE_URL = "https://fal.run";
|
|
export const DEFAULT_FAL_REALTIME_MODEL = "";
|
|
|
|
export type RealtimeProviderType = "bfl" | "replicate" | "fal";
|
|
|
|
export type RealtimeCanvasConfig = {
|
|
enabled?: boolean;
|
|
providerType?: RealtimeProviderType;
|
|
bflApiKey?: string;
|
|
bflModel?: string;
|
|
bflBaseUrl?: string;
|
|
runwareApiKey?: string;
|
|
runwareModel?: string;
|
|
falApiKey?: string;
|
|
falModel?: string;
|
|
falBaseUrl?: string;
|
|
apiKey?: string;
|
|
replicateApiToken?: string;
|
|
replicateModel?: string;
|
|
fallbackEnabled?: boolean;
|
|
fallbackProviderType?: RealtimeProviderType;
|
|
fallbackBflApiKey?: string;
|
|
fallbackBflModel?: string;
|
|
fallbackBflBaseUrl?: string;
|
|
fallbackRunwareApiKey?: string;
|
|
fallbackRunwareModel?: string;
|
|
fallbackFalApiKey?: string;
|
|
fallbackFalModel?: string;
|
|
fallbackFalBaseUrl?: string;
|
|
fallbackApiKey?: string;
|
|
fallbackReplicateApiToken?: string;
|
|
fallbackReplicateModel?: string;
|
|
negativePrompt?: string;
|
|
renderMode?: "strict-structure" | "balanced" | "stylize";
|
|
strictSketch?: boolean;
|
|
throttleInterval?: number;
|
|
maxBuffering?: number;
|
|
maxInputResolution?: number;
|
|
jpegQuality?: number;
|
|
previewMegapixels?: number;
|
|
detailMegapixels?: number;
|
|
outputJpegQuality?: number;
|
|
defaults?: {
|
|
steps?: number;
|
|
cfgScale?: number;
|
|
strength?: number;
|
|
scheduleMu?: number;
|
|
feedback?: number;
|
|
imageSize?: "square" | "square_hd";
|
|
};
|
|
};
|
|
|
|
export type RealtimeProviderConfig = {
|
|
type: RealtimeProviderType;
|
|
apiKey: string;
|
|
baseUrl: string;
|
|
model: string;
|
|
source: "primary" | "fallback";
|
|
label: string;
|
|
};
|
|
|
|
function trimString(value: unknown) {
|
|
return String(value || "").trim();
|
|
}
|
|
|
|
function normalizeProviderType(raw: unknown): RealtimeProviderType {
|
|
if (raw === "fal") return "fal";
|
|
return raw === "replicate" ? "replicate" : "bfl";
|
|
}
|
|
|
|
export function normalizeBaseUrl(raw: string | undefined) {
|
|
const next = trimString(raw || DEFAULT_BFL_BASE_URL);
|
|
return (next || DEFAULT_BFL_BASE_URL).replace(/\/+$/, "");
|
|
}
|
|
|
|
function hasFallbackIntent(config: RealtimeCanvasConfig) {
|
|
if (config.fallbackEnabled === true) return true;
|
|
if (config.fallbackEnabled === false) return false;
|
|
|
|
return Boolean(
|
|
trimString(
|
|
config.fallbackBflApiKey ||
|
|
config.fallbackRunwareApiKey ||
|
|
config.fallbackFalApiKey ||
|
|
config.fallbackApiKey ||
|
|
config.fallbackBflBaseUrl ||
|
|
config.fallbackBflModel ||
|
|
config.fallbackRunwareModel ||
|
|
config.fallbackFalModel ||
|
|
config.fallbackReplicateApiToken ||
|
|
config.fallbackReplicateModel
|
|
)
|
|
);
|
|
}
|
|
|
|
export function resolvePrimaryRealtimeProvider(
|
|
config: RealtimeCanvasConfig,
|
|
env: NodeJS.ProcessEnv = process.env
|
|
): RealtimeProviderConfig | null {
|
|
const type = normalizeProviderType(config.providerType);
|
|
|
|
if (type === "replicate") {
|
|
const apiKey = trimString(config.replicateApiToken || env.REPLICATE_API_TOKEN);
|
|
if (!apiKey) return null;
|
|
|
|
return {
|
|
type,
|
|
apiKey,
|
|
model: trimString(config.replicateModel) || DEFAULT_REPLICATE_MODEL,
|
|
baseUrl: DEFAULT_REPLICATE_BASE_URL,
|
|
source: "primary",
|
|
label: "主实时服务",
|
|
};
|
|
}
|
|
|
|
if (type === "fal") {
|
|
const apiKey = trimString(config.falApiKey || config.apiKey || env.FAL_KEY || env.FAL_API_KEY);
|
|
const model = trimString(config.falModel || env.FAL_REALTIME_MODEL || env.FAL_MODEL);
|
|
if (!apiKey || !model) return null;
|
|
|
|
return {
|
|
type,
|
|
apiKey,
|
|
model,
|
|
baseUrl: trimString(config.falBaseUrl || env.FAL_BASE_URL || DEFAULT_FAL_BASE_URL),
|
|
source: "primary",
|
|
label: "主实时服务",
|
|
};
|
|
}
|
|
|
|
const apiKey = trimString(
|
|
config.bflApiKey ||
|
|
config.runwareApiKey ||
|
|
config.falApiKey ||
|
|
config.apiKey ||
|
|
env.BFL_API_KEY ||
|
|
env.BFL_KEY
|
|
);
|
|
if (!apiKey) return null;
|
|
|
|
const model =
|
|
trimString(config.bflModel || config.runwareModel || env.BFL_MODEL || env.RUNWARE_MODEL) ||
|
|
DEFAULT_BFL_MODEL;
|
|
const baseUrl = normalizeBaseUrl(config.bflBaseUrl || env.BFL_BASE_URL);
|
|
|
|
return {
|
|
type,
|
|
apiKey,
|
|
model,
|
|
baseUrl,
|
|
source: "primary",
|
|
label: "主实时服务",
|
|
};
|
|
}
|
|
|
|
export function resolveFallbackRealtimeProvider(
|
|
config: RealtimeCanvasConfig
|
|
): RealtimeProviderConfig | null {
|
|
if (!hasFallbackIntent(config)) return null;
|
|
|
|
const type = normalizeProviderType(config.fallbackProviderType);
|
|
|
|
if (type === "replicate") {
|
|
const apiKey = trimString(config.fallbackReplicateApiToken);
|
|
if (!apiKey) return null;
|
|
|
|
return {
|
|
type,
|
|
apiKey,
|
|
model: trimString(config.fallbackReplicateModel) || DEFAULT_REPLICATE_MODEL,
|
|
baseUrl: DEFAULT_REPLICATE_BASE_URL,
|
|
source: "fallback",
|
|
label: "备用实时服务",
|
|
};
|
|
}
|
|
|
|
if (type === "fal") {
|
|
const apiKey = trimString(config.fallbackFalApiKey || config.fallbackApiKey);
|
|
const model = trimString(config.fallbackFalModel);
|
|
if (!apiKey || !model) return null;
|
|
|
|
return {
|
|
type,
|
|
apiKey,
|
|
model,
|
|
baseUrl: trimString(config.fallbackFalBaseUrl || DEFAULT_FAL_BASE_URL),
|
|
source: "fallback",
|
|
label: "备用实时服务",
|
|
};
|
|
}
|
|
|
|
const apiKey = trimString(
|
|
config.fallbackBflApiKey ||
|
|
config.fallbackRunwareApiKey ||
|
|
config.fallbackFalApiKey ||
|
|
config.fallbackApiKey
|
|
);
|
|
if (!apiKey) return null;
|
|
|
|
const model =
|
|
trimString(config.fallbackBflModel || config.fallbackRunwareModel) || DEFAULT_BFL_MODEL;
|
|
const baseUrl = normalizeBaseUrl(config.fallbackBflBaseUrl || DEFAULT_BFL_BASE_URL);
|
|
|
|
return {
|
|
type,
|
|
apiKey,
|
|
model,
|
|
baseUrl,
|
|
source: "fallback",
|
|
label: "备用实时服务",
|
|
};
|
|
}
|
|
|
|
export function resolveRealtimeProviders(
|
|
config: RealtimeCanvasConfig,
|
|
env: NodeJS.ProcessEnv = process.env
|
|
) {
|
|
const providers: RealtimeProviderConfig[] = [];
|
|
const primary = resolvePrimaryRealtimeProvider(config, env);
|
|
const fallback = resolveFallbackRealtimeProvider(config);
|
|
|
|
if (primary) {
|
|
providers.push(primary);
|
|
}
|
|
|
|
if (
|
|
fallback &&
|
|
!providers.some(
|
|
(provider) =>
|
|
provider.apiKey === fallback.apiKey &&
|
|
provider.baseUrl === fallback.baseUrl &&
|
|
provider.model === fallback.model
|
|
)
|
|
) {
|
|
providers.push(fallback);
|
|
}
|
|
|
|
return providers;
|
|
}
|
|
|
|
export function normalizeRealtimeCanvasConfigForStorage(
|
|
value: Record<string, unknown>,
|
|
env: NodeJS.ProcessEnv = process.env
|
|
): RealtimeCanvasConfig {
|
|
const renderMode =
|
|
value.renderMode === "strict-structure" ||
|
|
value.renderMode === "stylize" ||
|
|
value.renderMode === "balanced"
|
|
? value.renderMode
|
|
: "balanced";
|
|
|
|
const primary = resolvePrimaryRealtimeProvider(value as RealtimeCanvasConfig, env);
|
|
const fallback = resolveFallbackRealtimeProvider(value as RealtimeCanvasConfig);
|
|
|
|
return {
|
|
...value,
|
|
providerType: primary?.type || normalizeProviderType(value.providerType),
|
|
bflApiKey:
|
|
primary?.type === "bfl"
|
|
? primary.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).bflApiKey),
|
|
bflModel:
|
|
primary?.type === "bfl"
|
|
? primary.model
|
|
: trimString((value as RealtimeCanvasConfig).bflModel) || DEFAULT_BFL_MODEL,
|
|
bflBaseUrl:
|
|
primary?.type === "bfl"
|
|
? primary.baseUrl
|
|
: normalizeBaseUrl((value as RealtimeCanvasConfig).bflBaseUrl || env.BFL_BASE_URL),
|
|
runwareApiKey:
|
|
primary?.type === "bfl"
|
|
? primary.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).runwareApiKey),
|
|
falApiKey:
|
|
primary?.type === "bfl" || primary?.type === "fal"
|
|
? primary.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).falApiKey),
|
|
falModel:
|
|
primary?.type === "fal"
|
|
? primary.model
|
|
: trimString((value as RealtimeCanvasConfig).falModel) || DEFAULT_FAL_REALTIME_MODEL,
|
|
falBaseUrl:
|
|
primary?.type === "fal"
|
|
? primary.baseUrl
|
|
: trimString((value as RealtimeCanvasConfig).falBaseUrl || env.FAL_BASE_URL || DEFAULT_FAL_BASE_URL),
|
|
apiKey:
|
|
primary?.type === "bfl" || primary?.type === "fal"
|
|
? primary.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).apiKey),
|
|
runwareModel:
|
|
primary?.type === "bfl"
|
|
? primary.model
|
|
: trimString((value as RealtimeCanvasConfig).runwareModel) || DEFAULT_BFL_MODEL,
|
|
replicateApiToken:
|
|
primary?.type === "replicate"
|
|
? primary.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).replicateApiToken),
|
|
replicateModel:
|
|
primary?.type === "replicate"
|
|
? primary.model
|
|
: trimString((value as RealtimeCanvasConfig).replicateModel) || DEFAULT_REPLICATE_MODEL,
|
|
fallbackEnabled: Boolean(fallback),
|
|
fallbackProviderType: fallback?.type || normalizeProviderType(value.fallbackProviderType),
|
|
fallbackBflApiKey:
|
|
fallback?.type === "bfl"
|
|
? fallback.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).fallbackBflApiKey),
|
|
fallbackBflModel:
|
|
fallback?.type === "bfl"
|
|
? fallback.model
|
|
: trimString((value as RealtimeCanvasConfig).fallbackBflModel) || DEFAULT_BFL_MODEL,
|
|
fallbackBflBaseUrl:
|
|
fallback?.type === "bfl"
|
|
? fallback.baseUrl
|
|
: normalizeBaseUrl((value as RealtimeCanvasConfig).fallbackBflBaseUrl || DEFAULT_BFL_BASE_URL),
|
|
fallbackRunwareApiKey:
|
|
fallback?.type === "bfl"
|
|
? fallback.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).fallbackRunwareApiKey),
|
|
fallbackFalApiKey:
|
|
fallback?.type === "bfl" || fallback?.type === "fal"
|
|
? fallback.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).fallbackFalApiKey),
|
|
fallbackFalModel:
|
|
fallback?.type === "fal"
|
|
? fallback.model
|
|
: trimString((value as RealtimeCanvasConfig).fallbackFalModel) || DEFAULT_FAL_REALTIME_MODEL,
|
|
fallbackFalBaseUrl:
|
|
fallback?.type === "fal"
|
|
? fallback.baseUrl
|
|
: trimString((value as RealtimeCanvasConfig).fallbackFalBaseUrl || DEFAULT_FAL_BASE_URL),
|
|
fallbackApiKey:
|
|
fallback?.type === "bfl" || fallback?.type === "fal"
|
|
? fallback.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).fallbackApiKey),
|
|
fallbackRunwareModel:
|
|
fallback?.type === "bfl"
|
|
? fallback.model
|
|
: trimString((value as RealtimeCanvasConfig).fallbackRunwareModel) || DEFAULT_BFL_MODEL,
|
|
fallbackReplicateApiToken:
|
|
fallback?.type === "replicate"
|
|
? fallback.apiKey
|
|
: trimString((value as RealtimeCanvasConfig).fallbackReplicateApiToken),
|
|
fallbackReplicateModel:
|
|
fallback?.type === "replicate"
|
|
? fallback.model
|
|
: trimString((value as RealtimeCanvasConfig).fallbackReplicateModel) ||
|
|
DEFAULT_REPLICATE_MODEL,
|
|
renderMode,
|
|
};
|
|
}
|