接入grsai
This commit is contained in:
parent
0b1661a940
commit
bc36f6599e
@ -644,6 +644,11 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
|||||||
{ manufacturer: "vidu", model: "viduq2", grid: 0, type: "ti2i" },
|
{ manufacturer: "vidu", model: "viduq2", grid: 0, type: "ti2i" },
|
||||||
{ manufacturer: "runninghub", model: "nanobanana", grid: 1, type: "ti2i" },
|
{ manufacturer: "runninghub", model: "nanobanana", grid: 1, type: "ti2i" },
|
||||||
{ manufacturer: "modelScope", model: "Qwen/Qwen-Image", grid: 1, type: "ti2i" },
|
{ manufacturer: "modelScope", model: "Qwen/Qwen-Image", grid: 1, type: "ti2i" },
|
||||||
|
{ manufacturer: "grsai", model: "nano-banana-fast", grid: 1, type: "ti2i" },
|
||||||
|
{ manufacturer: "grsai", model: "nano-banana-pro", grid: 1, type: "ti2i" },
|
||||||
|
{ manufacturer: "grsai", model: "nano-banana", grid: 1, type: "ti2i" },
|
||||||
|
{ manufacturer: "grsai", model: "nano-banana-2", grid: 1, type: "ti2i" },
|
||||||
|
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1103,6 +1108,67 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
|
|||||||
audio: 0,
|
audio: 0,
|
||||||
type: JSON.stringify(["singleImage", "text"]),
|
type: JSON.stringify(["singleImage", "text"]),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 48,
|
||||||
|
manufacturer: "grsai",
|
||||||
|
model: "sora-2",
|
||||||
|
durationResolutionMap: JSON.stringify([{ duration: [10, 15], resolution: [] }]),
|
||||||
|
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||||
|
audio: 0,
|
||||||
|
type: JSON.stringify(["singleImage", "text"]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 49,
|
||||||
|
manufacturer: "grsai",
|
||||||
|
model: "veo3.1-pro",
|
||||||
|
durationResolutionMap: JSON.stringify([]),
|
||||||
|
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||||
|
audio: 0,
|
||||||
|
type: JSON.stringify(["startEndRequired", "text"]),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 50,
|
||||||
|
manufacturer: "grsai",
|
||||||
|
model: "veo3.1-pro-1080p",
|
||||||
|
durationResolutionMap: JSON.stringify([]),
|
||||||
|
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||||
|
audio: 0,
|
||||||
|
type: JSON.stringify(["startEndRequired", "text"]),
|
||||||
|
},{
|
||||||
|
id: 51,
|
||||||
|
manufacturer: "grsai",
|
||||||
|
model: "veo3.1-pro-4k",
|
||||||
|
durationResolutionMap: JSON.stringify([]),
|
||||||
|
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||||
|
audio: 0,
|
||||||
|
type: JSON.stringify(["startEndRequired", "text"]),
|
||||||
|
},{
|
||||||
|
id: 52,
|
||||||
|
manufacturer: "grsai",
|
||||||
|
model: "veo3.1-fast",
|
||||||
|
durationResolutionMap: JSON.stringify([]),
|
||||||
|
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||||
|
audio: 0,
|
||||||
|
type: JSON.stringify(["startEndRequired", "text"]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 53,
|
||||||
|
manufacturer: "grsai",
|
||||||
|
model: "veo3.1-fast-1080p",
|
||||||
|
durationResolutionMap: JSON.stringify([]),
|
||||||
|
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||||
|
audio: 0,
|
||||||
|
type: JSON.stringify(["startEndRequired", "text"]),
|
||||||
|
},{
|
||||||
|
id: 54,
|
||||||
|
manufacturer: "grsai",
|
||||||
|
model: "veo3.1-fast-4k",
|
||||||
|
durationResolutionMap: JSON.stringify([]),
|
||||||
|
aspectRatio: JSON.stringify(["16:9", "9:16"]),
|
||||||
|
audio: 0,
|
||||||
|
type: JSON.stringify(["startEndRequired", "text"]),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// @routes-hash e13311bd461bd8de8701383106557048
|
// @routes-hash c97cf72361299980ea4b0c43549a0de8
|
||||||
import { Express } from "express";
|
import { Express } from "express";
|
||||||
|
|
||||||
import route1 from "./routes/assets/addAssets";
|
import route1 from "./routes/assets/addAssets";
|
||||||
@ -69,19 +69,20 @@ import route65 from "./routes/storyboard/uploadImage";
|
|||||||
import route66 from "./routes/task/getTaskApi";
|
import route66 from "./routes/task/getTaskApi";
|
||||||
import route67 from "./routes/task/taskDetails";
|
import route67 from "./routes/task/taskDetails";
|
||||||
import route68 from "./routes/user/getUser";
|
import route68 from "./routes/user/getUser";
|
||||||
import route69 from "./routes/video/addVideo";
|
import route69 from "./routes/user/saveUser";
|
||||||
import route70 from "./routes/video/addVideoConfig";
|
import route70 from "./routes/video/addVideo";
|
||||||
import route71 from "./routes/video/deleteVideoConfig";
|
import route71 from "./routes/video/addVideoConfig";
|
||||||
import route72 from "./routes/video/generatePrompt";
|
import route72 from "./routes/video/deleteVideoConfig";
|
||||||
import route73 from "./routes/video/generateVideo";
|
import route73 from "./routes/video/generatePrompt";
|
||||||
import route74 from "./routes/video/getManufacturer";
|
import route74 from "./routes/video/generateVideo";
|
||||||
import route75 from "./routes/video/getVideo";
|
import route75 from "./routes/video/getManufacturer";
|
||||||
import route76 from "./routes/video/getVideoConfigs";
|
import route76 from "./routes/video/getVideo";
|
||||||
import route77 from "./routes/video/getVideoModel";
|
import route77 from "./routes/video/getVideoConfigs";
|
||||||
import route78 from "./routes/video/getVideoStoryboards";
|
import route78 from "./routes/video/getVideoModel";
|
||||||
import route79 from "./routes/video/reviseVideoStoryboards";
|
import route79 from "./routes/video/getVideoStoryboards";
|
||||||
import route80 from "./routes/video/saveVideo";
|
import route80 from "./routes/video/reviseVideoStoryboards";
|
||||||
import route81 from "./routes/video/upDateVideoConfig";
|
import route81 from "./routes/video/saveVideo";
|
||||||
|
import route82 from "./routes/video/upDateVideoConfig";
|
||||||
|
|
||||||
export default async (app: Express) => {
|
export default async (app: Express) => {
|
||||||
app.use("/assets/addAssets", route1);
|
app.use("/assets/addAssets", route1);
|
||||||
@ -152,17 +153,18 @@ export default async (app: Express) => {
|
|||||||
app.use("/task/getTaskApi", route66);
|
app.use("/task/getTaskApi", route66);
|
||||||
app.use("/task/taskDetails", route67);
|
app.use("/task/taskDetails", route67);
|
||||||
app.use("/user/getUser", route68);
|
app.use("/user/getUser", route68);
|
||||||
app.use("/video/addVideo", route69);
|
app.use("/user/saveUser", route69);
|
||||||
app.use("/video/addVideoConfig", route70);
|
app.use("/video/addVideo", route70);
|
||||||
app.use("/video/deleteVideoConfig", route71);
|
app.use("/video/addVideoConfig", route71);
|
||||||
app.use("/video/generatePrompt", route72);
|
app.use("/video/deleteVideoConfig", route72);
|
||||||
app.use("/video/generateVideo", route73);
|
app.use("/video/generatePrompt", route73);
|
||||||
app.use("/video/getManufacturer", route74);
|
app.use("/video/generateVideo", route74);
|
||||||
app.use("/video/getVideo", route75);
|
app.use("/video/getManufacturer", route75);
|
||||||
app.use("/video/getVideoConfigs", route76);
|
app.use("/video/getVideo", route76);
|
||||||
app.use("/video/getVideoModel", route77);
|
app.use("/video/getVideoConfigs", route77);
|
||||||
app.use("/video/getVideoStoryboards", route78);
|
app.use("/video/getVideoModel", route78);
|
||||||
app.use("/video/reviseVideoStoryboards", route79);
|
app.use("/video/getVideoStoryboards", route79);
|
||||||
app.use("/video/saveVideo", route80);
|
app.use("/video/reviseVideoStoryboards", route80);
|
||||||
app.use("/video/upDateVideoConfig", route81);
|
app.use("/video/saveVideo", route81);
|
||||||
|
app.use("/video/upDateVideoConfig", route82);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,6 +124,7 @@ export default router.post(
|
|||||||
assetsId: id,
|
assetsId: id,
|
||||||
});
|
});
|
||||||
const apiConfig = await u.getPromptAi("assetsImage");
|
const apiConfig = await u.getPromptAi("assetsImage");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const contentStr = await u.ai.image(
|
const contentStr = await u.ai.image(
|
||||||
{
|
{
|
||||||
|
|||||||
@ -48,7 +48,6 @@ export default router.post(
|
|||||||
);
|
);
|
||||||
res.status(200).send(success(reply));
|
res.status(200).send(success(reply));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("%c Line:51 🥟 err", "background:#e41a6a", err);
|
|
||||||
const msg = u.error(err).message;
|
const msg = u.error(err).message;
|
||||||
console.error(msg);
|
console.error(msg);
|
||||||
res.status(500).send(error(msg));
|
res.status(500).send(error(msg));
|
||||||
|
|||||||
@ -12,7 +12,6 @@ export default router.post(
|
|||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { id } = req.body;
|
const { id } = req.body;
|
||||||
console.log("%c Line:15 🍕 id", "background:#f5ce50", id);
|
|
||||||
await u.db("t_assets").where("id", id).delete();
|
await u.db("t_assets").where("id", id).delete();
|
||||||
res.status(200).send(success("分镜删除成功"));
|
res.status(200).send(success("分镜删除成功"));
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import apimart from "./owned/apimart";
|
|||||||
import other from "./owned/other";
|
import other from "./owned/other";
|
||||||
import gemini from "./owned/gemini";
|
import gemini from "./owned/gemini";
|
||||||
import modelScope from "./owned/modelScope";
|
import modelScope from "./owned/modelScope";
|
||||||
|
import grsai from "./owned/grsai";
|
||||||
|
|
||||||
const urlToBase64 = async (url: string): Promise<string> => {
|
const urlToBase64 = async (url: string): Promise<string> => {
|
||||||
const res = await axios.get(url, { responseType: "arraybuffer" });
|
const res = await axios.get(url, { responseType: "arraybuffer" });
|
||||||
@ -28,6 +29,7 @@ const modelInstance = {
|
|||||||
// apimart: apimart,
|
// apimart: apimart,
|
||||||
modelScope,
|
modelScope,
|
||||||
other,
|
other,
|
||||||
|
grsai
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default async (input: ImageConfig, config: AIConfig) => {
|
export default async (input: ImageConfig, config: AIConfig) => {
|
||||||
|
|||||||
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -119,7 +119,6 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
|||||||
return { completed: false };
|
return { completed: false };
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("%c Line:90 🥪 error", "background:#93c0a4", error.response?.data?.errors?.message);
|
|
||||||
const msg = u.error(error).message || "图片生成失败";
|
const msg = u.error(error).message || "图片生成失败";
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,11 +68,12 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
|||||||
const apiKey = config.apiKey.replace("Bearer ", "");
|
const apiKey = config.apiKey.replace("Bearer ", "");
|
||||||
const baseURL = "https://www.runninghub.cn";
|
const baseURL = "https://www.runninghub.cn";
|
||||||
const imageUrls = await Promise.all(input.imageBase64.map((base64Image) => uploadBase64ToRunninghub(base64Image, apiKey, baseURL)));
|
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 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(
|
const taskRes = await axios.post(
|
||||||
`https://www.runninghub.cn${endpoint}`,
|
`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 } },
|
{ headers: { Authorization: "Bearer " + apiKey } },
|
||||||
);
|
);
|
||||||
const taskId = taskRes.data.taskId;
|
const taskId = taskRes.data.taskId;
|
||||||
|
|||||||
@ -18,9 +18,11 @@ export default async (input: ImageConfig, config: AIConfig): Promise<string> =>
|
|||||||
"4K": "2304x4096",
|
"4K": "2304x4096",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const fullPrompt = input.systemPrompt ? `${input.systemPrompt}\n\n${input.prompt}` : input.prompt;
|
||||||
|
|
||||||
const body: Record<string, any> = {
|
const body: Record<string, any> = {
|
||||||
model: config.model,
|
model: config.model,
|
||||||
prompt: input.prompt,
|
prompt: fullPrompt,
|
||||||
size: sizeMap[input.aspectRatio][size],
|
size: sizeMap[input.aspectRatio][size],
|
||||||
response_format: "url",
|
response_format: "url",
|
||||||
sequential_image_generation: "disabled",
|
sequential_image_generation: "disabled",
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const buildOptions = async (input: AIInput<any>, config: AIConfig = {}) => {
|
|||||||
if (manufacturer == "other") {
|
if (manufacturer == "other") {
|
||||||
owned = modelList.find((m) => m.manufacturer === manufacturer);
|
owned = modelList.find((m) => m.manufacturer === manufacturer);
|
||||||
} else {
|
} 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) owned = modelList.find((m) => m.manufacturer === manufacturer);
|
||||||
}
|
}
|
||||||
if (!owned) throw new Error("不支持的厂商");
|
if (!owned) throw new Error("不支持的厂商");
|
||||||
@ -54,7 +54,7 @@ const buildOptions = async (input: AIInput<any>, config: AIConfig = {}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const output = input.output ? (outputBuilders[owned.responseFormat]?.(input.output) ?? null) : null;
|
const output = input.output ? (outputBuilders[owned.responseFormat]?.(input.output) ?? null) : null;
|
||||||
const chatModelManufacturer = ["volcengine", "other", "openai", "modelScope"];
|
const chatModelManufacturer = ["volcengine", "other", "openai", "modelScope","grsai"];
|
||||||
const modelFn = chatModelManufacturer.includes(owned.manufacturer) ? (modelInstance as OpenAIProvider).chat(model!) : modelInstance(model!);
|
const modelFn = chatModelManufacturer.includes(owned.manufacturer) ? (modelInstance as OpenAIProvider).chat(model!) : modelInstance(model!);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -30,13 +30,12 @@ const instanceMap = {
|
|||||||
openai: createOpenAI,
|
openai: createOpenAI,
|
||||||
zhipu: createZhipu,
|
zhipu: createZhipu,
|
||||||
qwen: createQwen,
|
qwen: createQwen,
|
||||||
|
|
||||||
gemini: createGoogleGenerativeAI,
|
gemini: createGoogleGenerativeAI,
|
||||||
|
|
||||||
anthropic: createAnthropic,
|
anthropic: createAnthropic,
|
||||||
modelScope: (options: OpenAIProviderSettings) => createOpenAI({ ...options, headers: { ...options?.headers, "X-ModelScope-Async-Mode": "true" } }),
|
modelScope: (options: OpenAIProviderSettings) => createOpenAI({ ...options, headers: { ...options?.headers, "X-ModelScope-Async-Mode": "true" } }),
|
||||||
xai: createXai,
|
xai: createXai,
|
||||||
other: createOpenAI,
|
other: createOpenAI,
|
||||||
|
grsai:createOpenAI
|
||||||
};
|
};
|
||||||
const modelList: Owned[] = [
|
const modelList: Owned[] = [
|
||||||
// DeepSeek
|
// DeepSeek
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import runninghub from "./owned/runninghub";
|
|||||||
import gemini from "./owned/gemini";
|
import gemini from "./owned/gemini";
|
||||||
import apimart from "./owned/apimart";
|
import apimart from "./owned/apimart";
|
||||||
import other from "./owned/other";
|
import other from "./owned/other";
|
||||||
|
import grsai from "./owned/grsai";
|
||||||
const modelInstance = {
|
const modelInstance = {
|
||||||
volcengine: volcengine,
|
volcengine: volcengine,
|
||||||
kling: kling,
|
kling: kling,
|
||||||
@ -20,7 +20,8 @@ const modelInstance = {
|
|||||||
gemini: gemini,
|
gemini: gemini,
|
||||||
runninghub: runninghub,
|
runninghub: runninghub,
|
||||||
apimart: apimart,
|
apimart: apimart,
|
||||||
// other: other,
|
other: other,
|
||||||
|
grsai:grsai
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default async (input: VideoConfig, config?: AIConfig) => {
|
export default async (input: VideoConfig, config?: AIConfig) => {
|
||||||
|
|||||||
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 };
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -4,12 +4,11 @@ import sharp from "sharp";
|
|||||||
import FormData from "form-data";
|
import FormData from "form-data";
|
||||||
import { pollTask, validateVideoConfig } from "@/utils/ai/utils";
|
import { pollTask, validateVideoConfig } from "@/utils/ai/utils";
|
||||||
import { createOpenAI } from "@ai-sdk/openai";
|
import { createOpenAI } from "@ai-sdk/openai";
|
||||||
|
import { experimental_generateVideo as generateVideo } from "ai";
|
||||||
export default async (input: VideoConfig, config: AIConfig) => {
|
export default async (input: VideoConfig, config: AIConfig) => {
|
||||||
if (!config.apiKey) throw new Error("缺少API Key");
|
if (!config.apiKey) throw new Error("缺少API Key");
|
||||||
if (!config.baseURL) throw new Error("缺少baseURL");
|
if (!config.baseURL) throw new Error("缺少baseURL");
|
||||||
// const { owned, images, hasTextType } = validateVideoConfig(input, config);
|
// const { owned, images, hasTextType } = validateVideoConfig(input, config);
|
||||||
|
|
||||||
const [requestUrl, queryUrl] = config.baseURL.split("|");
|
const [requestUrl, queryUrl] = config.baseURL.split("|");
|
||||||
|
|
||||||
const authorization = `Bearer ${config.apiKey}`;
|
const authorization = `Bearer ${config.apiKey}`;
|
||||||
@ -21,30 +20,40 @@ export default async (input: VideoConfig, config: AIConfig) => {
|
|||||||
|
|
||||||
// 根据 aspectRatio 设置 size
|
// 根据 aspectRatio 设置 size
|
||||||
const sizeMap: Record<string, string> = {
|
const sizeMap: Record<string, string> = {
|
||||||
"16:9": "1920x1080",
|
"16:9": "1280x720",
|
||||||
"9:16": "1080x1920",
|
"9:16": "720x1280",
|
||||||
};
|
};
|
||||||
formData.append("size", sizeMap[input.aspectRatio] || "1920x1080");
|
formData.append("size", sizeMap[input.aspectRatio] || "1920x1080");
|
||||||
|
|
||||||
if (input.imageBase64 && input.imageBase64.length) {
|
if (input.imageBase64 && input.imageBase64.length) {
|
||||||
const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, "");
|
const base64Data = input.imageBase64[0]!.replace(/^data:image\/\w+;base64,/, "");
|
||||||
const buffer = Buffer.from(base64Data, "base64");
|
const buffer = Buffer.from(base64Data, "base64");
|
||||||
formData.append("input_reference", buffer, { filename: "image.jpg", contentType: "image/jpeg" });
|
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") {
|
const body = {
|
||||||
return data.results?.length ? { completed: true, url: data.results[0].url } : { completed: false, error: "任务成功但未返回视频链接" };
|
model: config.model,
|
||||||
}
|
messages: [
|
||||||
if (data.status === "FAILED") return { completed: false, error: `任务失败: ${data.errorMessage || "未知错误"}` };
|
{
|
||||||
if (data.status === "QUEUED" || data.status === "RUNNING") return { completed: false };
|
role: "user",
|
||||||
return { completed: false, error: `未知状态: ${data.status}` };
|
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 || "未知错误"}`);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user