koishi-plugin-aka-ai-generator 0.7.0 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +3 -0
- package/lib/index.js +122 -50
- package/lib/providers/yunwu-video.d.ts +2 -2
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -42,6 +42,9 @@ export interface Config {
|
|
|
42
42
|
securityBlockWindow: number;
|
|
43
43
|
securityBlockWarningThreshold: number;
|
|
44
44
|
enableVideoGeneration: boolean;
|
|
45
|
+
videoProvider: 'yunwu';
|
|
46
|
+
videoApiKey: string;
|
|
47
|
+
videoApiBase: string;
|
|
45
48
|
videoModelId: string;
|
|
46
49
|
videoMaxWaitTime: number;
|
|
47
50
|
videoCreditsMultiplier: number;
|
package/lib/index.js
CHANGED
|
@@ -886,7 +886,7 @@ var YunwuVideoProvider = class {
|
|
|
886
886
|
}
|
|
887
887
|
/**
|
|
888
888
|
* 创建视频生成任务
|
|
889
|
-
* API: POST /v1/
|
|
889
|
+
* API: POST /v1/video/create
|
|
890
890
|
*/
|
|
891
891
|
async createVideoTask(prompt, imageUrl, options) {
|
|
892
892
|
const { logger, ctx } = this.config;
|
|
@@ -898,64 +898,127 @@ var YunwuVideoProvider = class {
|
|
|
898
898
|
this.config.apiTimeout,
|
|
899
899
|
logger
|
|
900
900
|
);
|
|
901
|
+
let orientation = "landscape";
|
|
902
|
+
if (options?.aspectRatio === "9:16") {
|
|
903
|
+
orientation = "portrait";
|
|
904
|
+
} else if (options?.aspectRatio === "1:1") {
|
|
905
|
+
orientation = "portrait";
|
|
906
|
+
}
|
|
907
|
+
let duration = options?.duration || 15;
|
|
908
|
+
if (duration < 15) {
|
|
909
|
+
duration = 15;
|
|
910
|
+
} else if (duration > 25) {
|
|
911
|
+
duration = 25;
|
|
912
|
+
} else if (duration <= 20) {
|
|
913
|
+
duration = 15;
|
|
914
|
+
} else {
|
|
915
|
+
duration = 25;
|
|
916
|
+
}
|
|
901
917
|
const requestBody = {
|
|
918
|
+
images: [`data:${mimeType};base64,${imageBase64}`],
|
|
902
919
|
model: this.config.modelId,
|
|
920
|
+
orientation,
|
|
903
921
|
prompt,
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
922
|
+
size: "large",
|
|
923
|
+
// 高清1080p
|
|
924
|
+
duration,
|
|
925
|
+
watermark: true,
|
|
926
|
+
// 默认优先无水印,出错会兜底到有水印
|
|
927
|
+
private: false
|
|
928
|
+
// 默认视频会发布
|
|
907
929
|
};
|
|
908
|
-
if (options?.fps) {
|
|
909
|
-
requestBody.fps = options.fps;
|
|
910
|
-
}
|
|
911
930
|
logger?.info("提交视频生成任务", {
|
|
912
931
|
model: this.config.modelId,
|
|
913
932
|
promptLength: prompt.length,
|
|
914
933
|
duration: requestBody.duration,
|
|
915
|
-
|
|
934
|
+
orientation: requestBody.orientation
|
|
916
935
|
});
|
|
917
936
|
const response = await ctx.http.post(
|
|
918
|
-
`${this.config.apiBase}/v1/
|
|
937
|
+
`${this.config.apiBase}/v1/video/create`,
|
|
919
938
|
requestBody,
|
|
920
939
|
{
|
|
921
940
|
headers: {
|
|
922
941
|
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
923
|
-
"Content-Type": "application/json"
|
|
942
|
+
"Content-Type": "application/json",
|
|
943
|
+
"Accept": "application/json"
|
|
924
944
|
},
|
|
925
945
|
timeout: this.config.apiTimeout * 1e3
|
|
926
946
|
}
|
|
927
947
|
);
|
|
928
948
|
if (response.error) {
|
|
929
|
-
|
|
949
|
+
const errorMsg = response.error.message || response.error.type || "创建任务失败";
|
|
950
|
+
throw new Error(sanitizeString(errorMsg));
|
|
930
951
|
}
|
|
931
|
-
|
|
952
|
+
if (response.status && response.status >= 400) {
|
|
953
|
+
const errorMsg = response.data?.error?.message || response.data?.error || response.statusText || "创建任务失败";
|
|
954
|
+
logger?.error("API 返回错误状态", { status: response.status, error: errorMsg, response: response.data });
|
|
955
|
+
throw new Error(sanitizeString(errorMsg));
|
|
956
|
+
}
|
|
957
|
+
const taskId = response.id || response.data?.id;
|
|
932
958
|
if (!taskId) {
|
|
933
959
|
logger?.error("未能获取任务ID", { response });
|
|
934
|
-
throw new Error("未能获取任务ID");
|
|
960
|
+
throw new Error("未能获取任务ID,请检查 API 响应格式");
|
|
935
961
|
}
|
|
936
962
|
logger?.info("视频任务已创建", { taskId });
|
|
937
963
|
return taskId;
|
|
938
964
|
} catch (error) {
|
|
939
965
|
logger?.error("创建视频任务失败", { error: sanitizeError(error) });
|
|
940
|
-
|
|
966
|
+
let errorMessage = error.message || "创建视频任务失败";
|
|
967
|
+
if (error.response) {
|
|
968
|
+
const responseData = error.response.data;
|
|
969
|
+
if (responseData?.error) {
|
|
970
|
+
errorMessage = responseData.error.message || responseData.error.type || errorMessage;
|
|
971
|
+
} else if (responseData?.message) {
|
|
972
|
+
errorMessage = responseData.message;
|
|
973
|
+
}
|
|
974
|
+
} else if (error.response?.data?.error) {
|
|
975
|
+
errorMessage = error.response.data.error.message || error.response.data.error.type || errorMessage;
|
|
976
|
+
}
|
|
977
|
+
throw new Error(`创建视频任务失败: ${sanitizeString(errorMessage)}`);
|
|
941
978
|
}
|
|
942
979
|
}
|
|
943
980
|
/**
|
|
944
981
|
* 查询任务状态
|
|
945
|
-
* API: GET /v1/
|
|
982
|
+
* API: GET /v1/video/{taskId} (根据创建端点的模式推断)
|
|
946
983
|
*/
|
|
947
984
|
async queryTaskStatus(taskId) {
|
|
948
985
|
const { logger, ctx } = this.config;
|
|
949
986
|
try {
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
{
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
987
|
+
const possibleEndpoints = [
|
|
988
|
+
`/v1/video/${taskId}`,
|
|
989
|
+
`/v1/video/status/${taskId}`,
|
|
990
|
+
`/v1/videos/generations/${taskId}`
|
|
991
|
+
];
|
|
992
|
+
let response = null;
|
|
993
|
+
let lastError = null;
|
|
994
|
+
for (const endpoint of possibleEndpoints) {
|
|
995
|
+
try {
|
|
996
|
+
response = await ctx.http.get(
|
|
997
|
+
`${this.config.apiBase}${endpoint}`,
|
|
998
|
+
{
|
|
999
|
+
headers: {
|
|
1000
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
1001
|
+
"Accept": "application/json"
|
|
1002
|
+
},
|
|
1003
|
+
timeout: this.config.apiTimeout * 1e3
|
|
1004
|
+
}
|
|
1005
|
+
);
|
|
1006
|
+
if (response && !response.error) {
|
|
1007
|
+
logger?.debug("查询任务状态成功", { taskId, endpoint });
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
lastError = err;
|
|
1012
|
+
if (err.response?.status === 404 && possibleEndpoints.indexOf(endpoint) < possibleEndpoints.length - 1) {
|
|
1013
|
+
logger?.debug("尝试下一个查询端点", { taskId, endpoint, nextEndpoint: possibleEndpoints[possibleEndpoints.indexOf(endpoint) + 1] });
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
throw err;
|
|
957
1017
|
}
|
|
958
|
-
|
|
1018
|
+
}
|
|
1019
|
+
if (!response) {
|
|
1020
|
+
throw lastError || new Error("所有查询端点都失败");
|
|
1021
|
+
}
|
|
959
1022
|
const status = response.status || response.data?.status || "pending";
|
|
960
1023
|
const videoUrl = response.video_url || response.url || response.data?.video_url || response.data?.url;
|
|
961
1024
|
return {
|
|
@@ -967,7 +1030,11 @@ var YunwuVideoProvider = class {
|
|
|
967
1030
|
};
|
|
968
1031
|
} catch (error) {
|
|
969
1032
|
logger?.error("查询任务状态失败", { taskId, error: sanitizeError(error) });
|
|
970
|
-
|
|
1033
|
+
let errorMessage = error.message || "查询任务失败";
|
|
1034
|
+
if (error.response?.data?.error) {
|
|
1035
|
+
errorMessage = error.response.data.error.message || error.response.data.error.type || errorMessage;
|
|
1036
|
+
}
|
|
1037
|
+
throw new Error(`查询任务失败: ${sanitizeString(errorMessage)}`);
|
|
971
1038
|
}
|
|
972
1039
|
}
|
|
973
1040
|
/**
|
|
@@ -1586,28 +1653,27 @@ var Config = import_koishi2.Schema.intersect([
|
|
|
1586
1653
|
prompts: import_koishi2.Schema.array(StyleItemSchema).role("table").default([]).description("属于该类型的 prompt 列表")
|
|
1587
1654
|
})).role("table").default({}).description("按类型管理的 prompt 组,键名即为分组名称")
|
|
1588
1655
|
}),
|
|
1589
|
-
//
|
|
1656
|
+
// 视频生成配置(独立于图像生成配置)
|
|
1590
1657
|
import_koishi2.Schema.object({
|
|
1591
1658
|
enableVideoGeneration: import_koishi2.Schema.boolean().default(false).description("启用图生成视频功能(消耗较大,需谨慎开启)"),
|
|
1659
|
+
videoProvider: import_koishi2.Schema.union([
|
|
1660
|
+
import_koishi2.Schema.const("yunwu").description("云雾服务")
|
|
1661
|
+
]).default("yunwu").description("视频生成供应商(目前只支持云雾)"),
|
|
1662
|
+
videoApiKey: import_koishi2.Schema.string().description("视频生成 API 密钥(独立于图像生成配置)").role("secret").default(""),
|
|
1663
|
+
videoApiBase: import_koishi2.Schema.string().default("https://yunwu.ai").description("视频生成 API 地址"),
|
|
1592
1664
|
videoModelId: import_koishi2.Schema.string().default("sora-2").description("视频生成模型ID (sora-2 或 sora-2-pro)"),
|
|
1593
1665
|
videoMaxWaitTime: import_koishi2.Schema.number().default(300).min(60).max(600).description("视频生成最大等待时间(秒),超时后可异步查询"),
|
|
1594
1666
|
videoCreditsMultiplier: import_koishi2.Schema.number().default(5).min(1).max(20).description("视频生成积分倍数(相对于图片生成,默认5倍)"),
|
|
1595
1667
|
videoStyles: import_koishi2.Schema.array(import_koishi2.Schema.object({
|
|
1596
1668
|
commandName: import_koishi2.Schema.string().required().description("命令名称").role("table-cell", { width: 100 }),
|
|
1597
1669
|
prompt: import_koishi2.Schema.string().role("textarea", { rows: 2 }).required().description("视频描述 prompt"),
|
|
1598
|
-
duration: import_koishi2.Schema.number().
|
|
1670
|
+
duration: import_koishi2.Schema.number().default(15).description("视频时长(秒,仅支持 15 或 25)"),
|
|
1599
1671
|
aspectRatio: import_koishi2.Schema.string().description("宽高比(如 16:9)")
|
|
1600
1672
|
})).role("table").default([
|
|
1601
1673
|
{
|
|
1602
|
-
commandName: "
|
|
1603
|
-
prompt: "
|
|
1604
|
-
duration:
|
|
1605
|
-
aspectRatio: "16:9"
|
|
1606
|
-
},
|
|
1607
|
-
{
|
|
1608
|
-
commandName: "快节奏视频",
|
|
1609
|
-
prompt: "快速动态的场景变化,充满活力",
|
|
1610
|
-
duration: 5,
|
|
1674
|
+
commandName: "变视频",
|
|
1675
|
+
prompt: "将该图片生成一段符合产品展现的流畅视频",
|
|
1676
|
+
duration: 15,
|
|
1611
1677
|
aspectRatio: "16:9"
|
|
1612
1678
|
}
|
|
1613
1679
|
]).description("视频风格预设")
|
|
@@ -1636,16 +1702,22 @@ function apply(ctx, config) {
|
|
|
1636
1702
|
const modelMappingIndex = buildModelMappingIndex(config.modelMappings);
|
|
1637
1703
|
let videoProvider = null;
|
|
1638
1704
|
if (config.enableVideoGeneration) {
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1705
|
+
if (!config.videoApiKey) {
|
|
1706
|
+
logger.warn("视频生成功能已启用,但未配置视频 API 密钥,视频功能将不可用");
|
|
1707
|
+
} else if (config.videoProvider !== "yunwu") {
|
|
1708
|
+
logger.warn(`视频生成供应商 ${config.videoProvider} 暂不支持,仅支持 yunwu`);
|
|
1709
|
+
} else {
|
|
1710
|
+
videoProvider = new YunwuVideoProvider({
|
|
1711
|
+
apiKey: config.videoApiKey,
|
|
1712
|
+
modelId: config.videoModelId,
|
|
1713
|
+
apiBase: config.videoApiBase,
|
|
1714
|
+
apiTimeout: config.apiTimeout,
|
|
1715
|
+
logLevel: config.logLevel,
|
|
1716
|
+
logger,
|
|
1717
|
+
ctx
|
|
1718
|
+
});
|
|
1719
|
+
logger.info(`视频生成功能已启用 (供应商: ${config.videoProvider}, 模型: ${config.videoModelId}, API: ${config.videoApiBase})`);
|
|
1720
|
+
}
|
|
1649
1721
|
}
|
|
1650
1722
|
const styleDefinitions = collectStyleDefinitions();
|
|
1651
1723
|
function collectStyleDefinitions() {
|
|
@@ -2137,7 +2209,7 @@ ${infoParts.join("\n")}`;
|
|
|
2137
2209
|
}
|
|
2138
2210
|
}
|
|
2139
2211
|
if (config.enableVideoGeneration && videoProvider) {
|
|
2140
|
-
ctx.command("图生视频 [img:text]", "根据图片和描述生成视频").option("duration", "-d <duration:number> 视频时长(
|
|
2212
|
+
ctx.command("图生视频 [img:text]", "根据图片和描述生成视频").option("duration", "-d <duration:number> 视频时长(15 或 25 秒)").option("ratio", "-r <ratio:string> 宽高比(16:9, 9:16, 1:1)").action(async ({ session, options }, img) => {
|
|
2141
2213
|
if (!session?.userId) return "会话无效";
|
|
2142
2214
|
const userId = session.userId;
|
|
2143
2215
|
const userName = session.username || userId || "未知用户";
|
|
@@ -2177,9 +2249,9 @@ ${infoParts.join("\n")}`;
|
|
|
2177
2249
|
}
|
|
2178
2250
|
prompt = text;
|
|
2179
2251
|
}
|
|
2180
|
-
const duration = options?.duration ||
|
|
2181
|
-
if (duration
|
|
2182
|
-
return "
|
|
2252
|
+
const duration = options?.duration || 15;
|
|
2253
|
+
if (duration !== 15 && duration !== 25) {
|
|
2254
|
+
return "视频时长必须是 15 或 25 秒";
|
|
2183
2255
|
}
|
|
2184
2256
|
const ratio = options?.ratio || "16:9";
|
|
2185
2257
|
const validRatios = ["16:9", "9:16", "1:1"];
|
|
@@ -2289,7 +2361,7 @@ ${infoParts.join("\n")}`;
|
|
|
2289
2361
|
finalPrompt,
|
|
2290
2362
|
imageUrls[0],
|
|
2291
2363
|
{
|
|
2292
|
-
duration: style.duration ||
|
|
2364
|
+
duration: style.duration || 15,
|
|
2293
2365
|
aspectRatio: style.aspectRatio || "16:9"
|
|
2294
2366
|
},
|
|
2295
2367
|
config.videoMaxWaitTime
|
|
@@ -9,12 +9,12 @@ export declare class YunwuVideoProvider implements VideoProvider {
|
|
|
9
9
|
constructor(config: YunwuVideoConfig);
|
|
10
10
|
/**
|
|
11
11
|
* 创建视频生成任务
|
|
12
|
-
* API: POST /v1/
|
|
12
|
+
* API: POST /v1/video/create
|
|
13
13
|
*/
|
|
14
14
|
createVideoTask(prompt: string, imageUrl: string, options?: VideoGenerationOptions): Promise<string>;
|
|
15
15
|
/**
|
|
16
16
|
* 查询任务状态
|
|
17
|
-
* API: GET /v1/
|
|
17
|
+
* API: GET /v1/video/{taskId} (根据创建端点的模式推断)
|
|
18
18
|
*/
|
|
19
19
|
queryTaskStatus(taskId: string): Promise<VideoTaskStatus>;
|
|
20
20
|
/**
|