Merge branch 'develop' of https://github.com/HBAI-Ltd/Toonflow-app into develop
This commit is contained in:
commit
2b13b3e55c
43
.github/workflows/release.yml
vendored
43
.github/workflows/release.yml
vendored
@ -78,8 +78,41 @@ jobs:
|
||||
dist/*.zip
|
||||
retention-days: 30
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build application
|
||||
run: yarn build
|
||||
|
||||
- name: Build Linux installer
|
||||
run: yarn dist:linux
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-builds
|
||||
path: |
|
||||
dist/*.AppImage
|
||||
dist/*.deb
|
||||
retention-days: 30
|
||||
|
||||
release:
|
||||
needs: [build-windows, build-macos]
|
||||
needs: [build-windows, build-macos, build-linux]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
@ -99,6 +132,12 @@ jobs:
|
||||
name: macos-builds
|
||||
path: dist
|
||||
|
||||
- name: Download Linux artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux-builds
|
||||
path: dist
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
@ -110,5 +149,7 @@ jobs:
|
||||
dist/*.exe
|
||||
dist/*.zip
|
||||
dist/*.dmg
|
||||
dist/*.AppImage
|
||||
dist/*.deb
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@ -24,8 +24,14 @@ asar: true
|
||||
|
||||
win:
|
||||
target:
|
||||
- nsis
|
||||
- portable
|
||||
- target: nsis
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
- target: portable
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
icon: ./scripts/logo.ico
|
||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||
|
||||
@ -34,22 +40,34 @@ nsis:
|
||||
allowToChangeInstallationDirectory: true
|
||||
perMachine: true
|
||||
shortcutName: ${productName}
|
||||
artifactName: ${productName}-Setup-${version}.${ext}
|
||||
artifactName: ${productName}-Setup-${version}-${arch}.${ext}
|
||||
installerIcon: './scripts/logo.ico'
|
||||
uninstallerIcon: './scripts/logo.ico'
|
||||
|
||||
mac:
|
||||
target:
|
||||
- dmg
|
||||
- zip
|
||||
- target: dmg
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
- target: zip
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
icon: ./scripts/logo.icns
|
||||
category: public.app-category.developer-tools
|
||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||
|
||||
linux:
|
||||
target:
|
||||
- AppImage
|
||||
- deb
|
||||
- target: AppImage
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
- target: deb
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
icon: ./scripts/logo.png
|
||||
category: Development
|
||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "toonflow-app",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "Toonflow 是一款 AI 短剧漫剧工具,能够利用 AI 技术将小说自动转化为剧本,并结合 AI 生成的图片和视频,实现高效的短剧创作。",
|
||||
"author": "HBAI-Ltd <ltlctools@outlook.com>",
|
||||
"homepage": "https://github.com/HBAI-Ltd/Toonflow-app#readme",
|
||||
@ -58,7 +58,7 @@
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"knex": "^3.1.0",
|
||||
"morgan": "^1.10.1",
|
||||
"qwen-ai-provider": "^0.1.1",
|
||||
"qwen-ai-provider-v5": "^2.1.0",
|
||||
"serialize-error": "^13.0.1",
|
||||
"sharp": "^0.34.5",
|
||||
"sqlite3": "^5.1.7",
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { app, BrowserWindow } from "electron";
|
||||
import path from "path";
|
||||
import startServe, { closeServe } from "src/app";
|
||||
import { number } from "zod";
|
||||
|
||||
function createMainWindow(): void {
|
||||
// 默认端口配置
|
||||
const defaultPort = 60000;
|
||||
|
||||
function createMainWindow(port: any): void {
|
||||
const win = new BrowserWindow({
|
||||
width: 900,
|
||||
height: 600,
|
||||
@ -14,14 +18,28 @@ function createMainWindow(): void {
|
||||
const htmlPath = isDev
|
||||
? path.join(process.cwd(), "scripts", "web", "index.html")
|
||||
: path.join(app.getAppPath(), "scripts", "web", "index.html");
|
||||
void win.loadFile(htmlPath);
|
||||
|
||||
// 使用实际端口构建地址
|
||||
const baseUrl = `http://localhost:${port}`;
|
||||
const wsBaseUrl = `ws://localhost:${port}`;
|
||||
|
||||
// 构建带有 query 参数的 URL
|
||||
const url = new URL(`file://${htmlPath}`);
|
||||
url.searchParams.set("baseUrl", baseUrl);
|
||||
url.searchParams.set("wsBaseUrl", wsBaseUrl);
|
||||
|
||||
console.log("%c Line:30 🥓 url", "background:#33a5ff", url.toString());
|
||||
|
||||
void win.loadURL(url.toString());
|
||||
}
|
||||
app.whenReady().then(async () => {
|
||||
createMainWindow();
|
||||
try {
|
||||
await startServe();
|
||||
const port = await startServe(false);
|
||||
createMainWindow(60000);
|
||||
} catch (err) {
|
||||
console.error("[服务启动失败]:", err);
|
||||
// 如果服务启动失败,使用默认端口创建窗口
|
||||
createMainWindow(defaultPort);
|
||||
}
|
||||
});
|
||||
|
||||
@ -30,7 +48,10 @@ app.on("window-all-closed", () => {
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createMainWindow();
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
// 重新激活时使用默认端口
|
||||
createMainWindow(defaultPort);
|
||||
}
|
||||
});
|
||||
|
||||
app.on("before-quit", async (event) => {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -242,11 +242,10 @@ ${sections.join("\n\n")}
|
||||
const skipped: number[] = [];
|
||||
|
||||
for (const item of shots) {
|
||||
const resultIndex = item.segmentIndex - 1;
|
||||
|
||||
const exists = this.shots.some((f) => f.segmentId === resultIndex);
|
||||
const exists = this.shots.some((f) => f.segmentId === item.segmentIndex);
|
||||
if (exists) {
|
||||
skipped.push(resultIndex);
|
||||
skipped.push(item.segmentIndex);
|
||||
continue;
|
||||
}
|
||||
// 分配独立的分镜ID
|
||||
@ -254,15 +253,15 @@ ${sections.join("\n\n")}
|
||||
const shotId = this.shotIdCounter;
|
||||
this.shots.push({
|
||||
id: shotId,
|
||||
segmentId: resultIndex,
|
||||
segmentId: item.segmentIndex,
|
||||
title: `分镜 ${shotId}`,
|
||||
x: 0,
|
||||
y: 0,
|
||||
cells: item.prompts.map((prompt) => ({ id: u.uuid(), prompt })),
|
||||
fragmentContent: this.segments[resultIndex]?.description,
|
||||
fragmentContent: this.segments[item.segmentIndex - 1]?.description,
|
||||
assetsTags: item.assetsTags,
|
||||
});
|
||||
added.push({ id: shotId, segmentIndex: resultIndex });
|
||||
added.push({ id: shotId, segmentIndex: item.segmentIndex });
|
||||
}
|
||||
|
||||
const addedInfo = added.map((a) => `分镜${a.id}(片段${a.segmentIndex})`).join(", ");
|
||||
|
||||
15
src/app.ts
15
src/app.ts
@ -14,7 +14,7 @@ import jwt from "jsonwebtoken";
|
||||
const app = express();
|
||||
let server: ReturnType<typeof app.listen> | null = null;
|
||||
|
||||
export default async function startServe() {
|
||||
export default async function startServe(randomPort: Boolean = false) {
|
||||
if (process.env.NODE_ENV == "dev") await buildRoute();
|
||||
|
||||
expressWs(app);
|
||||
@ -77,11 +77,14 @@ export default async function startServe() {
|
||||
res.status(err.status || 500).send(err);
|
||||
});
|
||||
|
||||
const port = parseInt(process.env.PORT || "60000");
|
||||
server = app.listen(port, async () => {
|
||||
const address = server?.address();
|
||||
const realPort = typeof address === "string" ? address : address?.port;
|
||||
console.log(`[服务启动成功]: http://localhost:${realPort}`);
|
||||
const port = randomPort ? 0 : parseInt(process.env.PORT || "60000");
|
||||
return await new Promise((resolve, reject) => {
|
||||
server = app.listen(port, async (v) => {
|
||||
const address = server?.address();
|
||||
const realPort = typeof address === "string" ? address : address?.port;
|
||||
console.log(`[服务启动成功]: http://localhost:${realPort}`);
|
||||
resolve(realPort);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
312
src/router.ts
312
src/router.ts
@ -1,160 +1,170 @@
|
||||
// @routes-hash 3cfad40b3c8658b442ab766a9323d740
|
||||
// @routes-hash c97cf72361299980ea4b0c43549a0de8
|
||||
import { Express } from "express";
|
||||
|
||||
import route1 from "./routes/assets/addAssets";
|
||||
import route2 from "./routes/assets/delAssets";
|
||||
import route3 from "./routes/assets/generateAssets";
|
||||
import route4 from "./routes/assets/getAssets";
|
||||
import route5 from "./routes/assets/getImage";
|
||||
import route6 from "./routes/assets/getStoryboard";
|
||||
import route7 from "./routes/assets/polishPrompt";
|
||||
import route8 from "./routes/assets/saveAssets";
|
||||
import route9 from "./routes/assets/updateAssets";
|
||||
import route10 from "./routes/index/index";
|
||||
import route11 from "./routes/novel/addNovel";
|
||||
import route12 from "./routes/novel/delNovel";
|
||||
import route13 from "./routes/novel/getNovel";
|
||||
import route14 from "./routes/novel/updateNovel";
|
||||
import route15 from "./routes/other/clearDatabase";
|
||||
import route16 from "./routes/other/deleteAllData";
|
||||
import route17 from "./routes/other/getCaptcha";
|
||||
import route18 from "./routes/other/login";
|
||||
import route19 from "./routes/other/testAI";
|
||||
import route20 from "./routes/other/testImage";
|
||||
import route21 from "./routes/other/testVideo";
|
||||
import route22 from "./routes/outline/addOutline";
|
||||
import route23 from "./routes/outline/agentsOutline";
|
||||
import route24 from "./routes/outline/delOutline";
|
||||
import route25 from "./routes/outline/getHistory";
|
||||
import route26 from "./routes/outline/getOutline";
|
||||
import route27 from "./routes/outline/getPartScript";
|
||||
import route28 from "./routes/outline/getStoryline";
|
||||
import route29 from "./routes/outline/setHistory";
|
||||
import route30 from "./routes/outline/updateOutline";
|
||||
import route31 from "./routes/outline/updateScript";
|
||||
import route32 from "./routes/outline/updateStoryline";
|
||||
import route33 from "./routes/project/addProject";
|
||||
import route34 from "./routes/project/delProject";
|
||||
import route35 from "./routes/project/getProject";
|
||||
import route36 from "./routes/project/getProjectCount";
|
||||
import route37 from "./routes/project/getSingleProject";
|
||||
import route38 from "./routes/project/updateProject";
|
||||
import route39 from "./routes/prompt/getPrompts";
|
||||
import route40 from "./routes/prompt/updatePrompt";
|
||||
import route41 from "./routes/script/generateScriptApi";
|
||||
import route42 from "./routes/script/generateScriptSave";
|
||||
import route43 from "./routes/script/geScriptApi";
|
||||
import route44 from "./routes/setting/addModel";
|
||||
import route45 from "./routes/setting/configurationModel";
|
||||
import route46 from "./routes/setting/delModel";
|
||||
import route47 from "./routes/setting/getAiModelMap";
|
||||
import route48 from "./routes/setting/getLog";
|
||||
import route49 from "./routes/setting/getSetting";
|
||||
import route50 from "./routes/setting/getVideoModelList";
|
||||
import route51 from "./routes/setting/updateModel";
|
||||
import route52 from "./routes/setting/updeteModel";
|
||||
import route53 from "./routes/storyboard/batchSuperScoreImage";
|
||||
import route54 from "./routes/storyboard/chatStoryboard";
|
||||
import route55 from "./routes/storyboard/generateShotImage";
|
||||
import route56 from "./routes/storyboard/generateStoryboardApi";
|
||||
import route57 from "./routes/storyboard/generateVideoPrompt";
|
||||
import route58 from "./routes/storyboard/getStoryboard";
|
||||
import route59 from "./routes/storyboard/keepStoryboard";
|
||||
import route60 from "./routes/storyboard/saveStoryboard";
|
||||
import route61 from "./routes/storyboard/uploadImage";
|
||||
import route62 from "./routes/task/getTaskApi";
|
||||
import route63 from "./routes/task/taskDetails";
|
||||
import route64 from "./routes/user/getUser";
|
||||
import route65 from "./routes/video/addVideo";
|
||||
import route66 from "./routes/video/addVideoConfig";
|
||||
import route67 from "./routes/video/deleteVideoConfig";
|
||||
import route68 from "./routes/video/generatePrompt";
|
||||
import route69 from "./routes/video/generateVideo";
|
||||
import route70 from "./routes/video/getManufacturer";
|
||||
import route71 from "./routes/video/getVideo";
|
||||
import route72 from "./routes/video/getVideoConfigs";
|
||||
import route73 from "./routes/video/getVideoModel";
|
||||
import route74 from "./routes/video/getVideoStoryboards";
|
||||
import route75 from "./routes/video/reviseVideoStoryboards";
|
||||
import route76 from "./routes/video/saveVideo";
|
||||
import route77 from "./routes/video/upDateVideoConfig";
|
||||
import route3 from "./routes/assets/delAssetsImage";
|
||||
import route4 from "./routes/assets/generateAssets";
|
||||
import route5 from "./routes/assets/getAssets";
|
||||
import route6 from "./routes/assets/getImage";
|
||||
import route7 from "./routes/assets/getStoryboard";
|
||||
import route8 from "./routes/assets/polishPrompt";
|
||||
import route9 from "./routes/assets/saveAssets";
|
||||
import route10 from "./routes/assets/updateAssets";
|
||||
import route11 from "./routes/index/index";
|
||||
import route12 from "./routes/novel/addNovel";
|
||||
import route13 from "./routes/novel/delNovel";
|
||||
import route14 from "./routes/novel/getNovel";
|
||||
import route15 from "./routes/novel/updateNovel";
|
||||
import route16 from "./routes/other/clearDatabase";
|
||||
import route17 from "./routes/other/deleteAllData";
|
||||
import route18 from "./routes/other/getCaptcha";
|
||||
import route19 from "./routes/other/login";
|
||||
import route20 from "./routes/other/testAI";
|
||||
import route21 from "./routes/other/testImage";
|
||||
import route22 from "./routes/other/testVideo";
|
||||
import route23 from "./routes/outline/addOutline";
|
||||
import route24 from "./routes/outline/agentsOutline";
|
||||
import route25 from "./routes/outline/delOutline";
|
||||
import route26 from "./routes/outline/getHistory";
|
||||
import route27 from "./routes/outline/getOutline";
|
||||
import route28 from "./routes/outline/getPartScript";
|
||||
import route29 from "./routes/outline/getStoryline";
|
||||
import route30 from "./routes/outline/setHistory";
|
||||
import route31 from "./routes/outline/updateOutline";
|
||||
import route32 from "./routes/outline/updateScript";
|
||||
import route33 from "./routes/outline/updateStoryline";
|
||||
import route34 from "./routes/project/addProject";
|
||||
import route35 from "./routes/project/delProject";
|
||||
import route36 from "./routes/project/getProject";
|
||||
import route37 from "./routes/project/getProjectCount";
|
||||
import route38 from "./routes/project/getSingleProject";
|
||||
import route39 from "./routes/project/updateProject";
|
||||
import route40 from "./routes/prompt/getPrompts";
|
||||
import route41 from "./routes/prompt/updatePrompt";
|
||||
import route42 from "./routes/script/generateScriptApi";
|
||||
import route43 from "./routes/script/generateScriptSave";
|
||||
import route44 from "./routes/script/geScriptApi";
|
||||
import route45 from "./routes/setting/addModel";
|
||||
import route46 from "./routes/setting/configurationModel";
|
||||
import route47 from "./routes/setting/delModel";
|
||||
import route48 from "./routes/setting/getAiModelList";
|
||||
import route49 from "./routes/setting/getAiModelMap";
|
||||
import route50 from "./routes/setting/getLog";
|
||||
import route51 from "./routes/setting/getSetting";
|
||||
import route52 from "./routes/setting/getVideoModelDetail";
|
||||
import route53 from "./routes/setting/getVideoModelList";
|
||||
import route54 from "./routes/setting/updateModel";
|
||||
import route55 from "./routes/setting/updeteModel";
|
||||
import route56 from "./routes/storyboard/batchSuperScoreImage";
|
||||
import route57 from "./routes/storyboard/chatStoryboard";
|
||||
import route58 from "./routes/storyboard/delStoryboard";
|
||||
import route59 from "./routes/storyboard/generateShotImage";
|
||||
import route60 from "./routes/storyboard/generateStoryboardApi";
|
||||
import route61 from "./routes/storyboard/generateVideoPrompt";
|
||||
import route62 from "./routes/storyboard/getStoryboard";
|
||||
import route63 from "./routes/storyboard/keepStoryboard";
|
||||
import route64 from "./routes/storyboard/saveStoryboard";
|
||||
import route65 from "./routes/storyboard/uploadImage";
|
||||
import route66 from "./routes/task/getTaskApi";
|
||||
import route67 from "./routes/task/taskDetails";
|
||||
import route68 from "./routes/user/getUser";
|
||||
import route69 from "./routes/user/saveUser";
|
||||
import route70 from "./routes/video/addVideo";
|
||||
import route71 from "./routes/video/addVideoConfig";
|
||||
import route72 from "./routes/video/deleteVideoConfig";
|
||||
import route73 from "./routes/video/generatePrompt";
|
||||
import route74 from "./routes/video/generateVideo";
|
||||
import route75 from "./routes/video/getManufacturer";
|
||||
import route76 from "./routes/video/getVideo";
|
||||
import route77 from "./routes/video/getVideoConfigs";
|
||||
import route78 from "./routes/video/getVideoModel";
|
||||
import route79 from "./routes/video/getVideoStoryboards";
|
||||
import route80 from "./routes/video/reviseVideoStoryboards";
|
||||
import route81 from "./routes/video/saveVideo";
|
||||
import route82 from "./routes/video/upDateVideoConfig";
|
||||
|
||||
export default async (app: Express) => {
|
||||
app.use("/assets/addAssets", route1);
|
||||
app.use("/assets/delAssets", route2);
|
||||
app.use("/assets/generateAssets", route3);
|
||||
app.use("/assets/getAssets", route4);
|
||||
app.use("/assets/getImage", route5);
|
||||
app.use("/assets/getStoryboard", route6);
|
||||
app.use("/assets/polishPrompt", route7);
|
||||
app.use("/assets/saveAssets", route8);
|
||||
app.use("/assets/updateAssets", route9);
|
||||
app.use("/index", route10);
|
||||
app.use("/novel/addNovel", route11);
|
||||
app.use("/novel/delNovel", route12);
|
||||
app.use("/novel/getNovel", route13);
|
||||
app.use("/novel/updateNovel", route14);
|
||||
app.use("/other/clearDatabase", route15);
|
||||
app.use("/other/deleteAllData", route16);
|
||||
app.use("/other/getCaptcha", route17);
|
||||
app.use("/other/login", route18);
|
||||
app.use("/other/testAI", route19);
|
||||
app.use("/other/testImage", route20);
|
||||
app.use("/other/testVideo", route21);
|
||||
app.use("/outline/addOutline", route22);
|
||||
app.use("/outline/agentsOutline", route23);
|
||||
app.use("/outline/delOutline", route24);
|
||||
app.use("/outline/getHistory", route25);
|
||||
app.use("/outline/getOutline", route26);
|
||||
app.use("/outline/getPartScript", route27);
|
||||
app.use("/outline/getStoryline", route28);
|
||||
app.use("/outline/setHistory", route29);
|
||||
app.use("/outline/updateOutline", route30);
|
||||
app.use("/outline/updateScript", route31);
|
||||
app.use("/outline/updateStoryline", route32);
|
||||
app.use("/project/addProject", route33);
|
||||
app.use("/project/delProject", route34);
|
||||
app.use("/project/getProject", route35);
|
||||
app.use("/project/getProjectCount", route36);
|
||||
app.use("/project/getSingleProject", route37);
|
||||
app.use("/project/updateProject", route38);
|
||||
app.use("/prompt/getPrompts", route39);
|
||||
app.use("/prompt/updatePrompt", route40);
|
||||
app.use("/script/generateScriptApi", route41);
|
||||
app.use("/script/generateScriptSave", route42);
|
||||
app.use("/script/geScriptApi", route43);
|
||||
app.use("/setting/addModel", route44);
|
||||
app.use("/setting/configurationModel", route45);
|
||||
app.use("/setting/delModel", route46);
|
||||
app.use("/setting/getAiModelMap", route47);
|
||||
app.use("/setting/getLog", route48);
|
||||
app.use("/setting/getSetting", route49);
|
||||
app.use("/setting/getVideoModelList", route50);
|
||||
app.use("/setting/updateModel", route51);
|
||||
app.use("/setting/updeteModel", route52);
|
||||
app.use("/storyboard/batchSuperScoreImage", route53);
|
||||
app.use("/storyboard/chatStoryboard", route54);
|
||||
app.use("/storyboard/generateShotImage", route55);
|
||||
app.use("/storyboard/generateStoryboardApi", route56);
|
||||
app.use("/storyboard/generateVideoPrompt", route57);
|
||||
app.use("/storyboard/getStoryboard", route58);
|
||||
app.use("/storyboard/keepStoryboard", route59);
|
||||
app.use("/storyboard/saveStoryboard", route60);
|
||||
app.use("/storyboard/uploadImage", route61);
|
||||
app.use("/task/getTaskApi", route62);
|
||||
app.use("/task/taskDetails", route63);
|
||||
app.use("/user/getUser", route64);
|
||||
app.use("/video/addVideo", route65);
|
||||
app.use("/video/addVideoConfig", route66);
|
||||
app.use("/video/deleteVideoConfig", route67);
|
||||
app.use("/video/generatePrompt", route68);
|
||||
app.use("/video/generateVideo", route69);
|
||||
app.use("/video/getManufacturer", route70);
|
||||
app.use("/video/getVideo", route71);
|
||||
app.use("/video/getVideoConfigs", route72);
|
||||
app.use("/video/getVideoModel", route73);
|
||||
app.use("/video/getVideoStoryboards", route74);
|
||||
app.use("/video/reviseVideoStoryboards", route75);
|
||||
app.use("/video/saveVideo", route76);
|
||||
app.use("/video/upDateVideoConfig", route77);
|
||||
app.use("/assets/delAssetsImage", route3);
|
||||
app.use("/assets/generateAssets", route4);
|
||||
app.use("/assets/getAssets", route5);
|
||||
app.use("/assets/getImage", route6);
|
||||
app.use("/assets/getStoryboard", route7);
|
||||
app.use("/assets/polishPrompt", route8);
|
||||
app.use("/assets/saveAssets", route9);
|
||||
app.use("/assets/updateAssets", route10);
|
||||
app.use("/index", route11);
|
||||
app.use("/novel/addNovel", route12);
|
||||
app.use("/novel/delNovel", route13);
|
||||
app.use("/novel/getNovel", route14);
|
||||
app.use("/novel/updateNovel", route15);
|
||||
app.use("/other/clearDatabase", route16);
|
||||
app.use("/other/deleteAllData", route17);
|
||||
app.use("/other/getCaptcha", route18);
|
||||
app.use("/other/login", route19);
|
||||
app.use("/other/testAI", route20);
|
||||
app.use("/other/testImage", route21);
|
||||
app.use("/other/testVideo", route22);
|
||||
app.use("/outline/addOutline", route23);
|
||||
app.use("/outline/agentsOutline", route24);
|
||||
app.use("/outline/delOutline", route25);
|
||||
app.use("/outline/getHistory", route26);
|
||||
app.use("/outline/getOutline", route27);
|
||||
app.use("/outline/getPartScript", route28);
|
||||
app.use("/outline/getStoryline", route29);
|
||||
app.use("/outline/setHistory", route30);
|
||||
app.use("/outline/updateOutline", route31);
|
||||
app.use("/outline/updateScript", route32);
|
||||
app.use("/outline/updateStoryline", route33);
|
||||
app.use("/project/addProject", route34);
|
||||
app.use("/project/delProject", route35);
|
||||
app.use("/project/getProject", route36);
|
||||
app.use("/project/getProjectCount", route37);
|
||||
app.use("/project/getSingleProject", route38);
|
||||
app.use("/project/updateProject", route39);
|
||||
app.use("/prompt/getPrompts", route40);
|
||||
app.use("/prompt/updatePrompt", route41);
|
||||
app.use("/script/generateScriptApi", route42);
|
||||
app.use("/script/generateScriptSave", route43);
|
||||
app.use("/script/geScriptApi", route44);
|
||||
app.use("/setting/addModel", route45);
|
||||
app.use("/setting/configurationModel", route46);
|
||||
app.use("/setting/delModel", route47);
|
||||
app.use("/setting/getAiModelList", route48);
|
||||
app.use("/setting/getAiModelMap", route49);
|
||||
app.use("/setting/getLog", route50);
|
||||
app.use("/setting/getSetting", route51);
|
||||
app.use("/setting/getVideoModelDetail", route52);
|
||||
app.use("/setting/getVideoModelList", route53);
|
||||
app.use("/setting/updateModel", route54);
|
||||
app.use("/setting/updeteModel", route55);
|
||||
app.use("/storyboard/batchSuperScoreImage", route56);
|
||||
app.use("/storyboard/chatStoryboard", route57);
|
||||
app.use("/storyboard/delStoryboard", route58);
|
||||
app.use("/storyboard/generateShotImage", route59);
|
||||
app.use("/storyboard/generateStoryboardApi", route60);
|
||||
app.use("/storyboard/generateVideoPrompt", route61);
|
||||
app.use("/storyboard/getStoryboard", route62);
|
||||
app.use("/storyboard/keepStoryboard", route63);
|
||||
app.use("/storyboard/saveStoryboard", route64);
|
||||
app.use("/storyboard/uploadImage", route65);
|
||||
app.use("/task/getTaskApi", route66);
|
||||
app.use("/task/taskDetails", route67);
|
||||
app.use("/user/getUser", route68);
|
||||
app.use("/user/saveUser", route69);
|
||||
app.use("/video/addVideo", route70);
|
||||
app.use("/video/addVideoConfig", route71);
|
||||
app.use("/video/deleteVideoConfig", route72);
|
||||
app.use("/video/generatePrompt", route73);
|
||||
app.use("/video/generateVideo", route74);
|
||||
app.use("/video/getManufacturer", route75);
|
||||
app.use("/video/getVideo", route76);
|
||||
app.use("/video/getVideoConfigs", route77);
|
||||
app.use("/video/getVideoModel", route78);
|
||||
app.use("/video/getVideoStoryboards", route79);
|
||||
app.use("/video/reviseVideoStoryboards", route80);
|
||||
app.use("/video/saveVideo", route81);
|
||||
app.use("/video/upDateVideoConfig", route82);
|
||||
}
|
||||
|
||||
27
src/routes/assets/delAssetsImage.ts
Normal file
27
src/routes/assets/delAssetsImage.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
// 删除资产图片
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
imageId: z.number().optional(),
|
||||
assetsId: z.number().optional(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { imageId, assetsId } = req.body;
|
||||
if (assetsId) {
|
||||
await u.db("t_assets").where("id", assetsId).update({
|
||||
filePath: null,
|
||||
});
|
||||
}
|
||||
if (imageId) {
|
||||
await u.db("t_image").where("id", imageId).delete();
|
||||
}
|
||||
res.status(200).send(success({ message: "删除资产图片成功" }));
|
||||
},
|
||||
);
|
||||
@ -2,7 +2,7 @@ import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { error, success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import sharp from "sharp";
|
||||
const router = express.Router();
|
||||
@ -125,56 +125,68 @@ export default router.post(
|
||||
});
|
||||
const apiConfig = await u.getPromptAi("assetsImage");
|
||||
|
||||
const contentStr = await u.ai.image(
|
||||
{
|
||||
systemPrompt,
|
||||
prompt: userPrompt,
|
||||
imageBase64: base64 ? [base64] : [],
|
||||
size: "2K",
|
||||
aspectRatio: "16:9",
|
||||
},
|
||||
apiConfig,
|
||||
);
|
||||
try {
|
||||
const contentStr = await u.ai.image(
|
||||
{
|
||||
systemPrompt,
|
||||
prompt: userPrompt,
|
||||
imageBase64: base64 ? [base64] : [],
|
||||
size: "2K",
|
||||
aspectRatio: "16:9",
|
||||
},
|
||||
apiConfig,
|
||||
);
|
||||
|
||||
let insertType;
|
||||
const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/);
|
||||
let buffer = Buffer.from(match && match.length >= 2 ? match[1]! : contentStr!, "base64");
|
||||
let insertType;
|
||||
const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/);
|
||||
let buffer = Buffer.from(match && match.length >= 2 ? match[1]! : contentStr!, "base64");
|
||||
|
||||
if (type != "storyboard") {
|
||||
//添加文本
|
||||
// buffer = await imageAddText(name, buffer);
|
||||
if (type != "storyboard") {
|
||||
//添加文本
|
||||
// buffer = await imageAddText(name, buffer);
|
||||
}
|
||||
let imagePath;
|
||||
if (type == "role") {
|
||||
insertType = "角色";
|
||||
imagePath = `/${projectId}/role/${uuidv4()}.jpg`;
|
||||
}
|
||||
if (type == "scene") {
|
||||
insertType = "场景";
|
||||
imagePath = `/${projectId}/scene/${uuidv4()}.jpg`;
|
||||
}
|
||||
if (type == "props") {
|
||||
insertType = "道具";
|
||||
imagePath = `/${projectId}/props/${uuidv4()}.jpg`;
|
||||
}
|
||||
if (type == "storyboard") {
|
||||
insertType = "分镜";
|
||||
imagePath = `/${projectId}/storyboard/${uuidv4()}.jpg`;
|
||||
}
|
||||
|
||||
await u.oss.writeFile(imagePath!, buffer);
|
||||
const imageData = await u.db("t_image").where("id", imageId).select("*").first();
|
||||
if (imageData) {
|
||||
await u.db("t_image").where("id", imageId).update({
|
||||
state: "生成成功",
|
||||
filePath: imagePath,
|
||||
type: insertType,
|
||||
});
|
||||
|
||||
const path = await u.oss.getFileUrl(imagePath!);
|
||||
|
||||
// const state = await u.db("t_assets").where("id", id).select("state").first();
|
||||
|
||||
return res.status(200).send(success({ path, assetsId: id }));
|
||||
} else {
|
||||
return res.status(500).send("资产已被删除");
|
||||
}
|
||||
} catch (e) {
|
||||
await u.db("t_image").where("id", imageId).update({
|
||||
state: "生成失败",
|
||||
});
|
||||
const msg = u.error(e).message || "图片生成失败";
|
||||
return res.status(400).send(error(msg));
|
||||
}
|
||||
let imagePath;
|
||||
if (type == "role") {
|
||||
insertType = "角色";
|
||||
imagePath = `/${projectId}/role/${uuidv4()}.jpg`;
|
||||
}
|
||||
if (type == "scene") {
|
||||
insertType = "场景";
|
||||
imagePath = `/${projectId}/scene/${uuidv4()}.jpg`;
|
||||
}
|
||||
if (type == "props") {
|
||||
insertType = "道具";
|
||||
imagePath = `/${projectId}/props/${uuidv4()}.jpg`;
|
||||
}
|
||||
if (type == "storyboard") {
|
||||
insertType = "分镜";
|
||||
imagePath = `/${projectId}/storyboard/${uuidv4()}.jpg`;
|
||||
}
|
||||
|
||||
await u.oss.writeFile(imagePath!, buffer);
|
||||
|
||||
await u.db("t_image").where("id", imageId).update({
|
||||
state: "生成成功",
|
||||
filePath: imagePath,
|
||||
type: insertType,
|
||||
});
|
||||
|
||||
const path = await u.oss.getFileUrl(imagePath!);
|
||||
|
||||
// const state = await u.db("t_assets").where("id", id).select("state").first();
|
||||
|
||||
res.status(200).send(success({ path, assetsId: id }));
|
||||
},
|
||||
);
|
||||
async function imageAddText(name: string, imageBuffer: Buffer) {
|
||||
|
||||
@ -27,6 +27,7 @@ export default router.post(
|
||||
resolution: "720p",
|
||||
aspectRatio: "16:9",
|
||||
audio: false,
|
||||
mode: "single",
|
||||
},
|
||||
{
|
||||
model: modelName,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { error, success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
import { generateScript } from "@/utils/generateScript";
|
||||
const router = express.Router();
|
||||
@ -43,14 +43,18 @@ export default router.post(
|
||||
if (novelData.length == 0) return res.status(500).send(success({ message: "原文为空" }));
|
||||
|
||||
const result: string = mergeNovelText(novelData);
|
||||
try {
|
||||
const data = await generateScript(parameter ?? "", result ?? "");
|
||||
if (!data) return res.status(500).send({ message: "生成剧本失败" });
|
||||
|
||||
const data = await generateScript(parameter ?? "", result ?? "");
|
||||
if (!data) return res.status(500).send({ message: "生成剧本失败" });
|
||||
await u.db("t_script").where("id", scriptId).update({
|
||||
content: data,
|
||||
});
|
||||
|
||||
await u.db("t_script").where("id", scriptId).update({
|
||||
content: data,
|
||||
});
|
||||
|
||||
res.status(200).send(success({ message: "生成剧本成功" }));
|
||||
res.status(200).send(success({ message: "生成剧本成功" }));
|
||||
} catch (e) {
|
||||
const errMsg = u.error(e).message || "生成剧本失败";
|
||||
res.status(500).send(error(errMsg));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
33
src/routes/setting/getAiModelList.ts
Normal file
33
src/routes/setting/getAiModelList.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
type: z.enum(["text", "image", "video"]),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { type } = req.body;
|
||||
const sqlTableMap = {
|
||||
text: "t_textModel",
|
||||
image: "t_imageModel",
|
||||
video: "t_videoModel",
|
||||
};
|
||||
const modelLists = await u
|
||||
.db(sqlTableMap[type as "image" | "text" | "video"])
|
||||
.whereNot("manufacturer", "other")
|
||||
.select("id", "manufacturer", "model");
|
||||
const result: Record<string, any[]> = {};
|
||||
for (const row of modelLists) {
|
||||
if (!result[row.manufacturer]) {
|
||||
result[row.manufacturer] = [];
|
||||
}
|
||||
result[row.manufacturer].push({ label: row.model, value: row.model });
|
||||
}
|
||||
res.status(200).send(success(result));
|
||||
},
|
||||
);
|
||||
31
src/routes/setting/getVideoModelDetail.ts
Normal file
31
src/routes/setting/getVideoModelDetail.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post("/", async (req, res) => {
|
||||
const videoData = await u.db("t_videoModel").select("*");
|
||||
const allData = videoData.map((i) => {
|
||||
const durationResolutionMap = JSON.parse(i.durationResolutionMap ?? "[]");
|
||||
const aspectRatio = JSON.parse(i.aspectRatio ?? "[]");
|
||||
const type = JSON.parse(i.type ?? "[]");
|
||||
return {
|
||||
...i,
|
||||
durationResolutionMap,
|
||||
aspectRatio,
|
||||
type,
|
||||
audio: i.audio === 1,
|
||||
};
|
||||
});
|
||||
|
||||
const otherConfig = {
|
||||
manufacturer: "other",
|
||||
model: "",
|
||||
durationResolutionMap: [{ duration: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], resolution: ["480p", "720p", "1080p"] }],
|
||||
aspectRatio: ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9"],
|
||||
type: ["text", "endFrameOptional", "singleImage", "multiImage"],
|
||||
audio: true,
|
||||
};
|
||||
const returnData = [otherConfig, ...allData];
|
||||
res.status(200).send(success(returnData));
|
||||
});
|
||||
18
src/routes/storyboard/delStoryboard.ts
Normal file
18
src/routes/storyboard/delStoryboard.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
id: z.number(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { id } = req.body;
|
||||
await u.db("t_assets").where("id", id).delete();
|
||||
res.status(200).send(success("分镜删除成功"));
|
||||
},
|
||||
);
|
||||
@ -16,8 +16,10 @@ export default router.post(
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { filePath, prompt, projectId, assetsId } = req.body;
|
||||
//拿到图片尺寸
|
||||
const projectInfo = await u.db("t_project").where({ id: projectId }).first();
|
||||
|
||||
let data = await u.editImage(filePath, prompt, projectId);
|
||||
let data = await u.editImage(filePath, prompt, projectId,projectInfo?.videoRatio!);
|
||||
const returnData: {
|
||||
id: number | null;
|
||||
url: string | null;
|
||||
|
||||
24
src/routes/user/saveUser.ts
Normal file
24
src/routes/user/saveUser.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import express from "express";
|
||||
import u from "@/utils";
|
||||
import { z } from "zod";
|
||||
import { success } from "@/lib/responseFormat";
|
||||
import { validateFields } from "@/middleware/middleware";
|
||||
const router = express.Router();
|
||||
|
||||
// 获取用户
|
||||
export default router.post(
|
||||
"/",
|
||||
validateFields({
|
||||
name: z.string(),
|
||||
password: z.string(),
|
||||
id: z.number(),
|
||||
}),
|
||||
async (req, res) => {
|
||||
const { name, password, id } = req.body;
|
||||
await u.db("t_user").where("id", id).update({
|
||||
name,
|
||||
password,
|
||||
});
|
||||
res.status(200).send(success("保存设置成功"));
|
||||
},
|
||||
);
|
||||
@ -33,9 +33,9 @@ export default router.post(
|
||||
if (result.filePath) {
|
||||
filesToDelete.push(result.filePath);
|
||||
}
|
||||
if (result.firstFrame) {
|
||||
filesToDelete.push(result.firstFrame);
|
||||
}
|
||||
// if (result.firstFrame) {
|
||||
// filesToDelete.push(result.firstFrame);
|
||||
// }
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
|
||||
@ -119,7 +119,7 @@ export default router.post(
|
||||
res.status(200).send(success({ id: videoId, configId: configId || null }));
|
||||
|
||||
// 异步生成视频
|
||||
generateVideoAsync(videoId, projectId, fileUrl, savePath, prompt, duration, resolution, audioEnabled, aiConfigData);
|
||||
generateVideoAsync(videoId, projectId, fileUrl, savePath, prompt, duration, resolution, audioEnabled, aiConfigData, mode);
|
||||
},
|
||||
);
|
||||
|
||||
@ -134,6 +134,7 @@ async function generateVideoAsync(
|
||||
resolution: string,
|
||||
audioEnabled: boolean,
|
||||
aiConfigData: t_config,
|
||||
mode: string,
|
||||
) {
|
||||
try {
|
||||
const projectData = await u.db("t_project").where("id", projectId).select("artStyle", "videoRatio").first();
|
||||
@ -175,6 +176,7 @@ ${prompt}
|
||||
aspectRatio: projectData?.videoRatio as any,
|
||||
resolution: resolution as any,
|
||||
audio: audioEnabled,
|
||||
mode: mode as any,
|
||||
},
|
||||
{
|
||||
baseURL: aiConfigData?.baseUrl!,
|
||||
|
||||
31
src/types/database.d.ts
vendored
31
src/types/database.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
// @db-hash 945540586ca016b1b9a42042fc5ccbf3
|
||||
// @db-hash 0f9789bd5ad2eebd79bd502988efcb4e
|
||||
//该文件由脚本自动生成,请勿手动修改
|
||||
|
||||
export interface t_aiModelMap {
|
||||
@ -52,6 +52,13 @@ export interface t_image {
|
||||
'type'?: string | null;
|
||||
'videoId'?: number | null;
|
||||
}
|
||||
export interface t_imageModel {
|
||||
'grid'?: number | null;
|
||||
'id'?: number;
|
||||
'manufacturer'?: string | null;
|
||||
'model'?: string | null;
|
||||
'type'?: string | null;
|
||||
}
|
||||
export interface t_novel {
|
||||
'chapter'?: string | null;
|
||||
'chapterData'?: string | null;
|
||||
@ -117,6 +124,15 @@ export interface t_taskList {
|
||||
'startTime'?: string | null;
|
||||
'state'?: string | null;
|
||||
}
|
||||
export interface t_textModel {
|
||||
'id'?: number;
|
||||
'image'?: number | null;
|
||||
'manufacturer'?: string | null;
|
||||
'model'?: string | null;
|
||||
'responseFormat'?: string | null;
|
||||
'think'?: number | null;
|
||||
'tool'?: number | null;
|
||||
}
|
||||
export interface t_user {
|
||||
'id'?: number;
|
||||
'name'?: string | null;
|
||||
@ -143,7 +159,6 @@ export interface t_videoConfig {
|
||||
'createTime'?: number | null;
|
||||
'duration'?: number | null;
|
||||
'endFrame'?: string | null;
|
||||
'errorReason'?: string | null;
|
||||
'id'?: number;
|
||||
'images'?: string | null;
|
||||
'manufacturer'?: string | null;
|
||||
@ -156,6 +171,15 @@ export interface t_videoConfig {
|
||||
'startFrame'?: string | null;
|
||||
'updateTime'?: number | null;
|
||||
}
|
||||
export interface t_videoModel {
|
||||
'aspectRatio'?: string | null;
|
||||
'audio'?: number | null;
|
||||
'durationResolutionMap'?: string | null;
|
||||
'id'?: number;
|
||||
'manufacturer'?: string | null;
|
||||
'model'?: string | null;
|
||||
'type'?: string | null;
|
||||
}
|
||||
|
||||
export interface DB {
|
||||
"t_aiModelMap": t_aiModelMap;
|
||||
@ -163,6 +187,7 @@ export interface DB {
|
||||
"t_chatHistory": t_chatHistory;
|
||||
"t_config": t_config;
|
||||
"t_image": t_image;
|
||||
"t_imageModel": t_imageModel;
|
||||
"t_novel": t_novel;
|
||||
"t_outline": t_outline;
|
||||
"t_project": t_project;
|
||||
@ -171,7 +196,9 @@ export interface DB {
|
||||
"t_setting": t_setting;
|
||||
"t_storyline": t_storyline;
|
||||
"t_taskList": t_taskList;
|
||||
"t_textModel": t_textModel;
|
||||
"t_user": t_user;
|
||||
"t_video": t_video;
|
||||
"t_videoConfig": t_videoConfig;
|
||||
"t_videoModel": t_videoModel;
|
||||
}
|
||||
|
||||
@ -10,6 +10,8 @@ import runninghub from "./owned/runninghub";
|
||||
import apimart from "./owned/apimart";
|
||||
import other from "./owned/other";
|
||||
import gemini from "./owned/gemini";
|
||||
import modelScope from "./owned/modelScope";
|
||||
import grsai from "./owned/grsai";
|
||||
|
||||
const urlToBase64 = async (url: string): Promise<string> => {
|
||||
const res = await axios.get(url, { responseType: "arraybuffer" });
|
||||
@ -25,20 +27,21 @@ const modelInstance = {
|
||||
vidu: vidu,
|
||||
runninghub: runninghub,
|
||||
// apimart: apimart,
|
||||
modelScope,
|
||||
other,
|
||||
grsai
|
||||
} as const;
|
||||
|
||||
export default async (input: ImageConfig, config: AIConfig) => {
|
||||
console.log("%c Line:32 🥪 config", "background:#33a5ff", config);
|
||||
const { model, apiKey, baseURL, manufacturer } = { ...config };
|
||||
if (!config || !config?.model || !config?.apiKey || !config?.manufacturer) throw new Error("请检查模型配置是否正确");
|
||||
|
||||
const manufacturerFn = modelInstance[manufacturer as keyof typeof modelInstance];
|
||||
if (!manufacturerFn) if (!manufacturerFn) throw new Error("不支持的图片厂商");
|
||||
if (manufacturer !== "other") {
|
||||
const owned = modelList.find((m) => m.model === model);
|
||||
if (!owned) throw new Error("不支持的模型");
|
||||
}
|
||||
// if (manufacturer !== "other") {
|
||||
// const owned = modelList.find((m) => m.model === model);
|
||||
// if (!owned) throw new Error("不支持的模型");
|
||||
// }
|
||||
|
||||
// 补充图片的 base64 内容类型字符串
|
||||
if (input.imageBase64 && input.imageBase64.length > 0) {
|
||||
@ -65,7 +68,6 @@ export default async (input: ImageConfig, config: AIConfig) => {
|
||||
}
|
||||
|
||||
let imageUrl = await manufacturerFn(input, { model, apiKey, baseURL });
|
||||
console.log("%c Line:68 🍷 imageUrl", "background:#4fff4B", imageUrl);
|
||||
if (!input.resType) input.resType = "b64";
|
||||
if (input.resType === "b64" && imageUrl.startsWith("http")) imageUrl = await urlToBase64(imageUrl);
|
||||
return imageUrl;
|
||||
|
||||
@ -59,13 +59,6 @@ const modelList: Owned[] = [
|
||||
grid: true,
|
||||
type: "ti2i",
|
||||
},
|
||||
//ApiMart
|
||||
{
|
||||
manufacturer: "apimart",
|
||||
model: "nanobanana",
|
||||
grid: true,
|
||||
type: "ti2i",
|
||||
},
|
||||
];
|
||||
|
||||
export default modelList;
|
||||
|
||||
92
src/utils/ai/image/owned/grsai.ts
Normal file
92
src/utils/ai/image/owned/grsai.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import axios from "axios";
|
||||
import u from "@/utils";
|
||||
import { pollTask } from "@/utils/ai/utils";
|
||||
function getApiUrl(apiUrl: string) {
|
||||
if (apiUrl.includes("|")) {
|
||||
const parts = apiUrl.split("|");
|
||||
if (parts.length !== 2 || !parts[0].trim() || !parts[1].trim()) {
|
||||
throw new Error("url 格式错误,请使用 url1|url2 格式");
|
||||
}
|
||||
return { requestUrl: parts[0].trim(), queryUrl: parts[1].trim() };
|
||||
}
|
||||
throw new Error("请填写正确的url");
|
||||
}
|
||||
function template(replaceObj: Record<string, any>, url: string) {
|
||||
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
||||
return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match;
|
||||
});
|
||||
}
|
||||
async function processImages(imageBase64: string[]): Promise<Array<string>> {
|
||||
let images = imageBase64.filter((img) => img?.trim());
|
||||
if (images.length === 0) return [];
|
||||
|
||||
// 压缩所有图片到10MB以内
|
||||
images = await Promise.all(images.map((img) => u.imageTools.compressImage(img, "10mb")));
|
||||
|
||||
// 参考主体数量和参考图片数量之和不得超过10
|
||||
if (images.length > 6) {
|
||||
const mergeImageList = images.splice(5);
|
||||
const mergedImage = await u.imageTools.mergeImages(mergeImageList, "10mb");
|
||||
images.push(mergedImage);
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
export default async (input: ImageConfig, config: AIConfig): Promise<string> => {
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||
|
||||
const defaultBaseURL = "https://grsai.dakka.com.cn/v1/draw/nano-banana|https://grsai.dakka.com.cn/v1/draw/result";
|
||||
|
||||
const { requestUrl, queryUrl } = getApiUrl(config.baseURL! || defaultBaseURL);
|
||||
// 构建完整的提示词
|
||||
const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt;
|
||||
|
||||
let mergedImage = await processImages(input.imageBase64 || []);
|
||||
|
||||
const taskBody: Record<string, any> = {
|
||||
model: config.model,
|
||||
prompt: fullPrompt,
|
||||
imageSize: input.size,
|
||||
aspectRatio: input.aspectRatio,
|
||||
...(mergedImage && mergedImage.length ? { urls: mergedImage } : {}),
|
||||
webHook: "-1",
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
const { data } = await axios.post(requestUrl, taskBody, { headers: { Authorization: `Bearer ${apiKey}` } });
|
||||
|
||||
|
||||
if (data.code != 0) throw new Error(`任务提交失败: ${data ? JSON.stringify(data, null, 2) : "未知错误"}`);
|
||||
|
||||
return await pollTask(async () => {
|
||||
const { data: queryData } = await axios.post(
|
||||
queryUrl,
|
||||
{
|
||||
id: data.data.id,
|
||||
},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
},
|
||||
);
|
||||
|
||||
if (queryData.code != 0) throw new Error(`查询任务失败: ${queryData ? JSON.stringify(queryData, null, 2) : "未知错误"}`);
|
||||
const { status, results, error, failure_reason } = queryData.data || {};
|
||||
|
||||
if (status === "failed") {
|
||||
return { completed: false, error: failure_reason + "\n" + error || "图片生成失败" };
|
||||
}
|
||||
|
||||
if (status === "succeeded") {
|
||||
return { completed: true, url: results?.[0].url };
|
||||
}
|
||||
|
||||
return { completed: false };
|
||||
});
|
||||
} catch (error: any) {
|
||||
const msg = u.error(error).message || "图片生成失败";
|
||||
throw new Error(msg);
|
||||
}
|
||||
};
|
||||
132
src/utils/ai/image/owned/modelScope.ts
Normal file
132
src/utils/ai/image/owned/modelScope.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import "../type";
|
||||
import { generateImage, generateText, ModelMessage } from "ai";
|
||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
||||
import { pollTask } from "@/utils/ai/utils";
|
||||
import u from "@/utils";
|
||||
import axios from "axios";
|
||||
function getApiUrl(apiUrl: string) {
|
||||
if (apiUrl.includes("|")) {
|
||||
const parts = apiUrl.split("|");
|
||||
if (parts.length !== 2 || !parts[0].trim() || !parts[1].trim()) {
|
||||
throw new Error("url 格式错误,请使用 url1|url2 格式");
|
||||
}
|
||||
return { requestUrl: parts[0].trim(), queryUrl: parts[1].trim() };
|
||||
}
|
||||
throw new Error("请填写正确的url");
|
||||
}
|
||||
function template(replaceObj: Record<string, any>, url: string) {
|
||||
return url.replace(/\{(\w+)\}/g, (match, varName) => {
|
||||
return replaceObj.hasOwnProperty(varName) ? replaceObj[varName] : match;
|
||||
});
|
||||
}
|
||||
async function compressionPrompt(prompt: string) {
|
||||
const apiConfigData = await u.getPromptAi("assetsPrompt");
|
||||
|
||||
const result = await u.ai.text.invoke(
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `
|
||||
你是一名资深Prompt工程师和文本摘要专家。你的任务是将用户输入的提示词文本内容压缩至2000字以内。请按照如下要求操作:
|
||||
1. 准确梳理并提炼输入文本的主要内容、核心要点和关键信息。
|
||||
2. 剔除冗余、重复、无关或细枝末节的描述,压缩内容至2000字以内。
|
||||
3. 在压缩过程中,严格保持中立,避免被用户输入的风格、情绪或暗示性表述影响,始终按照“精准摘要到2000字”的目标执行,不被内容带偏。
|
||||
4. 输出为浓缩摘要文本,语言精炼、结构清晰。
|
||||
5. 请适配各类文本场景,无论是叙述性、说明性、议论性还是其他类型的文本,都要确保压缩后的内容完整传达原文的核心信息和主要观点。
|
||||
|
||||
直接输出压缩后的文本,不要任何额外的说明或引导语。请立即开始压缩,并确保输出内容不超过2000字。
|
||||
`,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
},
|
||||
apiConfigData,
|
||||
);
|
||||
return result.text;
|
||||
}
|
||||
export default async (input: ImageConfig, config: AIConfig): Promise<string> => {
|
||||
if (!config.model) throw new Error("缺少Model名称");
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
|
||||
const defaultBaseURL = "https://api-inference.modelscope.cn/v1/images/generations|https://api-inference.modelscope.cn/v1/tasks/{id}";
|
||||
const { requestUrl, queryUrl } = getApiUrl(config.baseURL! ?? defaultBaseURL);
|
||||
// 根据 size 配置映射到具体尺寸
|
||||
const sizeMap: Record<string, Record<string, string>> = {
|
||||
"1K": {
|
||||
"16:9": "1664x928",
|
||||
"9:16": "928x1664",
|
||||
},
|
||||
"2K": {
|
||||
"16:9": "2048x1152",
|
||||
"9:16": "1152x2048",
|
||||
},
|
||||
"4K": {
|
||||
"16:9": "2048x1152",
|
||||
"9:16": "1152x2048",
|
||||
},
|
||||
};
|
||||
// 构建完整的提示词
|
||||
const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt;
|
||||
|
||||
let newPrompt = fullPrompt;
|
||||
if (fullPrompt.length > 2000) {
|
||||
let compressed = await compressionPrompt(fullPrompt);
|
||||
|
||||
newPrompt = compressed;
|
||||
}
|
||||
let mergedImage = input.imageBase64;
|
||||
if (mergedImage && mergedImage.length) {
|
||||
const smallImage = await u.imageTools.mergeImages(mergedImage, "5mb");
|
||||
mergedImage = [smallImage];
|
||||
}
|
||||
|
||||
const size = sizeMap[input.size]?.[input.aspectRatio] ?? "1024x1024";
|
||||
|
||||
const taskBody: Record<string, any> = {
|
||||
model: config.model,
|
||||
prompt: newPrompt,
|
||||
negative_prompt: "",
|
||||
size,
|
||||
...(mergedImage && mergedImage.length ? { image_url: mergedImage } : {}),
|
||||
};
|
||||
|
||||
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||
try {
|
||||
const { data } = await axios.post(requestUrl, taskBody, { headers: { Authorization: `Bearer ${apiKey}`, "X-ModelScope-Async-Mode": "true" } });
|
||||
|
||||
if (data.task_status != "SUCCEED") throw new Error(`任务提交失败: ${data || "未知错误"}`);
|
||||
const taskId = data.task_id;
|
||||
|
||||
return await pollTask(async () => {
|
||||
const { data: queryData } = await axios.get(template({ id: taskId }, queryUrl), {
|
||||
headers: { Authorization: `Bearer ${apiKey}`, "X-ModelScope-Task-Type": "image_generation" },
|
||||
});
|
||||
|
||||
const { task_status, output_images } = queryData || {};
|
||||
|
||||
if (task_status === "FAILED") {
|
||||
return { completed: false, error: "图片生成失败" };
|
||||
}
|
||||
|
||||
if (task_status === "SUCCEED") {
|
||||
return { completed: true, url: output_images?.[0] };
|
||||
}
|
||||
|
||||
return { completed: false };
|
||||
});
|
||||
} catch (error: any) {
|
||||
const msg = u.error(error).message || "图片生成失败";
|
||||
throw new Error(msg);
|
||||
}
|
||||
};
|
||||
|
||||
async function urlToBase64(url: string): Promise<string> {
|
||||
const res = await axios.get(url, { responseType: "arraybuffer" });
|
||||
const base64 = Buffer.from(res.data).toString("base64");
|
||||
const mimeType = res.headers["content-type"] || "image/png";
|
||||
return `data:${mimeType};base64,${base64}`;
|
||||
}
|
||||
@ -69,26 +69,28 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
console.error(JSON.stringify(result.response, null, 2));
|
||||
throw new Error("图片生成失败");
|
||||
}
|
||||
const mdMatch = result.text.match(/^!\[.*?\]\((.+?)\)$/);
|
||||
if (mdMatch) {
|
||||
const imgInfo = mdMatch[1];
|
||||
const base64InMd = imgInfo.match(/data:image\/[a-z]+;base64,(.+)/);
|
||||
// 匹配所有 markdown 图片 
|
||||
const mdImgPattern = /!\[.*?\]\((.+?)\)/g;
|
||||
const matches = [...result.text.matchAll(mdImgPattern)];
|
||||
for (const match of matches) {
|
||||
const imgInfo = match[1];
|
||||
// 检查是否已是 base64
|
||||
const base64InMd = imgInfo.match(/data:image\/[a-z]+;base64,.+/);
|
||||
if (base64InMd) {
|
||||
return imgInfo;
|
||||
return imgInfo; // 已经是base64,直接返回
|
||||
} else {
|
||||
return await urlToBase64(imgInfo);
|
||||
return await urlToBase64(imgInfo); // 否则尝试转base64
|
||||
}
|
||||
}
|
||||
// 检查纯base64字符串
|
||||
const base64Match = result.text.match(/base64,([A-Za-z0-9+/=]+)/);
|
||||
|
||||
if (base64Match) {
|
||||
return "data:image/jpeg;base64," + base64Match[1];
|
||||
}
|
||||
// 检查是否为图片直链 url
|
||||
// 检查是否为图片直链接
|
||||
if (/^https?:\/\/.*\.(png|jpg|jpeg|gif|webp|bmp)$/i.test(result.text)) {
|
||||
return await urlToBase64(result.text);
|
||||
}
|
||||
|
||||
// 默认情况
|
||||
return result.text;
|
||||
}
|
||||
|
||||
@ -68,11 +68,12 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||
const baseURL = "https://www.runninghub.cn";
|
||||
const imageUrls = await Promise.all(input.imageBase64.map((base64Image) => uploadBase64ToRunninghub(base64Image, apiKey, baseURL)));
|
||||
const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt;
|
||||
|
||||
const endpoint = input.imageBase64.length === 0 ? "/openapi/v2/rhart-image-n-pro/text-to-image" : "/openapi/v2/rhart-image-n-pro/edit";
|
||||
const taskRes = await axios.post(
|
||||
`https://www.runninghub.cn${endpoint}`,
|
||||
{ prompt: input.prompt, resolution: input.size, aspectRatio: input.aspectRatio, ...(imageUrls.length > 0 && { imageUrls }) },
|
||||
{ prompt: fullPrompt, resolution: input.size, aspectRatio: input.aspectRatio, ...(imageUrls.length > 0 && { imageUrls }) },
|
||||
{ headers: { Authorization: "Bearer " + apiKey } },
|
||||
);
|
||||
const taskId = taskRes.data.taskId;
|
||||
|
||||
@ -8,18 +8,28 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
|
||||
const apiKey = "Bearer " + config.apiKey.replace(/Bearer\s+/g, "").trim();
|
||||
const size = input.size === "1K" ? "2K" : input.size;
|
||||
const sizeMap: Record<string, Record<string, string>> = {
|
||||
"16:9": {
|
||||
"2K": "2848x1600",
|
||||
"4K": "4096x2304",
|
||||
},
|
||||
"9:16": {
|
||||
"2K": "1600x2848",
|
||||
"4K": "2304x4096",
|
||||
},
|
||||
};
|
||||
const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt;
|
||||
|
||||
const body: Record<string, any> = {
|
||||
model: config.model,
|
||||
prompt: input.prompt,
|
||||
size,
|
||||
prompt: fullPrompt,
|
||||
size: sizeMap[input.aspectRatio][size],
|
||||
response_format: "url",
|
||||
sequential_image_generation: "disabled",
|
||||
stream: false,
|
||||
watermark: false,
|
||||
...(input.imageBase64 && { image: input.imageBase64 }),
|
||||
};
|
||||
|
||||
const url = config.baseURL ?? "https://ark.cn-beijing.volces.com/api/v3/images/generations";
|
||||
try {
|
||||
const { data } = await axios.post(url, body, { headers: { Authorization: apiKey } });
|
||||
@ -28,4 +38,4 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
||||
const msg = u.error(error).message || "Volcengine 图片生成失败";
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import { generateText, streamText, Output, stepCountIs, ModelMessage, LanguageMo
|
||||
import { wrapLanguageModel } from "ai";
|
||||
import { devToolsMiddleware } from "@ai-sdk/devtools";
|
||||
import { parse } from "best-effort-json-parser";
|
||||
import modelList from "./modelList";
|
||||
import { getModelList } from "./modelList";
|
||||
import { z } from "zod";
|
||||
import { OpenAIProvider } from "@ai-sdk/openai";
|
||||
interface AIInput<T extends Record<string, z.ZodTypeAny> | undefined = undefined> {
|
||||
@ -26,12 +26,14 @@ const buildOptions = async (input: AIInput<any>, config: AIConfig = {}) => {
|
||||
if (!config || !config?.model || !config?.apiKey || !config?.manufacturer) throw new Error("请检查模型配置是否正确");
|
||||
const { model, apiKey, baseURL, manufacturer } = { ...config };
|
||||
let owned;
|
||||
const modelList = await getModelList();
|
||||
if (manufacturer == "other") {
|
||||
owned = modelList.find((m) => m.manufacturer === manufacturer);
|
||||
} else {
|
||||
owned = modelList.find((m) => m.model === model);
|
||||
owned = modelList.find((m) => m.model === model && m.manufacturer === manufacturer);
|
||||
if (!owned) owned = modelList.find((m) => m.manufacturer === manufacturer);
|
||||
}
|
||||
if (!owned) throw new Error("不支持的模型或厂商");
|
||||
if (!owned) throw new Error("不支持的厂商");
|
||||
|
||||
const modelInstance = owned.instance({ apiKey, baseURL: baseURL!, name: "xixixi" });
|
||||
|
||||
@ -52,8 +54,9 @@ const buildOptions = async (input: AIInput<any>, config: AIConfig = {}) => {
|
||||
};
|
||||
|
||||
const output = input.output ? (outputBuilders[owned.responseFormat]?.(input.output) ?? null) : null;
|
||||
const chatModelManufacturer = ["doubao", "other", "openai"];
|
||||
const chatModelManufacturer = ["volcengine", "other", "openai", "modelScope","grsai"];
|
||||
const modelFn = chatModelManufacturer.includes(owned.manufacturer) ? (modelInstance as OpenAIProvider).chat(model!) : modelInstance(model!);
|
||||
|
||||
return {
|
||||
config: {
|
||||
model: modelFn as LanguageModel,
|
||||
@ -76,6 +79,7 @@ const ai = Object.create({}) as {
|
||||
|
||||
ai.invoke = async (input: AIInput<any>, config: AIConfig) => {
|
||||
const options = await buildOptions(input, config);
|
||||
|
||||
const result = await generateText(options.config);
|
||||
if (options.responseFormat === "object" && input.output) {
|
||||
const pattern = /{[^{}]*}|{(?:[^{}]*|{[^{}]*})*}/g;
|
||||
@ -92,6 +96,7 @@ ai.invoke = async (input: AIInput<any>, config: AIConfig) => {
|
||||
|
||||
ai.stream = async (input: AIInput, config: AIConfig) => {
|
||||
const options = await buildOptions(input, config);
|
||||
|
||||
return streamText(options.config);
|
||||
};
|
||||
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
import { createOpenAI, OpenAIProviderSettings } from "@ai-sdk/openai";
|
||||
import { createDeepSeek } from "@ai-sdk/deepseek";
|
||||
import { createZhipu } from "zhipu-ai-provider";
|
||||
import { createQwen } from "qwen-ai-provider";
|
||||
import { createQwen } from "qwen-ai-provider-v5";
|
||||
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
||||
import { createAnthropic } from "@ai-sdk/anthropic";
|
||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
||||
import { createXai } from '@ai-sdk/xai';
|
||||
import { createXai } from "@ai-sdk/xai";
|
||||
import db from "@/utils/db";
|
||||
|
||||
interface Owned {
|
||||
manufacturer: string;
|
||||
@ -23,11 +24,23 @@ interface Owned {
|
||||
| typeof createAnthropic
|
||||
| typeof createOpenAICompatible;
|
||||
}
|
||||
|
||||
const instanceMap = {
|
||||
deepSeek: createDeepSeek,
|
||||
volcengine: createOpenAI,
|
||||
openai: createOpenAI,
|
||||
zhipu: createZhipu,
|
||||
qwen: createQwen,
|
||||
gemini: createGoogleGenerativeAI,
|
||||
anthropic: createAnthropic,
|
||||
modelScope: (options: OpenAIProviderSettings) => createOpenAI({ ...options, headers: { ...options?.headers, "X-ModelScope-Async-Mode": "true" } }),
|
||||
xai: createXai,
|
||||
other: createOpenAI,
|
||||
grsai:createOpenAI
|
||||
};
|
||||
const modelList: Owned[] = [
|
||||
// DeepSeek
|
||||
{
|
||||
manufacturer: "deepseek",
|
||||
manufacturer: "deepSeek",
|
||||
model: "deepseek-chat",
|
||||
responseFormat: "schema",
|
||||
image: false,
|
||||
@ -36,7 +49,7 @@ const modelList: Owned[] = [
|
||||
tool: true,
|
||||
},
|
||||
{
|
||||
manufacturer: "deepseek",
|
||||
manufacturer: "deepSeek",
|
||||
model: "deepseek-reasoner",
|
||||
responseFormat: "schema",
|
||||
image: false,
|
||||
@ -47,7 +60,16 @@ const modelList: Owned[] = [
|
||||
|
||||
// 豆包
|
||||
{
|
||||
manufacturer: "doubao",
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seed-2-0-mini-260215",
|
||||
responseFormat: "object",
|
||||
image: true,
|
||||
think: false,
|
||||
instance: createOpenAI,
|
||||
tool: true,
|
||||
},
|
||||
{
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seed-1-8-251228",
|
||||
responseFormat: "schema",
|
||||
image: true,
|
||||
@ -56,7 +78,7 @@ const modelList: Owned[] = [
|
||||
tool: true,
|
||||
},
|
||||
{
|
||||
manufacturer: "doubao",
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seed-1-6-251015",
|
||||
responseFormat: "schema",
|
||||
image: true,
|
||||
@ -65,7 +87,7 @@ const modelList: Owned[] = [
|
||||
tool: true,
|
||||
},
|
||||
{
|
||||
manufacturer: "doubao",
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seed-1-6-lite-251015",
|
||||
responseFormat: "schema",
|
||||
image: true,
|
||||
@ -74,7 +96,7 @@ const modelList: Owned[] = [
|
||||
tool: true,
|
||||
},
|
||||
{
|
||||
manufacturer: "doubao",
|
||||
manufacturer: "volcengine",
|
||||
model: "doubao-seed-1-6-flash-250828",
|
||||
responseFormat: "schema",
|
||||
image: true,
|
||||
@ -413,7 +435,7 @@ const modelList: Owned[] = [
|
||||
tool: true,
|
||||
},
|
||||
//xai
|
||||
{
|
||||
{
|
||||
manufacturer: "xai",
|
||||
model: "grok-3",
|
||||
responseFormat: "schema",
|
||||
@ -422,7 +444,7 @@ const modelList: Owned[] = [
|
||||
instance: createXai,
|
||||
tool: true,
|
||||
},
|
||||
{
|
||||
{
|
||||
manufacturer: "xai",
|
||||
model: "grok-4",
|
||||
responseFormat: "schema",
|
||||
@ -431,7 +453,7 @@ const modelList: Owned[] = [
|
||||
instance: createXai,
|
||||
tool: true,
|
||||
},
|
||||
{
|
||||
{
|
||||
manufacturer: "xai",
|
||||
model: "grok-4.1",
|
||||
responseFormat: "schema",
|
||||
@ -444,12 +466,24 @@ const modelList: Owned[] = [
|
||||
{
|
||||
manufacturer: "other",
|
||||
model: "gpt-4.1",
|
||||
responseFormat: "schema",
|
||||
responseFormat: "object",
|
||||
image: true,
|
||||
think: false,
|
||||
instance: createOpenAI,
|
||||
tool: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const getModelList = async () => {
|
||||
const modelLists = await db("t_textModel").select("*");
|
||||
const resultInstaceList = modelLists.map((model) => {
|
||||
return {
|
||||
...model,
|
||||
tool: model.tool == 1 ? true : false,
|
||||
think: model.think == 1 ? true : false,
|
||||
image: model.image == 1 ? true : false,
|
||||
instance: instanceMap[model.manufacturer as keyof typeof instanceMap],
|
||||
};
|
||||
});
|
||||
return resultInstaceList as Owned[];
|
||||
};
|
||||
export default modelList;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import modelList from "./video/modelList";
|
||||
|
||||
import { db } from "../db";
|
||||
interface ValidateResult {
|
||||
owned: (typeof modelList)[number];
|
||||
images: string[];
|
||||
@ -80,3 +80,5 @@ export const pollTask = async (
|
||||
}
|
||||
throw new Error(`任务轮询超时,已尝试 ${maxAttempts} 次`);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import runninghub from "./owned/runninghub";
|
||||
import gemini from "./owned/gemini";
|
||||
import apimart from "./owned/apimart";
|
||||
import other from "./owned/other";
|
||||
|
||||
import grsai from "./owned/grsai";
|
||||
const modelInstance = {
|
||||
volcengine: volcengine,
|
||||
kling: kling,
|
||||
@ -20,7 +20,8 @@ const modelInstance = {
|
||||
gemini: gemini,
|
||||
runninghub: runninghub,
|
||||
apimart: apimart,
|
||||
// other: other,
|
||||
other: other,
|
||||
grsai:grsai
|
||||
} as const;
|
||||
|
||||
export default async (input: VideoConfig, config?: AIConfig) => {
|
||||
@ -29,8 +30,8 @@ export default async (input: VideoConfig, config?: AIConfig) => {
|
||||
|
||||
const manufacturerFn = modelInstance[manufacturer as keyof typeof modelInstance];
|
||||
if (!manufacturerFn) if (!manufacturerFn) throw new Error("不支持的视频厂商");
|
||||
const owned = modelList.find((m) => m.model === model);
|
||||
if (!owned) throw new Error("不支持的模型");
|
||||
// const owned = modelList.find((m) => m.model === model);
|
||||
// if (!owned) throw new Error("不支持的模型");
|
||||
|
||||
// 补充图片的 base64 内容类型字符串
|
||||
if (input.imageBase64 && input.imageBase64.length > 0) {
|
||||
|
||||
@ -10,7 +10,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
if (!config.model) throw new Error("缺少Model名称");
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
|
||||
const { owned, images, hasStartEndType } = validateVideoConfig(input, config);
|
||||
// const { owned, images, hasStartEndType } = validateVideoConfig(input, config);
|
||||
|
||||
const defaultBaseUrl = [
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/{model}:predictLongRunning",
|
||||
@ -18,8 +18,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
].join("|");
|
||||
|
||||
const [submitUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|");
|
||||
|
||||
|
||||
|
||||
const headers = { "x-goog-api-key": config.apiKey };
|
||||
|
||||
const instance: Record<string, any> = { prompt: input.prompt };
|
||||
@ -30,17 +29,17 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
};
|
||||
|
||||
// 根据图片数量和模型能力决定图片用法
|
||||
const len = images.length;
|
||||
const hasRef = owned.type.includes("reference");
|
||||
const hasSingle = owned.type.includes("singleImage");
|
||||
|
||||
const len = input.imageBase64 ? input.imageBase64.length : 0;
|
||||
const hasRef = input.mode == "multi";
|
||||
const hasSingle = input.mode == "single";
|
||||
const hasStartEndType = input.mode === "startEnd";
|
||||
if (len === 2 && hasStartEndType) {
|
||||
instance.image = buildInlineImage(images[0]);
|
||||
parameters.lastFrame = buildInlineImage(images[1]);
|
||||
instance.image = buildInlineImage(input.imageBase64![0]);
|
||||
parameters.lastFrame = buildInlineImage(input.imageBase64![1]);
|
||||
} else if (len === 1 && (hasSingle || hasStartEndType)) {
|
||||
instance.image = buildInlineImage(images[0]);
|
||||
instance.image = buildInlineImage(input.imageBase64![0]);
|
||||
} else if (len >= 1 && len <= 3 && hasRef) {
|
||||
parameters.referenceImages = images.map((img) => ({ image: buildInlineImage(img), referenceType: "asset" }));
|
||||
parameters.referenceImages = input.imageBase64!.map((img) => ({ image: buildInlineImage(img), referenceType: "asset" }));
|
||||
}
|
||||
|
||||
const { data } = await axios.post(
|
||||
@ -53,15 +52,14 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
|
||||
return pollTask(async () => {
|
||||
const { data: status } = await axios.get(queryUrl.replace("{name}", data.name), { headers });
|
||||
|
||||
|
||||
const { done, response, error } = status;
|
||||
|
||||
|
||||
if (!done) return { completed: false };
|
||||
if (error) return { completed: false, error: `任务失败: ${error.message || JSON.stringify(error)}` };
|
||||
|
||||
const videoUri = response?.generateVideoResponse?.generatedSamples?.[0]?.video?.uri;
|
||||
|
||||
|
||||
if (!videoUri) return { completed: false, error: "未获取到视频下载地址" };
|
||||
|
||||
const videoRes = await axios.get(videoUri, { headers, responseType: "arraybuffer", maxRedirects: 5 });
|
||||
|
||||
76
src/utils/ai/video/owned/grsai.ts
Normal file
76
src/utils/ai/video/owned/grsai.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import "../type";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import axios from "axios";
|
||||
import { pollTask, validateVideoConfig } from "@/utils/ai/utils";
|
||||
|
||||
const buildInlineImage = (data: string) => ({ inlineData: { mimeType: "image/png", data } });
|
||||
|
||||
export default async (input: VideoConfig, config: AIConfig) => {
|
||||
if (!config.model) throw new Error("缺少Model名称");
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
|
||||
// const { owned, images, hasStartEndType } = validateVideoConfig(input, config);
|
||||
|
||||
const defaultBaseUrl = ["https://grsai.dakka.com.cn/v1/video/{model}", "https://grsai.dakka.com.cn/v1/draw/result"].join("|");
|
||||
|
||||
const [submitUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|");
|
||||
|
||||
const headers = { Authorization: "Bearer " + config.apiKey };
|
||||
let inputObj: Record<string, any> = {};
|
||||
let taskUrl = submitUrl.replace("{model}", config.model);
|
||||
if (config.model.includes("veo")) {
|
||||
inputObj = {
|
||||
model: config.model,
|
||||
prompt: input.prompt,
|
||||
firstFrameUrl: "",
|
||||
lastFrameUrl: "",
|
||||
aspectRatio: input.aspectRatio,
|
||||
webHook: "-1",
|
||||
};
|
||||
inputObj.firstFrameUrl = input.imageBase64?.[0] ?? "";
|
||||
inputObj.lastFrameUrl = input.imageBase64?.[1] ?? "";
|
||||
|
||||
taskUrl = submitUrl.replace("{model}", "veo");
|
||||
} else {
|
||||
inputObj = {
|
||||
model: config.model,
|
||||
prompt: input.prompt,
|
||||
url: "",
|
||||
aspectRatio: input.aspectRatio,
|
||||
duration: +input.duration,
|
||||
webHook: "-1",
|
||||
};
|
||||
inputObj.url = input.imageBase64?.[0] ?? "";
|
||||
|
||||
taskUrl = submitUrl.replace("{model}", "sora-video");
|
||||
}
|
||||
|
||||
const { data } = await axios.post(taskUrl, { ...inputObj }, { headers: { ...headers, "Content-Type": "application/json" } });
|
||||
|
||||
if (data.code != 0) throw new Error(`任务提交失败: ${data ? JSON.stringify(data, null, 2) : "未知错误"}`);
|
||||
|
||||
return await pollTask(async () => {
|
||||
const { data: queryData } = await axios.post(
|
||||
queryUrl,
|
||||
{
|
||||
id: data.data.id,
|
||||
},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${config.apiKey}` },
|
||||
},
|
||||
);
|
||||
if (queryData.code != 0) throw new Error(`查询任务失败: ${queryData ? JSON.stringify(queryData, null, 2) : "未知错误"}`);
|
||||
const { status, error, failure_reason, url } = queryData.data || {};
|
||||
|
||||
if (status === "failed") {
|
||||
return { completed: false, error: failure_reason + "\n" + error || "图片生成失败" };
|
||||
}
|
||||
|
||||
if (status === "succeeded") {
|
||||
return { completed: true, url: url };
|
||||
}
|
||||
|
||||
return { completed: false };
|
||||
});
|
||||
};
|
||||
@ -6,7 +6,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
if (!config.baseURL) throw new Error("缺少baseURL配置");
|
||||
|
||||
const { images } = validateVideoConfig(input, config);
|
||||
// const { images } = validateVideoConfig(input, config);
|
||||
|
||||
// 解析URL配置:图生视频|文生视频|查询地址
|
||||
const defaultBaseUrl =
|
||||
@ -24,7 +24,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
const mode = modelMatch ? (modelMatch[2].toLowerCase() as "std" | "pro") : "std";
|
||||
|
||||
// 判断是图生视频还是文生视频
|
||||
const hasImage = images.length > 0;
|
||||
const hasImage = input.imageBase64 ? input.imageBase64.length > 0 : false;
|
||||
const createUrl = hasImage ? image2videoUrl : text2videoUrl;
|
||||
|
||||
// 去除图片的内容类型前缀(kling要求纯base64)
|
||||
@ -41,9 +41,9 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
|
||||
if (hasImage) {
|
||||
// 图生视频:首帧和尾帧
|
||||
body.image = stripDataUrl(images[0]);
|
||||
if (images.length > 1) {
|
||||
body.image_tail = stripDataUrl(images[1]);
|
||||
body.image = stripDataUrl(input.imageBase64![0]);
|
||||
if (input.imageBase64!.length > 1) {
|
||||
body.image_tail = stripDataUrl(input.imageBase64![1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,12 +4,11 @@ import sharp from "sharp";
|
||||
import FormData from "form-data";
|
||||
import { pollTask, validateVideoConfig } from "@/utils/ai/utils";
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
|
||||
import { experimental_generateVideo as generateVideo } from "ai";
|
||||
export default async (input: VideoConfig, config: AIConfig) => {
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
if (!config.baseURL) throw new Error("缺少baseURL");
|
||||
// const { owned, images, hasTextType } = validateVideoConfig(input, config);
|
||||
|
||||
const [requestUrl, queryUrl] = config.baseURL.split("|");
|
||||
|
||||
const authorization = `Bearer ${config.apiKey}`;
|
||||
@ -21,30 +20,40 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
|
||||
// 根据 aspectRatio 设置 size
|
||||
const sizeMap: Record<string, string> = {
|
||||
"16:9": "1920x1080",
|
||||
"9:16": "1080x1920",
|
||||
"16:9": "1280x720",
|
||||
"9:16": "720x1280",
|
||||
};
|
||||
formData.append("size", sizeMap[input.aspectRatio] || "1920x1080");
|
||||
|
||||
if (input.imageBase64 && input.imageBase64.length) {
|
||||
const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, "");
|
||||
const buffer = Buffer.from(base64Data, "base64");
|
||||
formData.append("input_reference", buffer, { filename: "image.jpg", contentType: "image/jpeg" });
|
||||
}
|
||||
const { data } = await axios.post(requestUrl, formData, {
|
||||
headers: { "Content-Type": "application/json", Authorization: authorization, ...formData.getHeaders() },
|
||||
});
|
||||
if (data.status === "FAILED") throw new Error(`任务提交失败: ${data.errorMessage || "未知错误"}`);
|
||||
const taskId = data.id;
|
||||
return await pollTask(async () => {
|
||||
const { data } = await axios.get(`${queryUrl.replace("{id}", taskId)}`, {
|
||||
headers: { Authorization: authorization },
|
||||
});
|
||||
|
||||
if (data.status === "SUCCESS") {
|
||||
return data.results?.length ? { completed: true, url: data.results[0].url } : { completed: false, error: "任务成功但未返回视频链接" };
|
||||
}
|
||||
if (data.status === "FAILED") return { completed: false, error: `任务失败: ${data.errorMessage || "未知错误"}` };
|
||||
if (data.status === "QUEUED" || data.status === "RUNNING") return { completed: false };
|
||||
return { completed: false, error: `未知状态: ${data.status}` };
|
||||
});
|
||||
const body = {
|
||||
model: config.model,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: input.prompt,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const { data } = await axios.post(
|
||||
config.baseURL,
|
||||
{ ...body },
|
||||
{
|
||||
headers: { "Content-Type": "application/json", Authorization: authorization },
|
||||
},
|
||||
);
|
||||
|
||||
console.log("%c Line:49 🥓 data", "background:#ffdd4d", data);
|
||||
|
||||
if (data.status === "FAILED") throw new Error(`任务提交失败: ${data.errorMessage || "未知错误"}`);
|
||||
};
|
||||
|
||||
@ -7,7 +7,7 @@ import { pollTask, validateVideoConfig } from "@/utils/ai/utils";
|
||||
export default async (input: VideoConfig, config: AIConfig) => {
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
|
||||
const { owned, images, hasTextType } = validateVideoConfig(input, config);
|
||||
// const { owned, images, hasTextType } = validateVideoConfig(input, config);
|
||||
|
||||
const defaultBaseUrl = [
|
||||
"https://www.runninghub.cn/openapi/v2/rhart-video-s/image-to-video",
|
||||
@ -19,8 +19,8 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
].join("|");
|
||||
|
||||
const [image2videoUrl, image2videoProUrl, text2videoUrl, text2videoProUrl, queryUrl, uploadUrl] = (config.baseURL || defaultBaseUrl).split("|");
|
||||
|
||||
const isPro = owned.model === "sora-2-pro";
|
||||
const hasTextType = input.mode == "text";
|
||||
const isPro = config.model === "sora-2-pro";
|
||||
const authorization = `Bearer ${config.apiKey}`;
|
||||
|
||||
// 上传 base64 图片
|
||||
@ -65,20 +65,19 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
return { taskId: data.taskId, status: data.status, url: data.results?.[0]?.url };
|
||||
};
|
||||
|
||||
const isTextToVideo = images.length === 0 && hasTextType;
|
||||
const isTextToVideo = (!input.imageBase64 || input.imageBase64.length === 0) && hasTextType;
|
||||
const submitUrl = isTextToVideo ? (isPro ? text2videoProUrl : text2videoUrl) : isPro ? image2videoProUrl : image2videoUrl;
|
||||
|
||||
const requestBody: Record<string, unknown> = {
|
||||
prompt: input.prompt,
|
||||
duration: String(input.duration),
|
||||
aspectRatio: input.aspectRatio,
|
||||
...(isTextToVideo ? {} : { imageUrl: await uploadImage(images[0]) }),
|
||||
...(isTextToVideo ? {} : { imageUrl: await uploadImage(input.imageBase64![0]) }),
|
||||
};
|
||||
|
||||
const { taskId } = await submitTask(submitUrl, requestBody);
|
||||
|
||||
return await pollTask(async () => {
|
||||
|
||||
const { data } = await axios.post(
|
||||
queryUrl,
|
||||
{
|
||||
|
||||
@ -20,30 +20,30 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
const hasImages = input.imageBase64 && input.imageBase64.length > 0;
|
||||
|
||||
// 根据是否有图片,查找匹配的模型配置
|
||||
const customOwned = modelList.find((m) => {
|
||||
if (m.manufacturer !== "vidu") return false;
|
||||
if (m.model !== config.model) return false;
|
||||
if (hasImages) {
|
||||
return m.type.some((t) => t !== "text");
|
||||
} else {
|
||||
return m.type.includes("text");
|
||||
}
|
||||
});
|
||||
// const customOwned = modelList.find((m) => {
|
||||
// if (m.manufacturer !== "vidu") return false;
|
||||
// if (m.model !== config.model) return false;
|
||||
// if (hasImages) {
|
||||
// return m.type.some((t) => t !== "text");
|
||||
// } else {
|
||||
// return m.type.includes("text");
|
||||
// }
|
||||
// });
|
||||
|
||||
if (!customOwned) {
|
||||
throw new Error(`未找到匹配的模型配置: ${config.model}`);
|
||||
}
|
||||
// if (!customOwned) {
|
||||
// throw new Error(`未找到匹配的模型配置: ${config.model}`);
|
||||
// }
|
||||
|
||||
// 使用统一校验函数
|
||||
const { owned, images } = validateVideoConfig(input, config, customOwned);
|
||||
// const { owned, images } = validateVideoConfig(input, config, customOwned);
|
||||
|
||||
// 判断生成类型
|
||||
const genType: "text" | "image" = images.length === 0 ? "text" : "image";
|
||||
const genType: "text" | "image" = input.imageBase64 && input.imageBase64.length === 0 ? "text" : "image";
|
||||
|
||||
// 校验宽高比(仅文生视频需要)
|
||||
if (genType === "text" && owned.aspectRatio.length > 0 && !owned.aspectRatio.includes(input.aspectRatio as `${number}:${number}`)) {
|
||||
throw new Error(`模型 ${owned.model} 不支持宽高比 ${input.aspectRatio},支持的宽高比:${owned.aspectRatio.join("、")}`);
|
||||
}
|
||||
// // 校验宽高比(仅文生视频需要)
|
||||
// if (genType === "text" && owned.aspectRatio.length > 0 && !owned.aspectRatio.includes(input.aspectRatio as `${number}:${number}`)) {
|
||||
// throw new Error(`模型 ${owned.model} 不支持宽高比 ${input.aspectRatio},支持的宽高比:${owned.aspectRatio.join("、")}`);
|
||||
// }
|
||||
|
||||
// 创建任务
|
||||
let taskId: string;
|
||||
@ -51,13 +51,13 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
if (genType === "text") {
|
||||
// 文生视频
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model: owned.model,
|
||||
model: config.model,
|
||||
prompt: input.prompt,
|
||||
duration: input.duration,
|
||||
resolution: input.resolution,
|
||||
aspect_ratio: input.aspectRatio,
|
||||
};
|
||||
if (owned.audio && input.audio !== undefined) {
|
||||
if (input.audio && input.audio !== undefined) {
|
||||
requestBody.audio = input.audio;
|
||||
}
|
||||
|
||||
@ -71,15 +71,15 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
} else {
|
||||
// 图生视频
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model: owned.model,
|
||||
images: images,
|
||||
model: config.model,
|
||||
images: input.imageBase64,
|
||||
duration: input.duration,
|
||||
resolution: input.resolution,
|
||||
};
|
||||
if (input.prompt) {
|
||||
requestBody.prompt = input.prompt;
|
||||
}
|
||||
if (owned.audio && input.audio !== undefined) {
|
||||
if (input.audio && input.audio !== undefined) {
|
||||
requestBody.audio = input.audio;
|
||||
}
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@ import { pollTask, validateVideoConfig } from "@/utils/ai/utils";
|
||||
export default async (input: VideoConfig, config: AIConfig) => {
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
|
||||
const { owned, images, hasStartEndType } = validateVideoConfig(input, config);
|
||||
|
||||
// const { owned, images, hasStartEndType } = validateVideoConfig(input, config);
|
||||
const hasStartEndType = input.mode === "startEnd";
|
||||
const authorization = "Bearer " + config.apiKey.replace(/^Bearer\s*/i, "").trim();
|
||||
const baseUrl = config.baseURL ?? "https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks";
|
||||
|
||||
const images = input.imageBase64 || [];
|
||||
// 判断是否为首尾帧模式(需要两张图且类型支持首尾帧)
|
||||
const isStartEndMode = images.length === 2 && hasStartEndType;
|
||||
|
||||
@ -35,7 +35,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
};
|
||||
|
||||
// 仅当模型支持音频时才添加 generate_audio 字段
|
||||
if (owned.audio) {
|
||||
if (input?.audio) {
|
||||
requestBody.generate_audio = input.audio ?? false;
|
||||
}
|
||||
// 创建视频生成任务
|
||||
@ -52,11 +52,11 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
|
||||
// 轮询任务状态
|
||||
return await pollTask(async () => {
|
||||
const { status, content } = (
|
||||
await axios.get(`${baseUrl}/${taskId}`, {
|
||||
headers: { Authorization: authorization },
|
||||
})
|
||||
).data;
|
||||
const data = await axios.get(`${baseUrl}/${taskId}`, {
|
||||
headers: { Authorization: authorization },
|
||||
});
|
||||
|
||||
const { status, content } = data.data;
|
||||
|
||||
switch (status) {
|
||||
case "succeeded":
|
||||
|
||||
@ -39,8 +39,10 @@ const getSizeFromConfig = (resolution: string, aspectRatio: string): string => {
|
||||
export default async (input: VideoConfig, config: AIConfig) => {
|
||||
if (!config.apiKey) throw new Error("缺少API Key");
|
||||
|
||||
const { owned, images, hasStartEndType, hasTextType } = validateVideoConfig(input, config);
|
||||
|
||||
// const { owned, images, hasStartEndType, hasTextType } = validateVideoConfig(input, config);
|
||||
const hasStartEndType = input.mode === "startEnd";
|
||||
const hasTextType = input.mode === "text";
|
||||
const images = input.imageBase64 || [];
|
||||
const defaultBaseUrl = [
|
||||
"https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis",
|
||||
"https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis",
|
||||
@ -49,7 +51,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
|
||||
const [i2vUrl, kf2vUrl, queryUrl] = (config.baseURL || defaultBaseUrl).split("|");
|
||||
|
||||
const types = owned.type;
|
||||
// const types = owned.type;
|
||||
const authorization = `Bearer ${config.apiKey}`;
|
||||
|
||||
// 确定端点和构建请求体
|
||||
@ -69,7 +71,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
duration: input.duration,
|
||||
},
|
||||
};
|
||||
} else if (types.includes("singleImage")) {
|
||||
} else if (input.mode == 'single' && images.length === 1) {
|
||||
// 图生视频
|
||||
submitUrl = i2vUrl;
|
||||
body = {
|
||||
@ -84,7 +86,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
},
|
||||
};
|
||||
// audio参数仅部分模型支持
|
||||
if (owned.audio && input.audio !== undefined) {
|
||||
if (input.audio && input.audio !== undefined) {
|
||||
body.parameters.audio = input.audio;
|
||||
}
|
||||
} else if (hasStartEndType) {
|
||||
@ -95,9 +97,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
first_frame_url: images[0],
|
||||
};
|
||||
// 尾帧处理
|
||||
if (types.includes("startEndRequired")) {
|
||||
inputObj.last_frame_url = images[1];
|
||||
} else if ((types.includes("endFrameOptional") || types.includes("startFrameOptional")) && images.length >= 2) {
|
||||
if (hasStartEndType && images.length >= 2) {
|
||||
inputObj.last_frame_url = images[1];
|
||||
}
|
||||
body = {
|
||||
@ -109,7 +109,7 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
||||
},
|
||||
};
|
||||
} else {
|
||||
throw new Error(`不支持的视频生成类型: ${types.join(", ")}`);
|
||||
throw new Error(`不支持的视频生成类型: ${hasStartEndType ? "startEnd" : hasTextType ? "text" : "single"}`);
|
||||
}
|
||||
|
||||
// 提交任务
|
||||
|
||||
@ -6,6 +6,7 @@ interface VideoConfig {
|
||||
savePath: string;
|
||||
imageBase64?: string[];
|
||||
audio?: boolean;
|
||||
mode: "startEnd" | "multi" | "single" | "text";
|
||||
}
|
||||
|
||||
interface AIConfig {
|
||||
|
||||
@ -77,7 +77,7 @@ async function convertDirectiveAndImages(images: Record<string, string>, directi
|
||||
* "将@图10中圈起来的部分换成@图8"
|
||||
* );
|
||||
*/
|
||||
export default async (images: Record<string, string>, directive: string, projectId: number) => {
|
||||
export default async (images: Record<string, string>, directive: string, projectId: number, aspectRatio: string | null) => {
|
||||
const { prompt, images: base64Images } = await convertDirectiveAndImages(images, directive);
|
||||
const apiConfig = await u.getPromptAi("editImage");
|
||||
|
||||
@ -86,7 +86,7 @@ export default async (images: Record<string, string>, directive: string, project
|
||||
systemPrompt: "根据用户提供的具体修改指令,对上传的图片进行智能编辑。",
|
||||
prompt: prompt,
|
||||
imageBase64: base64Images,
|
||||
aspectRatio: "16:9",
|
||||
aspectRatio: aspectRatio ? aspectRatio : "16:9",
|
||||
size: "1K",
|
||||
},
|
||||
apiConfig,
|
||||
|
||||
50
yarn.lock
50
yarn.lock
@ -74,15 +74,6 @@
|
||||
"@standard-schema/spec" "^1.1.0"
|
||||
eventsource-parser "^3.0.6"
|
||||
|
||||
"@ai-sdk/provider-utils@^2.1.6":
|
||||
version "2.2.8"
|
||||
resolved "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz#ad11b92d5a1763ab34ba7b5fc42494bfe08b76d1"
|
||||
integrity sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==
|
||||
dependencies:
|
||||
"@ai-sdk/provider" "1.1.3"
|
||||
nanoid "^3.3.8"
|
||||
secure-json-parse "^2.7.0"
|
||||
|
||||
"@ai-sdk/provider-utils@^3.0.0":
|
||||
version "3.0.20"
|
||||
resolved "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-3.0.20.tgz#61d7741065550833eae3ac6440d943e9d3d25120"
|
||||
@ -92,12 +83,14 @@
|
||||
"@standard-schema/spec" "^1.0.0"
|
||||
eventsource-parser "^3.0.6"
|
||||
|
||||
"@ai-sdk/provider@1.1.3", "@ai-sdk/provider@^1.0.7":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-1.1.3.tgz#ebdda8077b8d2b3f290dcba32c45ad19b2704681"
|
||||
integrity sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==
|
||||
"@ai-sdk/provider-utils@^4.0.0":
|
||||
version "4.0.15"
|
||||
resolved "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.15.tgz#d585c7c89cfdf13697a40be5768ecd907a251585"
|
||||
integrity sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w==
|
||||
dependencies:
|
||||
json-schema "^0.4.0"
|
||||
"@ai-sdk/provider" "3.0.8"
|
||||
"@standard-schema/spec" "^1.1.0"
|
||||
eventsource-parser "^3.0.6"
|
||||
|
||||
"@ai-sdk/provider@2.0.1", "@ai-sdk/provider@^2.0.0":
|
||||
version "2.0.1"
|
||||
@ -113,6 +106,13 @@
|
||||
dependencies:
|
||||
json-schema "^0.4.0"
|
||||
|
||||
"@ai-sdk/provider@3.0.8", "@ai-sdk/provider@^3.0.0":
|
||||
version "3.0.8"
|
||||
resolved "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.8.tgz#fd7fac7533c03534ac1d3fb710a6b96e2aa00263"
|
||||
integrity sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==
|
||||
dependencies:
|
||||
json-schema "^0.4.0"
|
||||
|
||||
"@ai-sdk/xai@^3.0.47":
|
||||
version "3.0.47"
|
||||
resolved "https://registry.npmmirror.com/@ai-sdk/xai/-/xai-3.0.47.tgz#a8d3e08603865c5e401e19c801c7a80c3f31b890"
|
||||
@ -3160,11 +3160,6 @@ ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
|
||||
resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
nanoid@^3.3.8:
|
||||
version "3.3.11"
|
||||
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
|
||||
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
|
||||
|
||||
napi-build-utils@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
|
||||
@ -3614,13 +3609,13 @@ quick-lru@^5.1.1:
|
||||
resolved "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||
|
||||
qwen-ai-provider@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npmmirror.com/qwen-ai-provider/-/qwen-ai-provider-0.1.1.tgz#f854379514eed919fe01de20007f6238a8ad2b41"
|
||||
integrity sha512-7dVu97U7fbOGgCYdaOunC4NQqC+7Or3/Gsbx+P16+Ny4VxST7WJxfUCogQl6D2EDxIJdHGz4akHm+5fyEulmyw==
|
||||
qwen-ai-provider-v5@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmmirror.com/qwen-ai-provider-v5/-/qwen-ai-provider-v5-2.1.0.tgz#8672871135bb4a5fda32409c00b70d10637f8a50"
|
||||
integrity sha512-I+Iv45ymrez1wieZFu0n/lc/lSkbAQMlujWBCfUWBUOf6DizYfvPKaydsojXM7CU8TcqJbYJuN3ofnaxFIwBZA==
|
||||
dependencies:
|
||||
"@ai-sdk/provider" "^1.0.7"
|
||||
"@ai-sdk/provider-utils" "^2.1.6"
|
||||
"@ai-sdk/provider" "^3.0.0"
|
||||
"@ai-sdk/provider-utils" "^4.0.0"
|
||||
|
||||
range-parser@^1.2.1:
|
||||
version "1.2.1"
|
||||
@ -3846,11 +3841,6 @@ sax@^1.2.4:
|
||||
resolved "https://registry.npmmirror.com/sax/-/sax-1.4.4.tgz#f29c2bba80ce5b86f4343b4c2be9f2b96627cf8b"
|
||||
integrity sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==
|
||||
|
||||
secure-json-parse@^2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862"
|
||||
integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==
|
||||
|
||||
semver-compare@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user