Add Temp file
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m52s

This commit is contained in:
zyc 2026-05-28 11:09:55 +08:00
parent 88ea709bbb
commit a7b61c05f5
4 changed files with 917 additions and 759 deletions

158
.impeccable.md Normal file
View File

@ -0,0 +1,158 @@
# ToonFlow Design Context
## Design Context
### Users
ToonFlow serves creators and production teams turning novels, scripts, and source material into short-form video or drama assets. Primary users include AI video operators, short-drama producers, novel adaptation teams, prompt engineers, and internal content teams that need to move from planning to script, storyboard, assets, video generation, review, and export inside one workspace.
Their working context is production-heavy rather than casual browsing: users compare long text, inspect generated assets, operate model/provider settings, wait for asynchronous generation, revise prompts, and coordinate many related objects across projects. They need speed, traceability, confidence, and low cognitive friction more than decorative spectacle.
Core jobs to support:
- Create and manage short-drama projects from novels or source scripts.
- Extract chapter events and turn them into story skeletons, adaptation strategies, and structured scripts.
- Use ScriptAgent and ProductionAgent workflows while keeping generated decisions visible and editable.
- Organize characters, scenes, props, storyboard panels, generated images, video clips, and timeline-like tracks.
- Configure third-party text, image, and video model vendors safely.
- Review generated output, correct errors, regenerate selectively, and preserve production continuity across sessions.
### Brand Personality
Working personality: capable, cinematic, production-grade.
ToonFlow should feel like an AI production control room for short-drama creation: efficient enough for repeated daily work, confident enough for professional teams, and cinematic enough to remind users that the end product is video storytelling. The interface voice should be direct, calm, and operational. It should reduce uncertainty during long AI tasks through visible progress, clear next actions, and reversible controls.
The product should not feel like a generic AI chat toy, a marketing landing page, or a decorative demo. It should feel like a serious creative workstation with enough visual warmth to make long production sessions comfortable.
### Aesthetic Direction
Recommended direction: cinematic utilitarian workstation.
Use a dense but breathable desktop-first layout: persistent navigation, clear work regions, compact controls, object previews, status chips, task cards, editable panels, and canvas/timeline surfaces. The current UI already suggests a minimal black-and-white TDesign/Vue tool surface with rounded panels, left rail navigation, cards, tabs, Monaco editor usage, Vue Flow/infinite-canvas interactions, and production screenshots. The redesign should keep that operational clarity but make hierarchy, spacing, states, and visual rhythm more deliberate.
Signature move: storyboard-first production surfaces. When possible, make the core object visible: a project card should hint at story/style/progress; a storyboard item should carry thumbnail, duration, track, generation state, and dependencies; Agent output should show decision provenance and next action rather than plain chat blocks. The UI should help users see the production chain from novel to script to storyboard to video.
Visual guidance:
- Use the provided AirFlow Studio references as the primary color mood: deep blue-black workspace, soft indigo/violet depth, restrained cyan-teal accents, luminous but controlled data highlights, and cool off-white typography.
- Prefer dark/cinematic workspaces for the main product shell and production views, with enough contrast and surface separation for long editing sessions. Use light surfaces only when dense text editing or document review clearly benefits from them.
- Use cool-tinted neutrals instead of pure gray, pure black, or pure white.
- Use one primary action per surface; secondary actions should be icon buttons or quiet text buttons.
- Keep information dense but organized. Avoid oversized hero sections, decorative cards, nested cards, and generic SaaS marketing layouts.
- Use real thumbnails, generated assets, story snippets, status, and production metrics as visual material. Do not rely on abstract gradient decoration.
- Use motion as feedback: generation started, progress updated, asset completed, error needs intervention. Avoid bounce/elastic motion.
### Design Principles
1. Production clarity over decoration.
Every screen should answer: what project am I in, what stage is this, what changed, what is blocked, and what can I do next?
2. Preserve the creative chain.
Novel events, script decisions, storyboard rows, assets, prompts, and generated videos should feel connected. Users should be able to trace output back to its source and revise without losing context.
3. Dense, calm, repeatable work surfaces.
ToonFlow is used for long-form workflows with many objects. Prefer compact tables, split panes, drawers, tabs, inspector panels, and canvases over marketing-like sections or empty white space.
4. Human-in-control AI.
Agent actions need visible progress, explicit confirmations for destructive changes, undo or rollback where practical, and readable explanations for generated decisions. Never hide model/provider failures behind vague errors.
5. Media-first proof.
For video/image production, thumbnails, previews, track grouping, duration, generation state, and quality review are first-class UI elements. Text-only representations are fallback, not the primary experience.
6. Accessible by default.
Target WCAG AA contrast, visible focus states, keyboard-reachable controls, 44px touch targets where mobile/tablet is supported, and `prefers-reduced-motion` handling for animations.
7. Avoid AI template fingerprints.
Avoid purple-blue gradients, dark neon AI dashboards, glassmorphism everywhere, metric-card hero patterns, nested cards, generic rounded-icon feature blocks, gradient text, and monospace typography as a lazy "technical" signal.
## Product Surface Inventory
Known product areas from the current repository and bundled screenshots:
- Project dashboard: project cards, creation/edit/delete actions, project metadata.
- Script workflow: source novel, chapter events, story skeleton, adaptation strategy, generated scripts.
- Production workflow: infinite canvas / Vue Flow-style workspace, storyboard panels, derived assets, storyboard images, video tracks, preview and export flow.
- Agent chat/work panels: ScriptAgent and ProductionAgent decision, execution, supervision streams.
- Asset management: characters, scenes, props, images, audio, video clips, uploads, batch generation.
- Image editing flow: node-style image flow editing and image regeneration.
- Settings: model selection, model map prompts, vendor configuration, programmable vendor TypeScript code, skill management, memory management, login/user settings, database import/export.
- Deployment/runtime: web build embedded in `data/web`, Electron desktop shell, Express/Socket.IO backend, SQLite local data, local `data/oss` assets.
## Technical UI Context
- App shell: Electron desktop client plus web build served from `data/web`.
- Bundled frontend signals: Vue 3.5, TDesign 1.18, Vue Flow, Monaco editor, Pinia, Vue Router, Socket.IO, Axios, Day.js.
- Backend: Node/TypeScript, Express, Socket.IO, SQLite/Knex, AI SDK providers, ONNX embedding model for memory.
- The frontend source is not in this repository; this repo includes built assets. For substantial UI rewrites, use the ToonFlow web source repository or migrate/reconstruct frontend source before editing.
## UI Quality Bar
The target quality is flagship internal production software, not MVP. Screens should be fast to scan, robust in edge cases, and polished enough that a production operator can trust the tool during multi-step generation runs.
Required states for major workflows:
- Empty: explain what to create/import next, with one clear primary action.
- Loading/generating: show task name, stage, elapsed/progress where available, and cancellation/continue affordances when safe.
- Partial success: show what completed, what failed, and what can be retried.
- Error: identify provider/model/action, show actionable recovery, preserve user input.
- Success: confirm result and expose the next useful action.
## Color Direction From References
The color system should reference the two AirFlow Studio images provided by the product owner:
- Background mood: near-black with a blue/green undertone, not flat black.
- Depth color: deep navy and muted indigo for app shell, panels, charts, and canvas surroundings.
- Primary accent: cyan-teal for selected states, active labels, brand marks, and the most important progress signals.
- Secondary accent: soft violet/periwinkle for charts, AI activity, generated-state glow, and subtle atmosphere.
- Typography: cool off-white for primary text, lavender-gray for secondary text, muted blue-gray for low-emphasis labels.
- Surface style: translucent-looking dark panels are acceptable, but avoid heavy glassmorphism. Panels should remain readable, bounded, and utilitarian.
Suggested starting tokens:
- `--bg-app`: `#050813`
- `--bg-shell`: `#090B18`
- `--surface-1`: `#111525`
- `--surface-2`: `#1A1D31`
- `--surface-3`: `#24263A`
- `--border-soft`: `#31354B`
- `--text-primary`: `#F1F2FF`
- `--text-secondary`: `#B9BCD2`
- `--text-muted`: `#7E849D`
- `--accent-teal`: `#65D9CB`
- `--accent-teal-strong`: `#43C7BD`
- `--accent-violet`: `#7B6CFF`
- `--accent-lavender`: `#B9A7FF`
- `--accent-warning`: `#FFB547`
- `--accent-danger`: `#FF6363`
- `--accent-success`: `#2BD671`
Use these as direction tokens, then convert to OKLCH during implementation so hover, active, border, and disabled states can be generated perceptually rather than by arbitrary hex tweaks.
## Visual System Starting Point
This is a starting direction, not a locked brand system:
- Palette: dark cinematic blue-black shell, muted indigo panels, cyan-teal primary accent, violet/periwinkle data and AI-activity highlights, semantic status colors for success/warning/error/info.
- Typography: clear Chinese-first sans-serif stack; consider a more distinctive UI font only if it renders Chinese and Latin well without slowing the desktop app. Do not use monospace globally; reserve it for code/config editors.
- Shape: current 8-12px radii are acceptable for panels and cards. Reference images use softly rounded dark cards; keep tool buttons stable and compact.
- Spacing: 4px base scale; dense screens can use 8/12/16px rhythms, major layout gaps 24/32px.
- Motion: 100-150ms for control feedback, 200-300ms for panel state changes, 300-500ms for larger canvas/task transitions, only transform/opacity where possible.
## Anti-References
- Marketing landing page hero layouts for the main app.
- One-note dark neon "AI dashboard" styling. The reference palette is dark and cinematic, but it must stay restrained and production-focused.
- Generic purple/blue gradient branding as the dominant visual language. Violet can be used as atmosphere or chart/data accent, while cyan-teal should carry the clearer product identity.
- Card-inside-card structures for dense settings or production panels.
- Text-only Agent transcripts when structured task/progress/state UI would be clearer.
- Decorative media placeholders when real generated images or project objects are available.
## Open Questions To Confirm
- Target users: internal production team only, public creator community, or both?
- Brand tone: should AirLabs/ToonFlow feel more enterprise, creator-friendly, cinematic, or experimental?
- Theme: should the redesign stay mainly light, add full dark mode, or use hybrid light workspace with dark media review?
- References: are there products whose workflow feel you like, such as Runway, CapCut desktop, Figma, Linear, Notion, ComfyUI, or professional NLE tools?
- Constraints: are there existing brand colors, logo assets, or AirLabs visual rules that must be preserved?

