mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-01 06:01:13 +08:00
feat: transition to a fully frontend architecture by removing backend dependencies and updating configuration for local deployment
This commit is contained in:
@@ -1,294 +0,0 @@
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import { fetchPrompts, type Prompt, type PromptListResponse } from "@/services/api/prompts";
|
||||
import { fetchImageModels } from "@/services/api/image";
|
||||
|
||||
export type AdminPromptCategory = {
|
||||
category: string;
|
||||
name: string;
|
||||
description: string;
|
||||
file: string;
|
||||
githubUrl: string;
|
||||
remote: boolean;
|
||||
};
|
||||
|
||||
export type AdminUser = {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
role: "user" | "admin";
|
||||
credits: number;
|
||||
affCode: string;
|
||||
affCount: number;
|
||||
inviterId: string;
|
||||
linuxDoId: string;
|
||||
status: "active" | "ban";
|
||||
lastLoginAt: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type AdminUserListResponse = {
|
||||
items: AdminUser[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type AdminCreditLog = {
|
||||
id: string;
|
||||
userId: string;
|
||||
type: string;
|
||||
amount: number;
|
||||
balance: number;
|
||||
relatedId: string;
|
||||
remark: string;
|
||||
extra: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type AdminCreditLogListResponse = {
|
||||
items: AdminCreditLog[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type AdminUserQuery = {
|
||||
keyword?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
};
|
||||
|
||||
export async function fetchAdminUsers(token: string, query: AdminUserQuery = {}) {
|
||||
return { items: [], total: 0 };
|
||||
}
|
||||
|
||||
export async function saveAdminUser(token: string, user: Partial<AdminUser> & { password?: string }) {
|
||||
return { ...emptyAdminUser(), ...user, id: user.id || nanoid() };
|
||||
}
|
||||
|
||||
export async function adjustAdminUserCredits(token: string, id: string, credits: number) {
|
||||
return { ...emptyAdminUser(), id, credits };
|
||||
}
|
||||
|
||||
export async function deleteAdminUser(token: string, id: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function fetchAdminCreditLogs(token: string, query: AdminUserQuery = {}) {
|
||||
return { items: [], total: 0 };
|
||||
}
|
||||
|
||||
export async function saveAdminCreditLog(token: string, log: Partial<AdminCreditLog>) {
|
||||
return { id: log.id || nanoid(), userId: "", type: "", amount: 0, balance: 0, relatedId: "", remark: "", extra: "", createdAt: new Date().toISOString(), ...log };
|
||||
}
|
||||
|
||||
export async function deleteAdminCreditLog(token: string, id: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function fetchAdminPromptCategories(token: string) {
|
||||
return promptCategories;
|
||||
}
|
||||
|
||||
export async function syncAdminPromptCategory(token: string, category: string) {
|
||||
await fetchPrompts({ category, pageSize: 1 });
|
||||
return promptCategories;
|
||||
}
|
||||
|
||||
export type AdminPromptQuery = {
|
||||
keyword?: string;
|
||||
category?: string;
|
||||
tag?: string[];
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
};
|
||||
|
||||
export type AdminAsset = {
|
||||
id: string;
|
||||
title: string;
|
||||
type: "text" | "image" | "video";
|
||||
coverUrl: string;
|
||||
tags: string[];
|
||||
category: string;
|
||||
description: string;
|
||||
content: string;
|
||||
url: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type AdminAssetListResponse = {
|
||||
items: AdminAsset[];
|
||||
tags: string[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export async function fetchAdminPrompts(token: string, query: AdminPromptQuery = {}) {
|
||||
return fetchPrompts(query);
|
||||
}
|
||||
|
||||
export async function saveAdminPrompt(token: string, prompt: Partial<Prompt>) {
|
||||
const now = new Date().toISOString();
|
||||
return { id: prompt.id || nanoid(), title: "", coverUrl: "", prompt: "", tags: [], category: "", githubUrl: "", preview: "", createdAt: now, updatedAt: now, ...prompt };
|
||||
}
|
||||
|
||||
export async function deleteAdminPrompt(token: string, id: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function deleteAdminPrompts(token: string, ids: string[]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export type AdminAssetQuery = {
|
||||
keyword?: string;
|
||||
type?: string;
|
||||
tag?: string[];
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
};
|
||||
|
||||
export async function fetchAdminAssets(token: string, query: AdminAssetQuery = {}) {
|
||||
return { items: [], tags: [], total: 0 };
|
||||
}
|
||||
|
||||
export async function saveAdminAsset(token: string, asset: Partial<AdminAsset>) {
|
||||
return { id: asset.id || nanoid(), title: "", type: "text", coverUrl: "", tags: [], category: "", description: "", content: "", url: "", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), ...asset };
|
||||
}
|
||||
|
||||
export async function deleteAdminAsset(token: string, id: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export type AdminModelChannel = {
|
||||
protocol: "openai";
|
||||
name: string;
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
models: string[];
|
||||
weight: number;
|
||||
enabled: boolean;
|
||||
remark: string;
|
||||
};
|
||||
|
||||
export type AdminPublicModelChannelSettings = {
|
||||
availableModels: string[];
|
||||
modelCosts: AdminModelCost[];
|
||||
defaultModel: string;
|
||||
defaultImageModel: string;
|
||||
defaultVideoModel: string;
|
||||
defaultTextModel: string;
|
||||
systemPrompt: string;
|
||||
allowCustomChannel: boolean;
|
||||
};
|
||||
|
||||
export type AdminModelCost = {
|
||||
model: string;
|
||||
credits: number;
|
||||
};
|
||||
|
||||
export type AdminPublicSettings = {
|
||||
modelChannel: AdminPublicModelChannelSettings;
|
||||
auth: {
|
||||
allowRegister: boolean;
|
||||
linuxDo: {
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type AdminPrivateSettings = {
|
||||
channels: AdminModelChannel[];
|
||||
promptSync: {
|
||||
enabled: boolean;
|
||||
cron: string;
|
||||
};
|
||||
auth: {
|
||||
linuxDo: {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type AdminSettings = {
|
||||
public: AdminPublicSettings;
|
||||
private: AdminPrivateSettings;
|
||||
};
|
||||
|
||||
export async function fetchAdminSettings(token: string) {
|
||||
return defaultAdminSettings();
|
||||
}
|
||||
|
||||
export async function saveAdminSettings(token: string, settings: AdminSettings) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
export type AdminChannelActionRequest = {
|
||||
index?: number;
|
||||
channel: AdminModelChannel;
|
||||
model?: string;
|
||||
};
|
||||
|
||||
export async function fetchChannelModels(token: string, payload: AdminChannelActionRequest) {
|
||||
return fetchImageModels({ ...defaultAiConfig(), baseUrl: payload.channel.baseUrl, apiKey: payload.channel.apiKey || "local", model: payload.model || "", models: payload.channel.models });
|
||||
}
|
||||
|
||||
export async function testChannelModel(token: string, payload: AdminChannelActionRequest) {
|
||||
return "前台直连配置已保存,请在前台生成时验证模型可用性";
|
||||
}
|
||||
|
||||
const promptCategories: AdminPromptCategory[] = [
|
||||
{ category: "gpt-image-2-prompts", name: "GPT Image 2 Prompts", description: "", file: "", githubUrl: "https://github.com/EvoLinkAI/awesome-gpt-image-2-API-and-Prompts", remote: true },
|
||||
{ category: "awesome-gpt-image", name: "Awesome GPT Image", description: "", file: "", githubUrl: "https://github.com/ZeroLu/awesome-gpt-image", remote: true },
|
||||
{ category: "awesome-gpt4o-image-prompts", name: "Awesome GPT-4o Image Prompts", description: "", file: "", githubUrl: "https://github.com/ImgEdify/Awesome-GPT4o-Image-Prompts", remote: true },
|
||||
{ category: "youmind-gpt-image-2", name: "YouMind GPT Image 2", description: "", file: "", githubUrl: "https://github.com/YouMind-OpenLab/awesome-gpt-image-2", remote: true },
|
||||
{ category: "youmind-nano-banana-pro", name: "YouMind Nano Banana Pro", description: "", file: "", githubUrl: "https://github.com/YouMind-OpenLab/awesome-nano-banana-pro-prompts", remote: true },
|
||||
{ category: "davidwu-gpt-image2-prompts", name: "DavidWu GPT Image2 Prompts", description: "", file: "", githubUrl: "https://github.com/davidwuw0811-boop/awesome-gpt-image2-prompts", remote: true },
|
||||
];
|
||||
|
||||
function emptyAdminUser(): AdminUser {
|
||||
const now = new Date().toISOString();
|
||||
return { id: "", username: "", email: "", displayName: "", avatarUrl: "", role: "user", credits: 0, affCode: "", affCount: 0, inviterId: "", linuxDoId: "", status: "active", lastLoginAt: "", createdAt: now, updatedAt: now };
|
||||
}
|
||||
|
||||
function defaultAdminSettings(): AdminSettings {
|
||||
return {
|
||||
public: {
|
||||
modelChannel: { availableModels: [], modelCosts: [], defaultModel: "", defaultImageModel: "", defaultVideoModel: "", defaultTextModel: "", systemPrompt: "", allowCustomChannel: true },
|
||||
auth: { allowRegister: false, linuxDo: { enabled: false } },
|
||||
},
|
||||
private: { channels: [], promptSync: { enabled: false, cron: "" }, auth: { linuxDo: { clientId: "", clientSecret: "" } } },
|
||||
};
|
||||
}
|
||||
|
||||
function defaultAiConfig() {
|
||||
return {
|
||||
channelMode: "local" as const,
|
||||
baseUrl: "",
|
||||
apiKey: "",
|
||||
model: "",
|
||||
imageModel: "",
|
||||
videoModel: "",
|
||||
textModel: "",
|
||||
audioModel: "",
|
||||
audioVoice: "",
|
||||
audioFormat: "",
|
||||
audioSpeed: "",
|
||||
audioInstructions: "",
|
||||
videoSeconds: "",
|
||||
vquality: "",
|
||||
videoGenerateAudio: "",
|
||||
videoWatermark: "",
|
||||
systemPrompt: "",
|
||||
models: [],
|
||||
imageModels: [],
|
||||
videoModels: [],
|
||||
textModels: [],
|
||||
audioModels: [],
|
||||
quality: "",
|
||||
size: "",
|
||||
count: "",
|
||||
canvasImageCount: "",
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import axios from "axios";
|
||||
|
||||
import { audioMimeType, normalizeAudioFormatValue, normalizeAudioSpeedValue, normalizeAudioVoiceValue } from "@/lib/audio-generation";
|
||||
import { uploadMediaFile, type UploadedFile } from "@/services/file-storage";
|
||||
import { buildApiUrl, type AiConfig } from "@/stores/use-config-store";
|
||||
import { buildApiUrl, resolveModelRequestConfig, type AiConfig } from "@/stores/use-config-store";
|
||||
|
||||
function aiApiUrl(config: AiConfig, path: string) {
|
||||
return buildApiUrl(config.baseUrl, path);
|
||||
@@ -16,14 +16,15 @@ function aiHeaders(config: AiConfig) {
|
||||
}
|
||||
|
||||
export async function requestAudioGeneration(config: AiConfig, prompt: string): Promise<Blob> {
|
||||
const model = (config.model || config.audioModel).trim();
|
||||
assertAudioConfig(config, model);
|
||||
const requestConfig = resolveModelRequestConfig(config, config.model || config.audioModel);
|
||||
const model = requestConfig.model.trim();
|
||||
assertAudioConfig(requestConfig, model);
|
||||
const format = normalizeAudioFormatValue(config.audioFormat);
|
||||
const instructions = config.audioInstructions.trim();
|
||||
|
||||
try {
|
||||
const response = await axios.post<Blob>(
|
||||
aiApiUrl(config, "/audio/speech"),
|
||||
aiApiUrl(requestConfig, "/audio/speech"),
|
||||
{
|
||||
model,
|
||||
input: prompt,
|
||||
@@ -32,7 +33,7 @@ export async function requestAudioGeneration(config: AiConfig, prompt: string):
|
||||
speed: Number(normalizeAudioSpeedValue(config.audioSpeed)),
|
||||
...(instructions ? { instructions } : {}),
|
||||
},
|
||||
{ headers: aiHeaders(config), responseType: "blob" },
|
||||
{ headers: aiHeaders(requestConfig), responseType: "blob" },
|
||||
);
|
||||
await assertAudioBlob(response.data);
|
||||
return response.data.type.startsWith("audio/") ? response.data : new Blob([response.data], { type: audioMimeType(format) });
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
export const AUTH_TOKEN_KEY = "infinite-canvas-auth-token-v1";
|
||||
|
||||
export type UserRole = "guest" | "user" | "admin";
|
||||
|
||||
export type AuthUser = {
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
role: UserRole;
|
||||
credits: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type AuthSession = {
|
||||
token: string;
|
||||
user: AuthUser;
|
||||
};
|
||||
|
||||
export type AuthPayload = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export async function login(payload: AuthPayload) {
|
||||
const now = new Date().toISOString();
|
||||
return { token: "", user: { id: "local-user", username: payload.username || "local", displayName: payload.username || "本地用户", avatarUrl: "", role: "user" as const, credits: 0, createdAt: now, updatedAt: now } };
|
||||
}
|
||||
|
||||
export async function register(payload: AuthPayload) {
|
||||
return login(payload);
|
||||
}
|
||||
|
||||
export async function fetchCurrentUser(token?: string) {
|
||||
return { id: "local-user", username: "local", displayName: "本地用户", avatarUrl: "", role: "user" as const, credits: 0, createdAt: "", updatedAt: "" };
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { buildApiUrl, type AiConfig } from "@/stores/use-config-store";
|
||||
import { buildApiUrl, resolveModelRequestConfig, type AiConfig, type ModelChannel } from "@/stores/use-config-store";
|
||||
import { nanoid } from "nanoid";
|
||||
import { dataUrlToFile } from "@/lib/image-utils";
|
||||
import { buildImageReferencePromptText } from "@/lib/image-reference-prompt";
|
||||
@@ -184,15 +184,16 @@ function withSystemMessage(config: AiConfig, messages: ChatCompletionMessage[])
|
||||
}
|
||||
|
||||
export async function requestGeneration(config: AiConfig, prompt: string) {
|
||||
const requestConfig = resolveModelRequestConfig(config, config.model || config.imageModel);
|
||||
const n = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1)));
|
||||
const quality = normalizeQuality(config.quality);
|
||||
const requestSize = resolveRequestSize(quality, config.size);
|
||||
try {
|
||||
const response = await axios.post<ImageApiResponse>(
|
||||
aiApiUrl(config, "/images/generations"),
|
||||
aiApiUrl(requestConfig, "/images/generations"),
|
||||
{
|
||||
model: config.model,
|
||||
prompt: withSystemPrompt(config, prompt),
|
||||
model: requestConfig.model,
|
||||
prompt: withSystemPrompt(requestConfig, prompt),
|
||||
n,
|
||||
...(quality ? { quality } : {}),
|
||||
...(requestSize ? { size: requestSize } : {}),
|
||||
@@ -200,7 +201,7 @@ export async function requestGeneration(config: AiConfig, prompt: string) {
|
||||
output_format: IMAGE_OUTPUT_FORMAT,
|
||||
},
|
||||
{
|
||||
headers: aiHeaders(config, "application/json"),
|
||||
headers: aiHeaders(requestConfig, "application/json"),
|
||||
},
|
||||
);
|
||||
const images = parseImagePayload(response.data);
|
||||
@@ -211,13 +212,14 @@ export async function requestGeneration(config: AiConfig, prompt: string) {
|
||||
}
|
||||
|
||||
export async function requestEdit(config: AiConfig, prompt: string, references: ReferenceImage[], mask?: ReferenceImage) {
|
||||
const requestConfig = resolveModelRequestConfig(config, config.model || config.imageModel);
|
||||
const n = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(config.count)) || 1)));
|
||||
const quality = normalizeQuality(config.quality);
|
||||
const requestSize = resolveRequestSize(quality, config.size);
|
||||
const requestPrompt = buildImageReferencePromptText(prompt, references);
|
||||
const formData = new FormData();
|
||||
formData.set("model", config.model);
|
||||
formData.set("prompt", withSystemPrompt(config, requestPrompt));
|
||||
formData.set("model", requestConfig.model);
|
||||
formData.set("prompt", withSystemPrompt(requestConfig, requestPrompt));
|
||||
formData.set("n", String(n));
|
||||
formData.set("response_format", "b64_json");
|
||||
formData.set("output_format", IMAGE_OUTPUT_FORMAT);
|
||||
@@ -232,7 +234,7 @@ export async function requestEdit(config: AiConfig, prompt: string, references:
|
||||
if (mask) formData.set("mask", dataUrlToFile(mask));
|
||||
|
||||
try {
|
||||
const response = await axios.post<ImageApiResponse>(aiApiUrl(config, "/images/edits"), formData, { headers: aiHeaders(config) });
|
||||
const response = await axios.post<ImageApiResponse>(aiApiUrl(requestConfig, "/images/edits"), formData, { headers: aiHeaders(requestConfig) });
|
||||
const images = parseImagePayload(response.data);
|
||||
return images;
|
||||
} catch (error) {
|
||||
@@ -241,21 +243,22 @@ export async function requestEdit(config: AiConfig, prompt: string, references:
|
||||
}
|
||||
|
||||
export async function requestImageQuestion(config: AiConfig, messages: ChatCompletionMessage[], onDelta: (text: string) => void) {
|
||||
const requestConfig = resolveModelRequestConfig(config, config.model || config.textModel);
|
||||
let buffer = "";
|
||||
let answer = "";
|
||||
let processedLength = 0;
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
aiApiUrl(config, "/chat/completions"),
|
||||
aiApiUrl(requestConfig, "/chat/completions"),
|
||||
{
|
||||
model: config.model,
|
||||
messages: withSystemMessage(config, messages),
|
||||
model: requestConfig.model,
|
||||
messages: withSystemMessage(requestConfig, messages),
|
||||
stream: true,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
...aiHeaders(config, "application/json"),
|
||||
...aiHeaders(requestConfig, "application/json"),
|
||||
} as Record<string, string>,
|
||||
responseType: "text",
|
||||
onDownloadProgress: (event) => {
|
||||
@@ -301,7 +304,7 @@ export async function requestImageQuestion(config: AiConfig, messages: ChatCompl
|
||||
return answer || "没有返回内容";
|
||||
}
|
||||
|
||||
export async function fetchImageModels(config: AiConfig) {
|
||||
export async function fetchImageModels(config: Pick<AiConfig, "baseUrl" | "apiKey">) {
|
||||
try {
|
||||
const response = await axios.get<{ data?: Array<{ id?: string }>; error?: { message?: string } }>(buildApiUrl(config.baseUrl, "/models"), {
|
||||
headers: {
|
||||
@@ -316,3 +319,7 @@ export async function fetchImageModels(config: AiConfig) {
|
||||
throw new Error(readAxiosError(error, "读取模型失败"));
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchChannelModels(channel: ModelChannel) {
|
||||
return fetchImageModels({ baseUrl: channel.baseUrl, apiKey: channel.apiKey });
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { dataUrlToFile } from "@/lib/image-utils";
|
||||
import { getMediaBlob, uploadMediaFile, type UploadedFile } from "@/services/file-storage";
|
||||
import { imageToDataUrl } from "@/services/image-storage";
|
||||
import { boolConfig, buildSeedancePromptText, isSeedanceVideoConfig, normalizeSeedanceDuration, normalizeSeedanceRatio, normalizeSeedanceResolution, seedanceVideoReferenceError, SEEDANCE_REFERENCE_LIMITS } from "@/lib/seedance-video";
|
||||
import { buildApiUrl, type AiConfig } from "@/stores/use-config-store";
|
||||
import { buildApiUrl, modelOptionName, resolveModelRequestConfig, type AiConfig } from "@/stores/use-config-store";
|
||||
import type { ReferenceImage } from "@/types/image";
|
||||
import type { ReferenceAudio, ReferenceVideo } from "@/types/media";
|
||||
|
||||
@@ -47,20 +47,22 @@ export async function requestVideoGeneration(config: AiConfig, prompt: string, r
|
||||
}
|
||||
|
||||
export async function createVideoGenerationTask(config: AiConfig, prompt: string, references: ReferenceImage[] = [], videoReferences: ReferenceVideo[] = [], audioReferences: ReferenceAudio[] = []): Promise<VideoGenerationTask> {
|
||||
const model = (config.model || config.videoModel).trim();
|
||||
assertVideoConfig(config, model);
|
||||
if (isSeedanceVideoConfig({ ...config, model })) {
|
||||
return createSeedanceTask(config, model, prompt, references, videoReferences, audioReferences);
|
||||
const selectedModel = (config.model || config.videoModel).trim();
|
||||
const requestConfig = resolveModelRequestConfig(config, selectedModel);
|
||||
assertVideoConfig(requestConfig, requestConfig.model);
|
||||
if (isSeedanceVideoConfig(requestConfig)) {
|
||||
return createSeedanceTask(requestConfig, selectedModel, prompt, references, videoReferences, audioReferences);
|
||||
}
|
||||
if (videoReferences.length || audioReferences.length) {
|
||||
throw new Error("当前视频接口不支持参考视频或参考音频,请切换到 Seedance 2.0 / 火山 Agent Plan 模型,或移除参考素材");
|
||||
}
|
||||
return createOpenAIVideoTask(config, model, prompt, references);
|
||||
return createOpenAIVideoTask(requestConfig, selectedModel, prompt, references);
|
||||
}
|
||||
|
||||
export async function pollVideoGenerationTask(config: AiConfig, task: VideoGenerationTask): Promise<VideoGenerationTaskState> {
|
||||
assertVideoConfig(config, task.model);
|
||||
return task.provider === "seedance" ? pollSeedanceTask(config, task) : pollOpenAIVideoTask(config, task);
|
||||
const requestConfig = resolveModelRequestConfig(config, task.model);
|
||||
assertVideoConfig(requestConfig, requestConfig.model);
|
||||
return task.provider === "seedance" ? pollSeedanceTask(requestConfig, task) : pollOpenAIVideoTask(requestConfig, task);
|
||||
}
|
||||
|
||||
export async function storeGeneratedVideo(result: VideoGenerationResult): Promise<UploadedFile> {
|
||||
@@ -71,7 +73,7 @@ export async function storeGeneratedVideo(result: VideoGenerationResult): Promis
|
||||
|
||||
async function createOpenAIVideoTask(config: AiConfig, model: string, prompt: string, references: ReferenceImage[]): Promise<VideoGenerationTask> {
|
||||
const body = new FormData();
|
||||
body.append("model", model);
|
||||
body.append("model", modelOptionName(model));
|
||||
body.append("prompt", prompt);
|
||||
body.append("seconds", normalizeVideoSeconds(config.videoSeconds));
|
||||
if (normalizeVideoSize(config.size)) body.append("size", normalizeVideoSize(config.size)!);
|
||||
@@ -112,10 +114,10 @@ async function createSeedanceTask(config: AiConfig, model: string, prompt: strin
|
||||
const content = await buildSeedanceContent(config, prompt, references, videoReferences, audioReferences);
|
||||
if (!content.length) throw new Error("请输入视频提示词,或连接参考图片/视频/音频");
|
||||
const payload = {
|
||||
model,
|
||||
model: modelOptionName(model),
|
||||
content,
|
||||
ratio: normalizeSeedanceRatio(config.size),
|
||||
resolution: normalizeSeedanceResolution(config.vquality, model),
|
||||
resolution: normalizeSeedanceResolution(config.vquality, modelOptionName(model)),
|
||||
duration: normalizeSeedanceDuration(config.videoSeconds),
|
||||
generate_audio: boolConfig(config.videoGenerateAudio, true),
|
||||
watermark: boolConfig(config.videoWatermark, false),
|
||||
|
||||
Reference in New Issue
Block a user