848
data/vendor/grsai.ts vendored
View File

@ -1,424 +1,424 @@
/** /**
* Toonflow AI供应商模板 * Toonflow AI供应商模板
* @version 2.0 * @version 2.0
*/ */
// ============================================================ // ============================================================
// 类型定义 // 类型定义
// ============================================================ // ============================================================
type VideoMode = type VideoMode =
| "singleImage" //单图参考 | "singleImage" //单图参考
| "startEndRequired" //首尾帧(两张都得有) | "startEndRequired" //首尾帧(两张都得有)
| "endFrameOptional" //首尾帧(尾帧可选) | "endFrameOptional" //首尾帧(尾帧可选)
| "startFrameOptional" //首尾帧(首帧可选) | "startFrameOptional" //首尾帧(首帧可选)
| "text" //文本 | "text" //文本
| ( | (
| `videoReference:${number}` | `videoReference:${number}`
| `imageReference:${number}` | `imageReference:${number}`
| `audioReference:${number}` | `audioReference:${number}`
)[]; //多参考(数字代表限制数量) )[]; //多参考(数字代表限制数量)
interface TextModel { interface TextModel {
name: string; name: string;
modelName: string; modelName: string;
type: "text"; type: "text";
think: boolean; think: boolean;
} }
interface ImageModel { interface ImageModel {
name: string; name: string;
modelName: string; modelName: string;
type: "image"; type: "image";
mode: ("text" | "singleImage" | "multiReference")[]; mode: ("text" | "singleImage" | "multiReference")[];
associationSkills?: string; associationSkills?: string;
} }
interface VideoModel { interface VideoModel {
name: string; name: string;
modelName: string; modelName: string;
type: "video"; type: "video";
mode: VideoMode[]; mode: VideoMode[];
associationSkills?: string; associationSkills?: string;
audio: "optional" | false | true; audio: "optional" | false | true;
durationResolutionMap: { duration: number[]; resolution: string[] }[]; durationResolutionMap: { duration: number[]; resolution: string[] }[];
} }
interface TTSModel { interface TTSModel {
name: string; name: string;
modelName: string; modelName: string;
type: "tts"; type: "tts";
voices: { title: string; voice: string }[]; voices: { title: string; voice: string }[];
} }
interface VendorConfig { interface VendorConfig {
id: string; //唯一ID作为文件名存储用户磁盘上禁止符号 id: string; //唯一ID作为文件名存储用户磁盘上禁止符号
version: string; //版本号格式为x.y需遵守语义化版本控制 version: string; //版本号格式为x.y需遵守语义化版本控制
name: string; //供应商名称 name: string; //供应商名称
author: string; //作者 author: string; //作者
description?: string; //描述支持Markdown格式 description?: string; //描述支持Markdown格式
icon?: string; //图标仅支持Base64格式建议尺寸为128x128像素 icon?: string; //图标仅支持Base64格式建议尺寸为128x128像素
inputs: { inputs: {
key: string; key: string;
label: string; label: string;
type: "text" | "password" | "url"; type: "text" | "password" | "url";
required: boolean; required: boolean;
placeholder?: string; placeholder?: string;
}[]; }[];
inputValues: Record<string, string>; inputValues: Record<string, string>;
models: (TextModel | ImageModel | VideoModel | TTSModel)[]; models: (TextModel | ImageModel | VideoModel | TTSModel)[];
} }
type ReferenceList = type ReferenceList =
| { type: "image"; sourceType: "base64"; base64: string } | { type: "image"; sourceType: "base64"; base64: string }
| { type: "audio"; sourceType: "base64"; base64: string } | { type: "audio"; sourceType: "base64"; base64: string }
| { type: "video"; sourceType: "base64"; base64: string }; | { type: "video"; sourceType: "base64"; base64: string };
interface ImageConfig { interface ImageConfig {
prompt: string; prompt: string;
referenceList?: Extract<ReferenceList, { type: "image" }>[]; referenceList?: Extract<ReferenceList, { type: "image" }>[];
size: "1K" | "2K" | "4K"; size: "1K" | "2K" | "4K";
aspectRatio: `${number}:${number}`; aspectRatio: `${number}:${number}`;
} }
interface VideoConfig { interface VideoConfig {
duration: number; duration: number;
resolution: string; resolution: string;
aspectRatio: "16:9" | "9:16"; aspectRatio: "16:9" | "9:16";
prompt: string; prompt: string;
referenceList?: ReferenceList[]; referenceList?: ReferenceList[];
audio?: boolean; audio?: boolean;
mode: VideoMode[]; mode: VideoMode[];
} }
interface TTSConfig { interface TTSConfig {
text: string; text: string;
voice: string; voice: string;
speechRate: number; speechRate: number;
pitchRate: number; pitchRate: number;
volume: number; volume: number;
referenceList?: Extract<ReferenceList, { type: "audio" }>[]; referenceList?: Extract<ReferenceList, { type: "audio" }>[];
} }
interface PollResult { interface PollResult {
completed: boolean; completed: boolean;
data?: string; data?: string;
error?: string; error?: string;
} }
// ============================================================ // ============================================================
// 全局声明 // 全局声明
// ============================================================ // ============================================================
declare const axios: any; // HTTP请求库 declare const axios: any; // HTTP请求库
declare const logger: (msg: string) => void; // 日志函数 declare const logger: (msg: string) => void; // 日志函数
declare const jsonwebtoken: any; // JWT处理库 declare const jsonwebtoken: any; // JWT处理库
declare const zipImage: (base64: string, size: number) => Promise<string>; // 图片压缩函数返回有头base64字符串 declare const zipImage: (base64: string, size: number) => Promise<string>; // 图片压缩函数返回有头base64字符串
declare const zipImageResolution: ( declare const zipImageResolution: (
base64: string, base64: string,
w: number, w: number,
h: number, h: number,
) => Promise<string>; // 图片分辨率调整函数返回有头base64字符串 ) => Promise<string>; // 图片分辨率调整函数返回有头base64字符串
declare const mergeImages: ( declare const mergeImages: (
base64Arr: string[], base64Arr: string[],
maxSize?: string, maxSize?: string,
) => Promise<string>; // 图片合成函数返回有头base64字符串 ) => Promise<string>; // 图片合成函数返回有头base64字符串
declare const urlToBase64: (url: string) => Promise<string>; // URL转Base64函数返回有头base64字符串 declare const urlToBase64: (url: string) => Promise<string>; // URL转Base64函数返回有头base64字符串
declare const pollTask: ( declare const pollTask: (
fn: () => Promise<PollResult>, fn: () => Promise<PollResult>,
interval?: number, interval?: number,
timeout?: number, timeout?: number,
) => Promise<PollResult>; // 轮询函数fn为异步函数interval为轮询间隔timeout为超时时间返回fn的结果 ) => Promise<PollResult>; // 轮询函数fn为异步函数interval为轮询间隔timeout为超时时间返回fn的结果
declare const createOpenAI: any; declare const createOpenAI: any;
declare const createDeepSeek: any; declare const createDeepSeek: any;
declare const createZhipu: any; declare const createZhipu: any;
declare const createQwen: any; declare const createQwen: any;
declare const createAnthropic: any; declare const createAnthropic: any;
declare const createOpenAICompatible: any; declare const createOpenAICompatible: any;
declare const createXai: any; declare const createXai: any;
declare const createMinimax: any; declare const createMinimax: any;
declare const createGoogleGenerativeAI: any; declare const createGoogleGenerativeAI: any;
declare const exports: { declare const exports: {
vendor: VendorConfig; vendor: VendorConfig;
textRequest: (m: TextModel, t: boolean, tl: 0 | 1 | 2 | 3) => any; //文本模型 textRequest: (m: TextModel, t: boolean, tl: 0 | 1 | 2 | 3) => any; //文本模型
imageRequest: (c: ImageConfig, m: ImageModel) => Promise<string>; //图片模型返回有头base64字符串 imageRequest: (c: ImageConfig, m: ImageModel) => Promise<string>; //图片模型返回有头base64字符串
videoRequest: (c: VideoConfig, m: VideoModel) => Promise<string>; //视频模型返回有头base64字符串 videoRequest: (c: VideoConfig, m: VideoModel) => Promise<string>; //视频模型返回有头base64字符串
ttsRequest: (c: TTSConfig, m: TTSModel) => Promise<string>; //暂未开放语音模型返回有头base64字符串 ttsRequest: (c: TTSConfig, m: TTSModel) => Promise<string>; //暂未开放语音模型返回有头base64字符串
checkForUpdates?: () => Promise<{ checkForUpdates?: () => Promise<{
hasUpdate: boolean; hasUpdate: boolean;
latestVersion: string; latestVersion: string;
notice: string; notice: string;
}>; //检查更新函数返回是否有更新和最新版本号和更公告支持Markdown格式 }>; //检查更新函数返回是否有更新和最新版本号和更公告支持Markdown格式
updateVendor?: () => Promise<string>; //更新函数,返回最新的代码文本 updateVendor?: () => Promise<string>; //更新函数,返回最新的代码文本
}; };
// ============================================================ // ============================================================
// 供应商配置 // 供应商配置
// ============================================================ // ============================================================
const vendor: VendorConfig = { const vendor: VendorConfig = {
id: "grsai", id: "grsai",
version: "2.1", version: "2.1",
author: "Toonflow", author: "Toonflow",
name: "Grsai", name: "Grsai",
description: description:
"Grsai AI平台适配支持文生图、图生图、文生视频、Gemini兼容文本模型 \n [前往中转平台](https://tf.grsai.ai/zh)", "Grsai AI平台适配支持文生图、图生图、文生视频、Gemini兼容文本模型 \n [前往中转平台](https://tf.grsai.ai/zh)",
inputs: [ inputs: [
{ key: "apiKey", label: "API密钥", type: "password", required: true }, { key: "apiKey", label: "API密钥", type: "password", required: true },
{ {
key: "baseUrl", key: "baseUrl",
label: "请求地址", label: "请求地址",
type: "url", type: "url",
required: true, required: true,
placeholder: "示例https://grsai.dakka.com.cn", placeholder: "示例https://grsai.dakka.com.cn",
}, },
], ],
inputValues: { apiKey: "", baseUrl: "https://grsai.dakka.com.cn" }, inputValues: { apiKey: "", baseUrl: "https://grsai.dakka.com.cn" },
models: [ models: [
{ {
name: "GPT Image 2", name: "GPT Image 2",
modelName: "gpt-image-2", modelName: "gpt-image-2",
type: "image", type: "image",
mode: ["text", "singleImage", "multiReference"], mode: ["text", "singleImage", "multiReference"],
}, },
{ {
name: "Nano Banana Fast", name: "Nano Banana Fast",
modelName: "nano-banana-fast", modelName: "nano-banana-fast",
type: "image", type: "image",
mode: ["text", "singleImage", "multiReference"], mode: ["text", "singleImage", "multiReference"],
}, },
{ {
name: "Nano Banana 2", name: "Nano Banana 2",
modelName: "nano-banana-2", modelName: "nano-banana-2",
type: "image", type: "image",
mode: ["text", "singleImage", "multiReference"], mode: ["text", "singleImage", "multiReference"],
}, },
{ {
name: "Nano Banana Pro", name: "Nano Banana Pro",
modelName: "nano-banana-pro", modelName: "nano-banana-pro",
type: "image", type: "image",
mode: ["text", "singleImage", "multiReference"], mode: ["text", "singleImage", "multiReference"],
}, },
], ],
}; };
// ============================================================ // ============================================================
// 辅助工具 // 辅助工具
// ============================================================ // ============================================================
const getHeaders = () => { const getHeaders = () => {
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, ""); const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
return { return {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
}; };
}; };
// ============================================================ // ============================================================
// 适配器函数 // 适配器函数
// ============================================================ // ============================================================
const textRequest = ( const textRequest = (
model: TextModel, model: TextModel,
think: boolean, think: boolean,
thinkLevel: 0 | 1 | 2 | 3, thinkLevel: 0 | 1 | 2 | 3,
) => { ) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key"); if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, ""); const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
return createGoogleGenerativeAI({ return createGoogleGenerativeAI({
baseURL: `${vendor.inputValues.baseUrl}/v1beta`, baseURL: `${vendor.inputValues.baseUrl}/v1beta`,
apiKey, apiKey,
}).chat(model.modelName); }).chat(model.modelName);
}; };
const imageRequest = async ( const imageRequest = async (
config: ImageConfig, config: ImageConfig,
model: ImageModel, model: ImageModel,
): Promise<string> => { ): Promise<string> => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key"); if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const baseUrl = vendor.inputValues.baseUrl; const baseUrl = vendor.inputValues.baseUrl;
const headers = getHeaders(); const headers = getHeaders();
// 构造请求参数 // 构造请求参数
const requestBody: any = { const requestBody: any = {
model: model.modelName, model: model.modelName,
prompt: config.prompt, prompt: config.prompt,
aspectRatio: config.aspectRatio, aspectRatio: config.aspectRatio,
webHook: "-1", webHook: "-1",
shutProgress: true, shutProgress: true,
}; };
// 补充模型专属参数 // 补充模型专属参数
if (model.modelName.startsWith("nano-banana")) { if (model.modelName.startsWith("nano-banana")) {
requestBody.imageSize = config.size; requestBody.imageSize = config.size;
} else { } else {
requestBody.size = config.aspectRatio; requestBody.size = config.aspectRatio;
requestBody.variants = 1; requestBody.variants = 1;
} }
// 处理参考图 // 处理参考图
if (config.referenceList && config.referenceList.length > 0) { if (config.referenceList && config.referenceList.length > 0) {
requestBody.urls = config.referenceList.map((img) => img.base64); requestBody.urls = config.referenceList.map((img) => img.base64);
} }
// 选择接口路径 // 选择接口路径
const apiPath = model.modelName.startsWith("nano-banana") const apiPath = model.modelName.startsWith("nano-banana")
? "/v1/draw/nano-banana" ? "/v1/draw/nano-banana"
: "/v1/draw/completions"; : "/v1/draw/completions";
logger(`开始提交图片生成任务,模型:${model.modelName}`); logger(`开始提交图片生成任务,模型:${model.modelName}`);
const submitResp = await axios.post(`${baseUrl}${apiPath}`, requestBody, { const submitResp = await axios.post(`${baseUrl}${apiPath}`, requestBody, {
headers, headers,
}); });
if (submitResp.data.code !== 0) if (submitResp.data.code !== 0)
throw new Error(`任务提交失败:${submitResp.data.msg}`); throw new Error(`任务提交失败:${submitResp.data.msg}`);
const taskId = submitResp.data.data.id; const taskId = submitResp.data.data.id;
logger(`图片任务提交成功任务ID${taskId}`); logger(`图片任务提交成功任务ID${taskId}`);
// 轮询结果 // 轮询结果
const pollResult = await pollTask( const pollResult = await pollTask(
async () => { async () => {
const resp = await axios.post( const resp = await axios.post(
`${baseUrl}/v1/draw/result`, `${baseUrl}/v1/draw/result`,
{ id: taskId }, { id: taskId },
{ headers }, { headers },
); );
if (resp.data.code !== 0) if (resp.data.code !== 0)
return { completed: true, error: resp.data.msg }; return { completed: true, error: resp.data.msg };
const taskData = resp.data.data; const taskData = resp.data.data;
if (taskData.status === "failed") if (taskData.status === "failed")
return { return {
completed: true, completed: true,
error: taskData.failure_reason || taskData.error, error: taskData.failure_reason || taskData.error,
}; };
if (taskData.status === "succeeded") { if (taskData.status === "succeeded") {
const imgUrl = taskData.results?.[0]?.url || taskData.url; const imgUrl = taskData.results?.[0]?.url || taskData.url;
return { completed: true, data: imgUrl }; return { completed: true, data: imgUrl };
} }
logger(`图片任务生成中,进度:${taskData.progress}%`); logger(`图片任务生成中,进度:${taskData.progress}%`);
return { completed: false }; return { completed: false };
}, },
3000, 3000,
600000, 600000,
); );
if (pollResult.error) throw new Error(pollResult.error); if (pollResult.error) throw new Error(pollResult.error);
logger(`图片生成完成开始转换Base64`); logger(`图片生成完成开始转换Base64`);
return await urlToBase64(pollResult.data!); return await urlToBase64(pollResult.data!);
}; };
const videoRequest = async ( const videoRequest = async (
config: VideoConfig, config: VideoConfig,
model: VideoModel, model: VideoModel,
): Promise<string> => { ): Promise<string> => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key"); if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const baseUrl = vendor.inputValues.baseUrl; const baseUrl = vendor.inputValues.baseUrl;
const headers = getHeaders(); const headers = getHeaders();
// 构造请求参数 // 构造请求参数
const requestBody: any = { const requestBody: any = {
model: model.modelName, model: model.modelName,
prompt: config.prompt, prompt: config.prompt,
aspectRatio: config.aspectRatio, aspectRatio: config.aspectRatio,
webHook: "-1", webHook: "-1",
shutProgress: true, shutProgress: true,
}; };
// 处理参考资源 // 处理参考资源
if (config.referenceList && config.referenceList.length > 0) { if (config.referenceList && config.referenceList.length > 0) {
const imageRefs = config.referenceList.filter( const imageRefs = config.referenceList.filter(
(item) => item.type === "image", (item) => item.type === "image",
) as Extract<ReferenceList, { type: "image" }>[]; ) as Extract<ReferenceList, { type: "image" }>[];
if (config.mode.includes("endFrameOptional") && imageRefs.length >= 1) { if (config.mode.includes("endFrameOptional") && imageRefs.length >= 1) {
requestBody.firstFrameUrl = imageRefs[0].base64; requestBody.firstFrameUrl = imageRefs[0].base64;
if (imageRefs.length >= 2) requestBody.lastFrameUrl = imageRefs[1].base64; if (imageRefs.length >= 2) requestBody.lastFrameUrl = imageRefs[1].base64;
} else if ( } else if (
config.mode.some( config.mode.some(
(m) => Array.isArray(m) && m.includes("imageReference:3"), (m) => Array.isArray(m) && m.includes("imageReference:3"),
) )
) { ) {
requestBody.urls = imageRefs.map((img) => img.base64); requestBody.urls = imageRefs.map((img) => img.base64);
} }
} }
logger(`开始提交视频生成任务,模型:${model.modelName}`); logger(`开始提交视频生成任务,模型:${model.modelName}`);
const submitResp = await axios.post(`${baseUrl}/v1/video/veo`, requestBody, { const submitResp = await axios.post(`${baseUrl}/v1/video/veo`, requestBody, {
headers, headers,
}); });
if (submitResp.data.code !== 0) if (submitResp.data.code !== 0)
throw new Error(`任务提交失败:${submitResp.data.msg}`); throw new Error(`任务提交失败:${submitResp.data.msg}`);
const taskId = submitResp.data.data.id; const taskId = submitResp.data.data.id;
logger(`视频任务提交成功任务ID${taskId}`); logger(`视频任务提交成功任务ID${taskId}`);
// 轮询结果 // 轮询结果
const pollResult = await pollTask( const pollResult = await pollTask(
async () => { async () => {
const resp = await axios.post( const resp = await axios.post(
`${baseUrl}/v1/draw/result`, `${baseUrl}/v1/draw/result`,
{ id: taskId }, { id: taskId },
{ headers }, { headers },
); );
if (resp.data.code !== 0) if (resp.data.code !== 0)
return { completed: true, error: resp.data.msg }; return { completed: true, error: resp.data.msg };
const taskData = resp.data.data; const taskData = resp.data.data;
if (taskData.status === "failed") if (taskData.status === "failed")
return { return {
completed: true, completed: true,
error: taskData.failure_reason || taskData.error, error: taskData.failure_reason || taskData.error,
}; };
if (taskData.status === "succeeded") { if (taskData.status === "succeeded") {
return { completed: true, data: taskData.url }; return { completed: true, data: taskData.url };
} }
logger(`视频任务生成中,进度:${taskData.progress}%`); logger(`视频任务生成中,进度:${taskData.progress}%`);
return { completed: false }; return { completed: false };
}, },
5000, 5000,
1800000, 1800000,
); );
if (pollResult.error) throw new Error(pollResult.error); if (pollResult.error) throw new Error(pollResult.error);
logger(`视频生成完成开始转换Base64`); logger(`视频生成完成开始转换Base64`);
return await urlToBase64(pollResult.data!); return await urlToBase64(pollResult.data!);
}; };
const ttsRequest = async ( const ttsRequest = async (
config: TTSConfig, config: TTSConfig,
model: TTSModel, model: TTSModel,
): Promise<string> => { ): Promise<string> => {
return ""; return "";
}; };
const checkForUpdates = async (): Promise<{ const checkForUpdates = async (): Promise<{
hasUpdate: boolean; hasUpdate: boolean;
latestVersion: string; latestVersion: string;
notice: string; notice: string;
}> => { }> => {
return { return {
hasUpdate: false, hasUpdate: false,
latestVersion: "1.0", latestVersion: "1.0",
notice: "## 新版本更新公告", notice: "## 新版本更新公告",
}; };
}; };
const updateVendor = async (): Promise<string> => { const updateVendor = async (): Promise<string> => {
return ""; return "";
}; };
// ============================================================ // ============================================================
// 导出 // 导出
// ============================================================ // ============================================================
exports.vendor = vendor; exports.vendor = vendor;
exports.textRequest = textRequest; exports.textRequest = textRequest;
exports.imageRequest = imageRequest; exports.imageRequest = imageRequest;
exports.videoRequest = videoRequest; exports.videoRequest = videoRequest;
exports.ttsRequest = ttsRequest; exports.ttsRequest = ttsRequest;
exports.checkForUpdates = checkForUpdates; exports.checkForUpdates = checkForUpdates;
exports.updateVendor = updateVendor; exports.updateVendor = updateVendor;
// 这行代码用于确保当前文件被识别为模块,避免全局变量冲突 // 这行代码用于确保当前文件被识别为模块,避免全局变量冲突
export {}; export {};

668
data/vendor/null.ts vendored
View File

@ -1,334 +1,334 @@
/** /**
* Toonflow AI供应商模板 * Toonflow AI供应商模板
* @version 2.0 * @version 2.0
*/ */
// ============================================================ // ============================================================
// 类型定义 // 类型定义
// ============================================================ // ============================================================
type VideoMode = type VideoMode =
| "singleImage" //单图参考 | "singleImage" //单图参考
| "startEndRequired" //首尾帧(两张都得有) | "startEndRequired" //首尾帧(两张都得有)
| "endFrameOptional" //首尾帧(尾帧可选) | "endFrameOptional" //首尾帧(尾帧可选)
| "startFrameOptional" //首尾帧(首帧可选) | "startFrameOptional" //首尾帧(首帧可选)
| "text" //文本 | "text" //文本
| (`videoReference:${number}` | `imageReference:${number}` | `audioReference:${number}`)[]; //多参考(数字代表限制数量) | (`videoReference:${number}` | `imageReference:${number}` | `audioReference:${number}`)[]; //多参考(数字代表限制数量)
interface TextModel { interface TextModel {
name: string; name: string;
modelName: string; modelName: string;
type: "text"; type: "text";
think: boolean; think: boolean;
} }
interface ImageModel { interface ImageModel {
name: string; name: string;
modelName: string; modelName: string;
type: "image"; type: "image";
mode: ("text" | "singleImage" | "multiReference")[]; mode: ("text" | "singleImage" | "multiReference")[];
associationSkills?: string; associationSkills?: string;
} }
interface VideoModel { interface VideoModel {
name: string; name: string;
modelName: string; modelName: string;
type: "video"; type: "video";
mode: VideoMode[]; mode: VideoMode[];
associationSkills?: string; associationSkills?: string;
audio: "optional" | false | true; audio: "optional" | false | true;
durationResolutionMap: { duration: number[]; resolution: string[] }[]; durationResolutionMap: { duration: number[]; resolution: string[] }[];
} }
interface TTSModel { interface TTSModel {
name: string; name: string;
modelName: string; modelName: string;
type: "tts"; type: "tts";
voices: { title: string; voice: string }[]; voices: { title: string; voice: string }[];
} }
interface VendorConfig { interface VendorConfig {
id: string; //唯一ID作为文件名存储用户磁盘上禁止符号 id: string; //唯一ID作为文件名存储用户磁盘上禁止符号
version: string; //版本号格式为x.y需遵守语义化版本控制 version: string; //版本号格式为x.y需遵守语义化版本控制
name: string; //供应商名称 name: string; //供应商名称
author: string; //作者 author: string; //作者
description?: string; //描述支持Markdown格式 description?: string; //描述支持Markdown格式
icon?: string; //图标仅支持Base64格式建议尺寸为128x128像素 icon?: string; //图标仅支持Base64格式建议尺寸为128x128像素
inputs: { key: string; label: string; type: "text" | "password" | "url"; required: boolean; placeholder?: string }[]; inputs: { key: string; label: string; type: "text" | "password" | "url"; required: boolean; placeholder?: string }[];
inputValues: Record<string, string>; inputValues: Record<string, string>;
models: (TextModel | ImageModel | VideoModel | TTSModel)[]; models: (TextModel | ImageModel | VideoModel | TTSModel)[];
} }
type ReferenceList = type ReferenceList =
| { type: "image"; sourceType: "base64"; base64: string } | { type: "image"; sourceType: "base64"; base64: string }
| { type: "audio"; sourceType: "base64"; base64: string } | { type: "audio"; sourceType: "base64"; base64: string }
| { type: "video"; sourceType: "base64"; base64: string }; | { type: "video"; sourceType: "base64"; base64: string };
interface ImageConfig { interface ImageConfig {
prompt: string; prompt: string;
referenceList?: Extract<ReferenceList, { type: "image" }>[]; referenceList?: Extract<ReferenceList, { type: "image" }>[];
size: "1K" | "2K" | "4K"; size: "1K" | "2K" | "4K";
aspectRatio: `${number}:${number}`; aspectRatio: `${number}:${number}`;
} }
interface VideoConfig { interface VideoConfig {
duration: number; duration: number;
resolution: string; resolution: string;
aspectRatio: "16:9" | "9:16"; aspectRatio: "16:9" | "9:16";
prompt: string; prompt: string;
referenceList?: ReferenceList[]; referenceList?: ReferenceList[];
audio?: boolean; audio?: boolean;
mode: VideoMode[]; mode: VideoMode[];
} }
interface TTSConfig { interface TTSConfig {
text: string; text: string;
voice: string; voice: string;
speechRate: number; speechRate: number;
pitchRate: number; pitchRate: number;
volume: number; volume: number;
referenceList?: Extract<ReferenceList, { type: "audio" }>[]; referenceList?: Extract<ReferenceList, { type: "audio" }>[];
} }
interface PollResult { interface PollResult {
completed: boolean; completed: boolean;
data?: string; data?: string;
error?: string; error?: string;
} }
// ============================================================ // ============================================================
// 全局声明 // 全局声明
// ============================================================ // ============================================================
declare const axios: any; // HTTP请求库 declare const axios: any; // HTTP请求库
declare const logger: (msg: string) => void; // 日志函数 declare const logger: (msg: string) => void; // 日志函数
declare const jsonwebtoken: any; // JWT处理库 declare const jsonwebtoken: any; // JWT处理库
declare const zipImage: (base64: string, size: number) => Promise<string>; // 图片压缩函数返回有头base64字符串 declare const zipImage: (base64: string, size: number) => Promise<string>; // 图片压缩函数返回有头base64字符串
declare const zipImageResolution: (base64: string, w: number, h: number) => Promise<string>; // 图片分辨率调整函数返回有头base64字符串 declare const zipImageResolution: (base64: string, w: number, h: number) => Promise<string>; // 图片分辨率调整函数返回有头base64字符串
declare const mergeImages: (base64Arr: string[], maxSize?: string) => Promise<string>; // 图片合成函数返回有头base64字符串 declare const mergeImages: (base64Arr: string[], maxSize?: string) => Promise<string>; // 图片合成函数返回有头base64字符串
declare const urlToBase64: (url: string) => Promise<string>; // URL转Base64函数返回有头base64字符串 declare const urlToBase64: (url: string) => Promise<string>; // URL转Base64函数返回有头base64字符串
declare const pollTask: (fn: () => Promise<PollResult>, interval?: number, timeout?: number) => Promise<PollResult>; // 轮询函数fn为异步函数interval为轮询间隔timeout为超时时间返回fn的结果 declare const pollTask: (fn: () => Promise<PollResult>, interval?: number, timeout?: number) => Promise<PollResult>; // 轮询函数fn为异步函数interval为轮询间隔timeout为超时时间返回fn的结果
declare const createOpenAI: any; declare const createOpenAI: any;
declare const createDeepSeek: any; declare const createDeepSeek: any;
declare const createZhipu: any; declare const createZhipu: any;
declare const createQwen: any; declare const createQwen: any;
declare const createAnthropic: any; declare const createAnthropic: any;
declare const createOpenAICompatible: any; declare const createOpenAICompatible: any;
declare const createXai: any; declare const createXai: any;
declare const createMinimax: any; declare const createMinimax: any;
declare const createGoogleGenerativeAI: any; declare const createGoogleGenerativeAI: any;
declare const exports: { declare const exports: {
vendor: VendorConfig; vendor: VendorConfig;
textRequest: (m: TextModel, t: boolean, tl: 0 | 1 | 2 | 3) => any; //文本模型 textRequest: (m: TextModel, t: boolean, tl: 0 | 1 | 2 | 3) => any; //文本模型
imageRequest: (c: ImageConfig, m: ImageModel) => Promise<string>; //图片模型返回有头base64字符串 imageRequest: (c: ImageConfig, m: ImageModel) => Promise<string>; //图片模型返回有头base64字符串
videoRequest: (c: VideoConfig, m: VideoModel) => Promise<string>; //视频模型返回有头base64字符串 videoRequest: (c: VideoConfig, m: VideoModel) => Promise<string>; //视频模型返回有头base64字符串
ttsRequest: (c: TTSConfig, m: TTSModel) => Promise<string>; //暂未开放语音模型返回有头base64字符串 ttsRequest: (c: TTSConfig, m: TTSModel) => Promise<string>; //暂未开放语音模型返回有头base64字符串
checkForUpdates?: () => Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }>; //检查更新函数返回是否有更新和最新版本号和更公告支持Markdown格式 checkForUpdates?: () => Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }>; //检查更新函数返回是否有更新和最新版本号和更公告支持Markdown格式
updateVendor?: () => Promise<string>; //更新函数,返回最新的代码文本 updateVendor?: () => Promise<string>; //更新函数,返回最新的代码文本
}; };
// ============================================================ // ============================================================
// 供应商配置 // 供应商配置
// ============================================================ // ============================================================
const vendor: VendorConfig = { const vendor: VendorConfig = {
id: "null", id: "null",
version: "2.0", version: "2.0",
author: "Toonflow", author: "Toonflow",
name: "空模板", name: "空模板",
description: "## 开发模板您可以使用此模板进行Vibe Coding", description: "## 开发模板您可以使用此模板进行Vibe Coding",
inputs: [ inputs: [
{ key: "apiKey", label: "API密钥", type: "password", required: true }, { key: "apiKey", label: "API密钥", type: "password", required: true },
{ key: "baseUrl", label: "请求地址", type: "url", required: true, placeholder: "示例https://api.openai.com/v1" }, { key: "baseUrl", label: "请求地址", type: "url", required: true, placeholder: "示例https://api.openai.com/v1" },
], ],
inputValues: { apiKey: "", baseUrl: "https://api.openai.com/v1" }, inputValues: { apiKey: "", baseUrl: "https://api.openai.com/v1" },
models: [{ name: "GPT-4o", modelName: "gpt-4o", type: "text", think: false }], models: [{ name: "GPT-4o", modelName: "gpt-4o", type: "text", think: false }],
}; };
// ============================================================ // ============================================================
// 适配器函数 // 适配器函数
// ============================================================ // ============================================================
const textRequest = (model: TextModel, think: boolean, thinkLevel: 0 | 1 | 2 | 3) => { const textRequest = (model: TextModel, think: boolean, thinkLevel: 0 | 1 | 2 | 3) => {
if (!vendor.inputValues.apiKey) throw new Error("缺少API Key"); if (!vendor.inputValues.apiKey) throw new Error("缺少API Key");
const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, ""); const apiKey = vendor.inputValues.apiKey.replace(/^Bearer\s+/i, "");
return createOpenAI({ baseURL: vendor.inputValues.baseUrl, apiKey }).chat(model.modelName); return createOpenAI({ baseURL: vendor.inputValues.baseUrl, apiKey }).chat(model.modelName);
}; };
const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => { const imageRequest = async (config: ImageConfig, model: ImageModel): Promise<string> => {
return ""; return "";
}; };
const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<string> => { const videoRequest = async (config: VideoConfig, model: VideoModel): Promise<string> => {
return ""; return "";
}; };
const ttsRequest = async (config: TTSConfig, model: TTSModel): Promise<string> => { const ttsRequest = async (config: TTSConfig, model: TTSModel): Promise<string> => {
return ""; return "";
}; };
const checkForUpdates = async (): Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }> => { const checkForUpdates = async (): Promise<{ hasUpdate: boolean; latestVersion: string; notice: string }> => {
return { hasUpdate: false, latestVersion: "2.0", notice: "## 新版本更新公告" }; return { hasUpdate: false, latestVersion: "2.0", notice: "## 新版本更新公告" };
}; };
const updateVendor = async (): Promise<string> => { const updateVendor = async (): Promise<string> => {
return ""; return "";
}; };
// ============================================================ // ============================================================
// 导出 // 导出
// ============================================================ // ============================================================
exports.vendor = vendor; exports.vendor = vendor;
exports.textRequest = textRequest; exports.textRequest = textRequest;
exports.imageRequest = imageRequest; exports.imageRequest = imageRequest;
exports.videoRequest = videoRequest; exports.videoRequest = videoRequest;
exports.ttsRequest = ttsRequest; exports.ttsRequest = ttsRequest;
exports.checkForUpdates = checkForUpdates; exports.checkForUpdates = checkForUpdates;
exports.updateVendor = updateVendor; exports.updateVendor = updateVendor;
// 这行代码用于确保当前文件被识别为模块,避免全局变量冲突 // 这行代码用于确保当前文件被识别为模块,避免全局变量冲突
export {}; export {};
/** /**
* ============================================================ * ============================================================
* AI * AI
* ============================================================ * ============================================================
* *
* *
* Toonflow AI AI * Toonflow AI AI
* curl API * curl API
* *
* *
* *
* 1. API curl HeadersBody * 1. API curl HeadersBody
* 2. API / * 2. API /
* 3. text / image / video / tts * 3. text / image / video / tts
* API * API
* *
* *
* *
* 1. * 1.
* 使 import / require使 * 使 import / require使
* axiosloggerjsonwebtokenzipImagezipImageResolutionmergeImages * axiosloggerjsonwebtokenzipImagezipImageResolutionmergeImages
* urlToBase64pollTask createOpenAIcreateDeepSeekcreateZhipucreateQwen * urlToBase64pollTask createOpenAIcreateDeepSeekcreateZhipucreateQwen
* createAnthropiccreateOpenAICompatiblecreateXaicreateMinimax * createAnthropiccreateOpenAICompatiblecreateXaicreateMinimax
* createGoogleGenerativeAI AI SDK * createGoogleGenerativeAI AI SDK
* *
* 2. exports.* * 2. exports.*
* const API_URL = "https://..."; const MAX_RETRY = 3; * const API_URL = "https://..."; const MAX_RETRY = 3;
* vendor.inputValues * vendor.inputValues
* vendor.inputValues.xxx 访 * vendor.inputValues.xxx 访
* 使 exports.* 使 * 使 exports.* 使
* *
* 3. exports.* * 3. exports.*
* textRequest / imageRequest / videoRequest / ttsRequest * textRequest / imageRequest / videoRequest / ttsRequest
* *
* Token * Token
* *
* 使 * 使
* *
* 4. * 4.
* 使camelCase使 UPPER_SNAKE_CASE * 使camelCase使 UPPER_SNAKE_CASE
* *
* 5. * 5.
* VendorConfigImageConfigVideoConfig * VendorConfigImageConfigVideoConfig
* TTSConfigTextModelImageModelVideoModelTTSModelReferenceListPollResult * TTSConfigTextModelImageModelVideoModelTTSModelReferenceListPollResult
* AI 使 * AI 使
* *
* 6. * 6.
* - textRequest(model) AI SDK chat model createOpenAI * - textRequest(model) AI SDK chat model createOpenAI
* - imageRequest(config, model) base64 "data:image/png;base64,..." * - imageRequest(config, model) base64 "data:image/png;base64,..."
* config.referenceList Extract<ReferenceList, { type: "image" }>[] * config.referenceList Extract<ReferenceList, { type: "image" }>[]
* base64 sourceType "base64" * base64 sourceType "base64"
* - videoRequest(config, model) base64 "data:video/mp4;base64,..." * - videoRequest(config, model) base64 "data:video/mp4;base64,..."
* config.referenceList ReferenceList[] image / video / audio * config.referenceList ReferenceList[] image / video / audio
* base64 sourceType "base64" * base64 sourceType "base64"
* config.mode mode 使 referenceList * config.mode mode 使 referenceList
* - ttsRequest(config, model) base64 "data:audio/mp3;base64,..." * - ttsRequest(config, model) base64 "data:audio/mp3;base64,..."
* config.referenceList Extract<ReferenceList, { type: "audio" }>[] * config.referenceList Extract<ReferenceList, { type: "audio" }>[]
* API URL 使 urlToBase64(url) * API URL 使 urlToBase64(url)
* *
* 7. ReferenceList VideoMode * 7. ReferenceList VideoMode
* ReferenceList * ReferenceList
* - type: "image" | "audio" | "video" * - type: "image" | "audio" | "video"
* - sourceType: "base64" base64 * - sourceType: "base64" base64
* - base64 * - base64
* *
* VideoMode * VideoMode
* - "text" * - "text"
* - "singleImage" * - "singleImage"
* - "startEndRequired" * - "startEndRequired"
* - "endFrameOptional" * - "endFrameOptional"
* - "startFrameOptional" * - "startFrameOptional"
* - ["imageReference:9", "videoReference:3", "audioReference:3"] * - ["imageReference:9", "videoReference:3", "audioReference:3"]
* *
* *
* videoRequest config.mode * videoRequest config.mode
* - config.referenceList * - config.referenceList
* - API // * - API //
* *
* 8. * 8.
* 使 pollTask * 使 pollTask
* const result = await pollTask(async () => { * const result = await pollTask(async () => {
* const resp = await axios.get(...); * const resp = await axios.get(...);
* if (resp.data.status === "SUCCESS") return { completed: true, data: resp.data.url }; * if (resp.data.status === "SUCCESS") return { completed: true, data: resp.data.url };
* if (resp.data.status === "FAILED") return { completed: true, error: resp.data.message }; * if (resp.data.status === "FAILED") return { completed: true, error: resp.data.message };
* return { completed: false }; * return { completed: false };
* }, 5000, 600000); // 每5秒轮询10分钟超时 * }, 5000, 600000); // 每5秒轮询10分钟超时
* if (result.error) throw new Error(result.error); * if (result.error) throw new Error(result.error);
* return await urlToBase64(result.data!); * return await urlToBase64(result.data!);
* *
* 9. * 9.
* API Key使 throw new Error("...") * API Key使 throw new Error("...")
* API * API
* *
* 10. * 10.
* 使 logger("...") "开始提交任务""任务ID: xxx""轮询中..." * 使 logger("...") "开始提交任务""任务ID: xxx""轮询中..."
* 便 * 便
* *
* 11. vendor * 11. vendor
* - id使 * - id使
* - version "x.y" * - version "x.y"
* - inputs API API KeySecret * - inputs API API KeySecret
* - models type * - models type
* - VideoModel mode API 7 VideoMode * - VideoModel mode API 7 VideoMode
* - VideoModel audio truefalse"optional" * - VideoModel audio truefalse"optional"
* - VideoModel durationResolutionMap * - VideoModel durationResolutionMap
* - VideoModel associationSkills * - VideoModel associationSkills
* - ImageModel mode API "text" "singleImage" "multiReference" * - ImageModel mode API "text" "singleImage" "multiReference"
* - TTSModel voices * - TTSModel voices
* *
* 12. * 12.
* - 使 zipImage(base64, maxSizeKB) * - 使 zipImage(base64, maxSizeKB)
* - 使 zipImageResolution(base64, width, height) * - 使 zipImageResolution(base64, width, height)
* - 使 mergeImages(base64Arr, maxSize) * - 使 mergeImages(base64Arr, maxSize)
* - base64 * - base64
* *
* 13. * 13.
* *
* [] * []
* 线 * 线
* getHeadersgetBaseUrl * getHeadersgetBaseUrl
* *
* 14. * 14.
* exports.xxx = xxx * exports.xxx = xxx
* - exports.vendor * - exports.vendor
* - exports.textRequest * - exports.textRequest
* - exports.imageRequest * - exports.imageRequest
* - exports.videoRequest * - exports.videoRequest
* - exports.ttsRequest * - exports.ttsRequest
* - exports.checkForUpdates * - exports.checkForUpdates
* - exports.updateVendor * - exports.updateVendor
* return "" * return ""
* export {}; * export {};
* *
* *
* *
* 1. curl API * 1. curl API
* 2. API / * 2. API /
* 3. vendor * 3. vendor
* 4. ReferenceList base64 referenceList * 4. ReferenceList base64 referenceList
* 5. return "" * 5. return ""
* 6. * 6.
*/ */

View File

@ -1 +1 @@
1.1.7 1.1.7