@bashcat/ai-image-chat-mcp 2.3.5 → 3.0.0
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/dist/index.js +438 -1201
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
4
|
+
import { z } from "zod";
|
|
5
5
|
import dotenv from "dotenv";
|
|
6
6
|
import axios from "axios";
|
|
7
7
|
import * as fs from "fs";
|
|
@@ -13,990 +13,422 @@ const require = createRequire(import.meta.url);
|
|
|
13
13
|
const packageJson = require("../package.json");
|
|
14
14
|
// 載入環境變數
|
|
15
15
|
dotenv.config();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const cleanPrompt = prompt
|
|
49
|
-
.replace(/[^\w\s\u4e00-\u9fff]/g, '') // 保留字母、數字、空格和中文字符
|
|
50
|
-
.replace(/\s+/g, '-') // 將空格替換為連字符
|
|
51
|
-
.substring(0, 50); // 限制長度為50字符
|
|
52
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
53
|
-
// 確保輸出格式為小寫
|
|
54
|
-
const extension = outputFormat.toLowerCase() === 'jpeg' ? 'jpg' : outputFormat.toLowerCase();
|
|
55
|
-
return `${cleanPrompt}-${timestamp}.${extension}`;
|
|
16
|
+
// ===== 配置 =====
|
|
17
|
+
const apiKey = process.env.AI_API_KEY || "";
|
|
18
|
+
const baseUrl = process.env.AI_API_BASE_URL || "https://api.laozhang.ai/v1";
|
|
19
|
+
const saveDirectory = process.env.AI_IMAGE_SAVE_PATH || path.join(os.homedir(), "generated_images");
|
|
20
|
+
const dashScopeApiKey = process.env.ALI_API_KEY || process.env.DASHSCOPE_API_KEY || "";
|
|
21
|
+
if (!apiKey) {
|
|
22
|
+
throw new Error("AI_API_KEY 環境變數未設定");
|
|
23
|
+
}
|
|
24
|
+
if (!fs.existsSync(saveDirectory)) {
|
|
25
|
+
fs.mkdirSync(saveDirectory, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
// ===== 工具函式 =====
|
|
28
|
+
function generateFilename(prompt, outputFormat = "jpg") {
|
|
29
|
+
const cleanPrompt = prompt
|
|
30
|
+
.replace(/[^\w\s\u4e00-\u9fff]/g, "")
|
|
31
|
+
.replace(/\s+/g, "-")
|
|
32
|
+
.substring(0, 50);
|
|
33
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
34
|
+
const extension = outputFormat.toLowerCase() === "jpeg" ? "jpg" : outputFormat.toLowerCase();
|
|
35
|
+
return `${cleanPrompt}-${timestamp}.${extension}`;
|
|
36
|
+
}
|
|
37
|
+
function formatPathForDisplay(filePath) {
|
|
38
|
+
const homeDir = os.homedir();
|
|
39
|
+
return filePath.startsWith(homeDir) ? filePath.replace(homeDir, "~") : filePath;
|
|
40
|
+
}
|
|
41
|
+
async function downloadAndSaveImage(imageUrl, filename, outputFormat = "jpg") {
|
|
42
|
+
const response = await axios.get(imageUrl, { responseType: "arraybuffer" });
|
|
43
|
+
const filePath = path.join(saveDirectory, filename);
|
|
44
|
+
const fmt = outputFormat.toLowerCase() === "jpeg" ? "jpg" : outputFormat.toLowerCase();
|
|
45
|
+
const sharpInstance = sharp(Buffer.from(response.data));
|
|
46
|
+
if (fmt === "png") {
|
|
47
|
+
await sharpInstance.png().toFile(filePath);
|
|
56
48
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const homeDir = os.homedir();
|
|
60
|
-
if (this.saveDirectory.startsWith(homeDir)) {
|
|
61
|
-
return this.saveDirectory.replace(homeDir, '~');
|
|
62
|
-
}
|
|
63
|
-
return this.saveDirectory;
|
|
49
|
+
else if (fmt === "webp") {
|
|
50
|
+
await sharpInstance.webp({ quality: 90 }).toFile(filePath);
|
|
64
51
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const homeDir = os.homedir();
|
|
68
|
-
if (filePath.startsWith(homeDir)) {
|
|
69
|
-
return filePath.replace(homeDir, '~');
|
|
70
|
-
}
|
|
71
|
-
return filePath;
|
|
52
|
+
else {
|
|
53
|
+
await sharpInstance.jpeg({ quality: 90 }).toFile(filePath);
|
|
72
54
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (normalizedFormat === 'jpg' || normalizedFormat === 'jpeg') {
|
|
80
|
-
// 轉換為 JPG 格式
|
|
81
|
-
await sharp(Buffer.from(response.data))
|
|
82
|
-
.jpeg({ quality: 90 })
|
|
83
|
-
.toFile(filePath);
|
|
84
|
-
}
|
|
85
|
-
else if (normalizedFormat === 'png') {
|
|
86
|
-
// 保持 PNG 格式
|
|
87
|
-
await sharp(Buffer.from(response.data))
|
|
88
|
-
.png()
|
|
89
|
-
.toFile(filePath);
|
|
90
|
-
}
|
|
91
|
-
else if (normalizedFormat === 'webp') {
|
|
92
|
-
// 轉換為 WebP 格式
|
|
93
|
-
await sharp(Buffer.from(response.data))
|
|
94
|
-
.webp({ quality: 90 })
|
|
95
|
-
.toFile(filePath);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
// 預設轉換為 JPG
|
|
99
|
-
await sharp(Buffer.from(response.data))
|
|
100
|
-
.jpeg({ quality: 90 })
|
|
101
|
-
.toFile(filePath);
|
|
102
|
-
}
|
|
103
|
-
return filePath;
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
throw new Error(`圖片下載或轉換失敗: ${error instanceof Error ? error.message : String(error)}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async saveBase64Image(base64Data, filename, outputFormat = 'jpg') {
|
|
110
|
-
try {
|
|
111
|
-
// 解析 data URI 格式:data:image/png;base64,iVBORw0KGgoAAAANSU...
|
|
112
|
-
const matches = base64Data.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/);
|
|
113
|
-
if (!matches) {
|
|
114
|
-
throw new Error('無效的 base64 圖片格式');
|
|
115
|
-
}
|
|
116
|
-
const [, , base64String] = matches;
|
|
117
|
-
const buffer = Buffer.from(base64String, 'base64');
|
|
118
|
-
const filePath = path.join(this.saveDirectory, filename);
|
|
119
|
-
// 根據輸出格式處理圖片
|
|
120
|
-
const normalizedFormat = outputFormat.toLowerCase() === 'jpeg' ? 'jpg' : outputFormat.toLowerCase();
|
|
121
|
-
if (normalizedFormat === 'jpg' || normalizedFormat === 'jpeg') {
|
|
122
|
-
// 轉換為 JPG 格式
|
|
123
|
-
await sharp(buffer)
|
|
124
|
-
.jpeg({ quality: 90 })
|
|
125
|
-
.toFile(filePath);
|
|
126
|
-
}
|
|
127
|
-
else if (normalizedFormat === 'png') {
|
|
128
|
-
// 保持 PNG 格式
|
|
129
|
-
await sharp(buffer)
|
|
130
|
-
.png()
|
|
131
|
-
.toFile(filePath);
|
|
132
|
-
}
|
|
133
|
-
else if (normalizedFormat === 'webp') {
|
|
134
|
-
// 轉換為 WebP 格式
|
|
135
|
-
await sharp(buffer)
|
|
136
|
-
.webp({ quality: 90 })
|
|
137
|
-
.toFile(filePath);
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
// 預設轉換為 JPG
|
|
141
|
-
await sharp(buffer)
|
|
142
|
-
.jpeg({ quality: 90 })
|
|
143
|
-
.toFile(filePath);
|
|
144
|
-
}
|
|
145
|
-
return filePath;
|
|
146
|
-
}
|
|
147
|
-
catch (error) {
|
|
148
|
-
throw new Error(`Base64 圖片保存失敗: ${error instanceof Error ? error.message : String(error)}`);
|
|
149
|
-
}
|
|
55
|
+
return filePath;
|
|
56
|
+
}
|
|
57
|
+
async function saveBase64Image(base64Data, filename, outputFormat = "jpg") {
|
|
58
|
+
const matches = base64Data.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/);
|
|
59
|
+
if (!matches) {
|
|
60
|
+
throw new Error("無效的 base64 圖片格式");
|
|
150
61
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
];
|
|
158
|
-
for (const pattern of urlPatterns) {
|
|
159
|
-
const matches = content.match(pattern);
|
|
160
|
-
if (matches && matches.length > 0) {
|
|
161
|
-
const url = matches[0].replace(/^\!\[.*?\]\(/, '').replace(/\)$/, '');
|
|
162
|
-
if (url.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)) {
|
|
163
|
-
return url;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return null;
|
|
62
|
+
const buffer = Buffer.from(matches[2], "base64");
|
|
63
|
+
const filePath = path.join(saveDirectory, filename);
|
|
64
|
+
const fmt = outputFormat.toLowerCase() === "jpeg" ? "jpg" : outputFormat.toLowerCase();
|
|
65
|
+
const sharpInstance = sharp(buffer);
|
|
66
|
+
if (fmt === "png") {
|
|
67
|
+
await sharpInstance.png().toFile(filePath);
|
|
168
68
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const urlPatterns = [
|
|
172
|
-
/https?:\/\/[^\s\)]+\.(?:jpg|jpeg|png|gif|webp)/gi,
|
|
173
|
-
/!\[.*?\]\((https?:\/\/[^\)]+)\)/gi,
|
|
174
|
-
/https?:\/\/[^\s\)]+/gi
|
|
175
|
-
];
|
|
176
|
-
const urls = [];
|
|
177
|
-
for (const pattern of urlPatterns) {
|
|
178
|
-
const matches = content.match(pattern);
|
|
179
|
-
if (matches) {
|
|
180
|
-
for (const match of matches) {
|
|
181
|
-
const url = match.replace(/^\!\[.*?\]\(/, '').replace(/\)$/, '');
|
|
182
|
-
if (url.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)) {
|
|
183
|
-
urls.push(url);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
// 去重並返回
|
|
189
|
-
return [...new Set(urls)];
|
|
69
|
+
else if (fmt === "webp") {
|
|
70
|
+
await sharpInstance.webp({ quality: 90 }).toFile(filePath);
|
|
190
71
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const base64Pattern = /data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/=]+/gi;
|
|
194
|
-
const matches = content.match(base64Pattern);
|
|
195
|
-
return matches || [];
|
|
72
|
+
else {
|
|
73
|
+
await sharpInstance.jpeg({ quality: 90 }).toFile(filePath);
|
|
196
74
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
75
|
+
return filePath;
|
|
76
|
+
}
|
|
77
|
+
function extractMultipleImageUrls(content) {
|
|
78
|
+
const urlPatterns = [
|
|
79
|
+
/https?:\/\/[^\s\)]+\.(?:jpg|jpeg|png|gif|webp)/gi,
|
|
80
|
+
/!\[.*?\]\((https?:\/\/[^\)]+)\)/gi,
|
|
81
|
+
/https?:\/\/[^\s\)]+/gi,
|
|
82
|
+
];
|
|
83
|
+
const urls = [];
|
|
84
|
+
for (const pattern of urlPatterns) {
|
|
85
|
+
const matches = content.match(pattern);
|
|
86
|
+
if (matches) {
|
|
87
|
+
for (const match of matches) {
|
|
88
|
+
const url = match.replace(/^\!\[.*?\]\(/, "").replace(/\)$/, "");
|
|
89
|
+
if (url.match(/\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i)) {
|
|
90
|
+
urls.push(url);
|
|
211
91
|
}
|
|
212
92
|
}
|
|
213
93
|
}
|
|
214
|
-
return {
|
|
215
|
-
imageUrls,
|
|
216
|
-
savedImages,
|
|
217
|
-
hasBase64: base64Images.length > 0
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
// 阿里雲 DashScope 創建任務
|
|
221
|
-
async createDashScopeTask(prompt, negativePrompt, options) {
|
|
222
|
-
if (!this.dashScopeApiKey) {
|
|
223
|
-
throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
|
|
224
|
-
}
|
|
225
|
-
const requestData = {
|
|
226
|
-
model: options?.model || "wanx2.1-t2i-turbo",
|
|
227
|
-
input: {
|
|
228
|
-
prompt,
|
|
229
|
-
negative_prompt: negativePrompt || "人物",
|
|
230
|
-
},
|
|
231
|
-
parameters: {
|
|
232
|
-
size: options?.size || "1024*1024",
|
|
233
|
-
n: options?.n || 1,
|
|
234
|
-
seed: options?.seed,
|
|
235
|
-
prompt_extend: options?.promptExtend !== false,
|
|
236
|
-
watermark: options?.watermark || false,
|
|
237
|
-
},
|
|
238
|
-
};
|
|
239
|
-
try {
|
|
240
|
-
const response = await axios.post("https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis", requestData, {
|
|
241
|
-
headers: {
|
|
242
|
-
"Content-Type": "application/json",
|
|
243
|
-
"Authorization": `Bearer ${this.dashScopeApiKey}`,
|
|
244
|
-
"X-DashScope-Async": "enable",
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
return response.data;
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
if (axios.isAxiosError(error)) {
|
|
251
|
-
const axiosError = error;
|
|
252
|
-
const errorMessage = axiosError.response?.data?.message || axiosError.message;
|
|
253
|
-
throw new Error(`阿里雲 DashScope 創建任務失敗: ${errorMessage}`);
|
|
254
|
-
}
|
|
255
|
-
throw new Error(`創建任務時發生未知錯誤: ${error}`);
|
|
256
|
-
}
|
|
257
94
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
95
|
+
return [...new Set(urls)];
|
|
96
|
+
}
|
|
97
|
+
function extractBase64Images(content) {
|
|
98
|
+
const matches = content.match(/data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/=]+/gi);
|
|
99
|
+
return matches || [];
|
|
100
|
+
}
|
|
101
|
+
async function processImageContent(content, prompt, outputFormat = "jpg") {
|
|
102
|
+
const imageUrls = extractMultipleImageUrls(content);
|
|
103
|
+
const base64Images = extractBase64Images(content);
|
|
104
|
+
const savedImages = [];
|
|
105
|
+
for (let i = 0; i < base64Images.length; i++) {
|
|
263
106
|
try {
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
return response.data;
|
|
107
|
+
const filename = generateFilename(`${prompt}-${i + 1}`, outputFormat);
|
|
108
|
+
const savedPath = await saveBase64Image(base64Images[i], filename, outputFormat);
|
|
109
|
+
savedImages.push(savedPath);
|
|
270
110
|
}
|
|
271
111
|
catch (error) {
|
|
272
|
-
|
|
273
|
-
const axiosError = error;
|
|
274
|
-
const errorMessage = axiosError.response?.data?.message || axiosError.message;
|
|
275
|
-
throw new Error(`阿里雲 DashScope 查詢任務失敗: ${errorMessage}`);
|
|
276
|
-
}
|
|
277
|
-
throw new Error(`查詢任務時發生未知錯誤: ${error}`);
|
|
112
|
+
console.error(`保存第 ${i + 1} 張 base64 圖片失敗:`, error);
|
|
278
113
|
}
|
|
279
114
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
throw new Error("創建任務失敗,未獲取到任務ID");
|
|
287
|
-
}
|
|
288
|
-
// 步驟2: 輪詢查詢結果
|
|
289
|
-
const maxWaitMinutes = options?.maxWaitMinutes || 5; // 預設最多等待5分鐘
|
|
290
|
-
const maxAttempts = maxWaitMinutes * 6; // 每10秒查詢一次
|
|
291
|
-
let attempts = 0;
|
|
292
|
-
while (attempts < maxAttempts) {
|
|
293
|
-
const queryResponse = await this.queryDashScopeTask(taskId);
|
|
294
|
-
const taskStatus = queryResponse.output.task_status;
|
|
295
|
-
if (taskStatus === "SUCCEEDED") {
|
|
296
|
-
const results = queryResponse.output.results || [];
|
|
297
|
-
return results;
|
|
298
|
-
}
|
|
299
|
-
else if (taskStatus === "FAILED") {
|
|
300
|
-
throw new Error(`圖片生成失敗: ${queryResponse.output.message || "未知錯誤"}`);
|
|
301
|
-
}
|
|
302
|
-
else if (taskStatus === "CANCELED") {
|
|
303
|
-
throw new Error("任務已被取消");
|
|
304
|
-
}
|
|
305
|
-
// 等待10秒後再次查詢
|
|
306
|
-
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
307
|
-
attempts++;
|
|
308
|
-
}
|
|
309
|
-
throw new Error(`圖片生成超時,等待時間超過 ${maxWaitMinutes} 分鐘`);
|
|
115
|
+
return { imageUrls, savedImages, hasBase64: base64Images.length > 0 };
|
|
116
|
+
}
|
|
117
|
+
// ===== DashScope API =====
|
|
118
|
+
async function createDashScopeTask(prompt, negativePrompt, options) {
|
|
119
|
+
if (!dashScopeApiKey) {
|
|
120
|
+
throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
|
|
310
121
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
122
|
+
const requestData = {
|
|
123
|
+
model: options?.model || "wanx2.1-t2i-turbo",
|
|
124
|
+
input: {
|
|
125
|
+
prompt,
|
|
126
|
+
negative_prompt: negativePrompt || "人物",
|
|
127
|
+
},
|
|
128
|
+
parameters: {
|
|
129
|
+
size: options?.size || "1024*1024",
|
|
130
|
+
n: options?.n || 1,
|
|
131
|
+
seed: options?.seed,
|
|
132
|
+
prompt_extend: options?.promptExtend !== false,
|
|
133
|
+
watermark: options?.watermark || false,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
try {
|
|
137
|
+
const response = await axios.post("https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis", requestData, {
|
|
138
|
+
headers: {
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
Authorization: `Bearer ${dashScopeApiKey}`,
|
|
141
|
+
"X-DashScope-Async": "enable",
|
|
322
142
|
},
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
duration: options?.duration || 5,
|
|
326
|
-
prompt_extend: options?.promptExtend !== false,
|
|
327
|
-
seed: options?.seed,
|
|
328
|
-
},
|
|
329
|
-
};
|
|
330
|
-
try {
|
|
331
|
-
const response = await axios.post("https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis", requestData, {
|
|
332
|
-
headers: {
|
|
333
|
-
"Content-Type": "application/json",
|
|
334
|
-
"Authorization": `Bearer ${this.dashScopeApiKey}`,
|
|
335
|
-
"X-DashScope-Async": "enable",
|
|
336
|
-
},
|
|
337
|
-
});
|
|
338
|
-
return response.data;
|
|
339
|
-
}
|
|
340
|
-
catch (error) {
|
|
341
|
-
if (axios.isAxiosError(error)) {
|
|
342
|
-
const axiosError = error;
|
|
343
|
-
const errorMessage = axiosError.response?.data?.message || axiosError.message;
|
|
344
|
-
throw new Error(`阿里雲 DashScope 視頻生成任務創建失敗: ${errorMessage}`);
|
|
345
|
-
}
|
|
346
|
-
throw new Error(`創建視頻生成任務時發生未知錯誤: ${error}`);
|
|
347
|
-
}
|
|
143
|
+
});
|
|
144
|
+
return response.data;
|
|
348
145
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const taskId = createResponse.output.task_id;
|
|
354
|
-
if (!taskId) {
|
|
355
|
-
throw new Error("創建視頻生成任務失敗,未獲取到任務ID");
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (axios.isAxiosError(error)) {
|
|
148
|
+
const errorMessage = error.response?.data?.message || error.message;
|
|
149
|
+
throw new Error(`阿里雲 DashScope 創建任務失敗: ${errorMessage}`);
|
|
356
150
|
}
|
|
357
|
-
|
|
358
|
-
const maxWaitMinutes = options?.maxWaitMinutes || 15; // 預設最多等待15分鐘(視頻生成較慢)
|
|
359
|
-
const maxAttempts = maxWaitMinutes * 6; // 每10秒查詢一次
|
|
360
|
-
let attempts = 0;
|
|
361
|
-
while (attempts < maxAttempts) {
|
|
362
|
-
const queryResponse = await this.queryDashScopeTask(taskId);
|
|
363
|
-
const taskStatus = queryResponse.output.task_status;
|
|
364
|
-
if (taskStatus === "SUCCEEDED") {
|
|
365
|
-
const videoUrl = queryResponse.output.video_url;
|
|
366
|
-
if (!videoUrl) {
|
|
367
|
-
throw new Error("任務完成但未獲取到視頻URL");
|
|
368
|
-
}
|
|
369
|
-
return videoUrl;
|
|
370
|
-
}
|
|
371
|
-
else if (taskStatus === "FAILED") {
|
|
372
|
-
throw new Error(`視頻生成失敗: ${queryResponse.output.message || "未知錯誤"}`);
|
|
373
|
-
}
|
|
374
|
-
else if (taskStatus === "CANCELED") {
|
|
375
|
-
throw new Error("視頻生成任務已被取消");
|
|
376
|
-
}
|
|
377
|
-
// 等待10秒後再次查詢
|
|
378
|
-
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
379
|
-
attempts++;
|
|
380
|
-
}
|
|
381
|
-
throw new Error(`視頻生成超時,等待時間超過 ${maxWaitMinutes} 分鐘`);
|
|
151
|
+
throw new Error(`創建任務時發生未知錯誤: ${error}`);
|
|
382
152
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
inputSchema: {
|
|
392
|
-
type: "object",
|
|
393
|
-
properties: {
|
|
394
|
-
prompt: {
|
|
395
|
-
type: "string",
|
|
396
|
-
description: "圖像生成的提示詞",
|
|
397
|
-
},
|
|
398
|
-
model: {
|
|
399
|
-
type: "string",
|
|
400
|
-
description: "選擇圖像生成模型",
|
|
401
|
-
enum: [
|
|
402
|
-
// 通義萬相模型
|
|
403
|
-
"wanx2.1-t2i-turbo",
|
|
404
|
-
"wanx2.1-t2i-plus",
|
|
405
|
-
"wanx2.0-t2i-turbo",
|
|
406
|
-
// 老張API模型
|
|
407
|
-
"sora_image",
|
|
408
|
-
"gpt-4o-image",
|
|
409
|
-
"gemini-2.5-flash-image-preview"
|
|
410
|
-
],
|
|
411
|
-
default: "gemini-2.5-flash-image-preview",
|
|
412
|
-
},
|
|
413
|
-
negative_prompt: {
|
|
414
|
-
type: "string",
|
|
415
|
-
description: "反向提示詞,僅通義萬相模型支援(可選,預設為「人物」)",
|
|
416
|
-
default: "人物",
|
|
417
|
-
},
|
|
418
|
-
size: {
|
|
419
|
-
type: "string",
|
|
420
|
-
description: "輸出圖像的分辨率,僅通義萬相模型支援(可選,預設為 1024*1024)",
|
|
421
|
-
default: "1024*1024",
|
|
422
|
-
},
|
|
423
|
-
n: {
|
|
424
|
-
type: "number",
|
|
425
|
-
description: "生成圖片的數量,僅通義萬相模型支援(可選,範圍 1-4,預設為 1)",
|
|
426
|
-
minimum: 1,
|
|
427
|
-
maximum: 4,
|
|
428
|
-
default: 1,
|
|
429
|
-
},
|
|
430
|
-
seed: {
|
|
431
|
-
type: "number",
|
|
432
|
-
description: "隨機數種子,僅通義萬相模型支援(可選)",
|
|
433
|
-
minimum: 0,
|
|
434
|
-
maximum: 2147483647,
|
|
435
|
-
},
|
|
436
|
-
prompt_extend: {
|
|
437
|
-
type: "boolean",
|
|
438
|
-
description: "是否開啟 prompt 智能改寫,僅通義萬相模型支援(可選,預設為 true)",
|
|
439
|
-
default: true,
|
|
440
|
-
},
|
|
441
|
-
watermark: {
|
|
442
|
-
type: "boolean",
|
|
443
|
-
description: "是否添加水印標識,僅通義萬相模型支援(可選,預設為 false)",
|
|
444
|
-
default: false,
|
|
445
|
-
},
|
|
446
|
-
output_format: {
|
|
447
|
-
type: "string",
|
|
448
|
-
description: "輸出圖片格式(可選,預設為 jpg)",
|
|
449
|
-
enum: ["jpg", "jpeg", "png", "webp"],
|
|
450
|
-
default: "jpg",
|
|
451
|
-
},
|
|
452
|
-
system_prompt: {
|
|
453
|
-
type: "string",
|
|
454
|
-
description: "系統提示詞,僅老張API模型支援(可選)",
|
|
455
|
-
},
|
|
456
|
-
aspect_ratio: {
|
|
457
|
-
type: "string",
|
|
458
|
-
description: "圖片比例,僅老張API模型支援(可選),格式如:3:2, 16:9, 1:1",
|
|
459
|
-
},
|
|
460
|
-
max_wait_minutes: {
|
|
461
|
-
type: "number",
|
|
462
|
-
description: "最大等待時間(分鐘,可選,通義萬相預設5分鐘,老張API即時回應)",
|
|
463
|
-
minimum: 1,
|
|
464
|
-
maximum: 10,
|
|
465
|
-
default: 5,
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
|
-
required: ["prompt"],
|
|
469
|
-
},
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
name: "tongyi_wanxiang_generate_video",
|
|
473
|
-
description: "使用通義萬相圖生視頻完整流程(創建任務 + 等待完成 + 返回視頻URL)",
|
|
474
|
-
inputSchema: {
|
|
475
|
-
type: "object",
|
|
476
|
-
properties: {
|
|
477
|
-
img_url: {
|
|
478
|
-
type: "string",
|
|
479
|
-
description: "首幀圖像的 URL(必需,需為公網可訪問地址)",
|
|
480
|
-
},
|
|
481
|
-
prompt: {
|
|
482
|
-
type: "string",
|
|
483
|
-
description: "文本提示詞,支持中英文,長度不超過800字符(可選)",
|
|
484
|
-
},
|
|
485
|
-
model: {
|
|
486
|
-
type: "string",
|
|
487
|
-
description: "模型名稱 - turbo生成快(3-5分鐘),plus品質高(7-10分鐘)",
|
|
488
|
-
enum: ["wanx2.1-i2v-turbo", "wanx2.1-i2v-plus"],
|
|
489
|
-
default: "wanx2.1-i2v-turbo",
|
|
490
|
-
},
|
|
491
|
-
template: {
|
|
492
|
-
type: "string",
|
|
493
|
-
description: "視頻特效模板(可選):squish(解壓捏捏)、flying(魔法懸浮)、carousel(時光木馬)",
|
|
494
|
-
enum: ["squish", "flying", "carousel"],
|
|
495
|
-
},
|
|
496
|
-
resolution: {
|
|
497
|
-
type: "string",
|
|
498
|
-
description: "視頻分辨率檔位",
|
|
499
|
-
enum: ["480P", "720P"],
|
|
500
|
-
default: "720P",
|
|
501
|
-
},
|
|
502
|
-
duration: {
|
|
503
|
-
type: "number",
|
|
504
|
-
description: "視頻時長(秒)- turbo模型支持3-5秒,plus模型固定5秒",
|
|
505
|
-
minimum: 3,
|
|
506
|
-
maximum: 5,
|
|
507
|
-
default: 5,
|
|
508
|
-
},
|
|
509
|
-
prompt_extend: {
|
|
510
|
-
type: "boolean",
|
|
511
|
-
description: "是否開啟 prompt 智能改寫",
|
|
512
|
-
default: true,
|
|
513
|
-
},
|
|
514
|
-
seed: {
|
|
515
|
-
type: "number",
|
|
516
|
-
description: "隨機數種子",
|
|
517
|
-
minimum: 0,
|
|
518
|
-
maximum: 2147483647,
|
|
519
|
-
},
|
|
520
|
-
max_wait_minutes: {
|
|
521
|
-
type: "number",
|
|
522
|
-
description: "最大等待時間(分鐘,預設15分鐘)",
|
|
523
|
-
minimum: 5,
|
|
524
|
-
maximum: 30,
|
|
525
|
-
default: 15,
|
|
526
|
-
},
|
|
527
|
-
},
|
|
528
|
-
required: ["img_url"],
|
|
529
|
-
},
|
|
530
|
-
},
|
|
531
|
-
{
|
|
532
|
-
name: "chat_completion",
|
|
533
|
-
description: "使用 AI 進行對話",
|
|
534
|
-
inputSchema: {
|
|
535
|
-
type: "object",
|
|
536
|
-
properties: {
|
|
537
|
-
message: {
|
|
538
|
-
type: "string",
|
|
539
|
-
description: "用戶訊息",
|
|
540
|
-
},
|
|
541
|
-
system_prompt: {
|
|
542
|
-
type: "string",
|
|
543
|
-
description: "系統提示詞(可選,如未提供將使用預設值)",
|
|
544
|
-
},
|
|
545
|
-
model: {
|
|
546
|
-
type: "string",
|
|
547
|
-
description: "使用的模型名稱",
|
|
548
|
-
default: "gpt-4",
|
|
549
|
-
},
|
|
550
|
-
},
|
|
551
|
-
required: ["message"],
|
|
552
|
-
},
|
|
553
|
-
},
|
|
554
|
-
{
|
|
555
|
-
name: "get_usage_guide",
|
|
556
|
-
description: "獲取工具使用指南,包含所有可用模型的詳細說明",
|
|
557
|
-
inputSchema: {
|
|
558
|
-
type: "object",
|
|
559
|
-
properties: {},
|
|
560
|
-
required: [],
|
|
561
|
-
},
|
|
562
|
-
},
|
|
563
|
-
],
|
|
564
|
-
};
|
|
565
|
-
});
|
|
566
|
-
// 處理工具調用
|
|
567
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
568
|
-
const { name, arguments: args } = request.params;
|
|
569
|
-
try {
|
|
570
|
-
switch (name) {
|
|
571
|
-
case "generate_image":
|
|
572
|
-
return await this.handleUnifiedImageGeneration(args);
|
|
573
|
-
case "tongyi_wanxiang_generate_video":
|
|
574
|
-
return await this.handleTongyiWanxiangGenerateVideo(args);
|
|
575
|
-
case "chat_completion":
|
|
576
|
-
return await this.handleChatCompletion(args);
|
|
577
|
-
case "get_usage_guide":
|
|
578
|
-
return await this.handleUsageGuide(args);
|
|
579
|
-
// 保持向後兼容
|
|
580
|
-
case "tongyi_wanxiang_generate_image":
|
|
581
|
-
return await this.handleTongyiWanxiangGenerateImage(args);
|
|
582
|
-
case "tongyi_wanxiang_create_task":
|
|
583
|
-
case "dashscope_create_task":
|
|
584
|
-
return await this.handleTongyiWanxiangCreateTask(args);
|
|
585
|
-
case "tongyi_wanxiang_query_task":
|
|
586
|
-
case "dashscope_query_task":
|
|
587
|
-
return await this.handleTongyiWanxiangQueryTask(args);
|
|
588
|
-
case "dashscope_generate_image":
|
|
589
|
-
return await this.handleTongyiWanxiangGenerateImage(args);
|
|
590
|
-
case "tongyi_wanxiang_create_video_task":
|
|
591
|
-
return await this.handleTongyiWanxiangCreateVideoTask(args);
|
|
592
|
-
default:
|
|
593
|
-
throw new McpError(ErrorCode.MethodNotFound, `未知的工具: ${name}`);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
catch (error) {
|
|
597
|
-
if (error instanceof McpError) {
|
|
598
|
-
throw error;
|
|
599
|
-
}
|
|
600
|
-
throw new McpError(ErrorCode.InternalError, `工具執行錯誤: ${error instanceof Error ? error.message : String(error)}`);
|
|
601
|
-
}
|
|
153
|
+
}
|
|
154
|
+
async function queryDashScopeTask(taskId) {
|
|
155
|
+
if (!dashScopeApiKey) {
|
|
156
|
+
throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const response = await axios.get(`https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}`, {
|
|
160
|
+
headers: { Authorization: `Bearer ${dashScopeApiKey}` },
|
|
602
161
|
});
|
|
162
|
+
return response.data;
|
|
603
163
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
throw new
|
|
608
|
-
}
|
|
609
|
-
// 模型資訊
|
|
610
|
-
const modelInfo = {
|
|
611
|
-
'sora_image': { name: 'Sora圖像生成', provider: '老張API', feature: '基於Sora技術的圖像生成,$0.01/次' },
|
|
612
|
-
'gpt-4o-image': { name: 'GPT-4o圖像生成', provider: '老張API', feature: 'GPT-4o視覺模型,$0.01/次' },
|
|
613
|
-
'gemini-2.5-flash-image-preview': { name: 'Gemini 2.5 Flash圖像生成', provider: '老張API', feature: 'Google Gemini 2.5 Flash預覽版,高速生成,$0.01/次' }
|
|
614
|
-
};
|
|
615
|
-
const modelDesc = modelInfo[model];
|
|
616
|
-
// 根據老張API文檔格式,構建正確的請求
|
|
617
|
-
let finalPrompt = prompt;
|
|
618
|
-
// 如果有比例要求,添加到提示詞末尾(如:【3:2】)
|
|
619
|
-
if (aspect_ratio) {
|
|
620
|
-
finalPrompt = `${prompt}【${aspect_ratio}】`;
|
|
621
|
-
}
|
|
622
|
-
const messages = [
|
|
623
|
-
{
|
|
624
|
-
role: "user",
|
|
625
|
-
content: [
|
|
626
|
-
{
|
|
627
|
-
type: "text",
|
|
628
|
-
text: finalPrompt
|
|
629
|
-
}
|
|
630
|
-
]
|
|
631
|
-
}
|
|
632
|
-
];
|
|
633
|
-
const requestData = {
|
|
634
|
-
model,
|
|
635
|
-
n: Math.min(Math.max(n, 1), 4), // 限制在1-4之間
|
|
636
|
-
messages
|
|
637
|
-
};
|
|
638
|
-
try {
|
|
639
|
-
const response = await axios.post(`${this.baseUrl}/chat/completions`, requestData, {
|
|
640
|
-
headers: {
|
|
641
|
-
"Content-Type": "application/json",
|
|
642
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
643
|
-
},
|
|
644
|
-
});
|
|
645
|
-
const result = response.data;
|
|
646
|
-
const generatedContent = result.choices?.[0]?.message?.content || "未生成內容";
|
|
647
|
-
// 使用新的圖片處理邏輯,同時支援 URL 和 base64
|
|
648
|
-
const imageData = await this.processImageContent(generatedContent, prompt, output_format);
|
|
649
|
-
let responseText = `${modelDesc.name}圖像生成完成!\n\n`;
|
|
650
|
-
responseText += `🎨 使用模型: ${modelDesc.name}\n`;
|
|
651
|
-
responseText += `💰 費用: ${modelDesc.feature}\n`;
|
|
652
|
-
responseText += `📝 提示詞: ${finalPrompt}\n`;
|
|
653
|
-
responseText += `🖼️ 生成數量: ${n}張\n`;
|
|
654
|
-
if (system_prompt) {
|
|
655
|
-
responseText += `🔧 系統提示: ${system_prompt}\n`;
|
|
656
|
-
}
|
|
657
|
-
// 優先顯示 base64 圖片的保存結果
|
|
658
|
-
if (imageData.savedImages.length > 0) {
|
|
659
|
-
responseText += `\n💾 圖片已保存到 ${this.formatSaveDirectoryForDisplay()}:\n`;
|
|
660
|
-
imageData.savedImages.forEach((filePath, index) => {
|
|
661
|
-
const displayPath = this.formatFilePathForDisplay(filePath);
|
|
662
|
-
const filename = path.basename(filePath);
|
|
663
|
-
responseText += `${index + 1}. ${filename}\n 完整路徑: ${displayPath}\n`;
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
// 如果有 URL 圖片,也顯示
|
|
667
|
-
if (imageData.imageUrls.length > 0) {
|
|
668
|
-
responseText += `\n🔗 圖片URLs:\n`;
|
|
669
|
-
imageData.imageUrls.forEach((url, index) => {
|
|
670
|
-
responseText += `${index + 1}. ${url}\n`;
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
// 如果沒有找到任何圖片(URL 或 base64),顯示原始回應
|
|
674
|
-
if (!imageData.hasBase64 && imageData.imageUrls.length === 0) {
|
|
675
|
-
responseText += `\n📄 API回應內容:\n${generatedContent}`;
|
|
676
|
-
}
|
|
677
|
-
return {
|
|
678
|
-
content: [
|
|
679
|
-
{
|
|
680
|
-
type: "text",
|
|
681
|
-
text: responseText,
|
|
682
|
-
},
|
|
683
|
-
],
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
catch (error) {
|
|
687
|
-
if (axios.isAxiosError(error)) {
|
|
688
|
-
const axiosError = error;
|
|
689
|
-
const errorMessage = axiosError.response?.data?.error?.message || axiosError.message;
|
|
690
|
-
// 根據文檔建議,如果sora_image失敗,建議切換到其他模型
|
|
691
|
-
let suggestionText = "";
|
|
692
|
-
if (model === "sora_image") {
|
|
693
|
-
suggestionText = "\n💡 提示:如果sora_image持續失敗,建議切換到gpt-4o-image或gemini-2.5-flash-image-preview模型重試";
|
|
694
|
-
}
|
|
695
|
-
throw new McpError(ErrorCode.InternalError, `老張API請求失敗: ${errorMessage}${suggestionText}`);
|
|
696
|
-
}
|
|
697
|
-
throw new McpError(ErrorCode.InternalError, `未知錯誤: ${error instanceof Error ? error.message : String(error)}`);
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (axios.isAxiosError(error)) {
|
|
166
|
+
const errorMessage = error.response?.data?.message || error.message;
|
|
167
|
+
throw new Error(`阿里雲 DashScope 查詢任務失敗: ${errorMessage}`);
|
|
698
168
|
}
|
|
169
|
+
throw new Error(`查詢任務時發生未知錯誤: ${error}`);
|
|
699
170
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
],
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
catch (error) {
|
|
724
|
-
throw new McpError(ErrorCode.InternalError, `創建任務失敗: ${error instanceof Error ? error.message : String(error)}`);
|
|
725
|
-
}
|
|
171
|
+
}
|
|
172
|
+
async function generateImageWithDashScope(prompt, negativePrompt, options) {
|
|
173
|
+
const createResponse = await createDashScopeTask(prompt, negativePrompt, options);
|
|
174
|
+
const taskId = createResponse.output.task_id;
|
|
175
|
+
if (!taskId)
|
|
176
|
+
throw new Error("創建任務失敗,未獲取到任務ID");
|
|
177
|
+
const maxWaitMinutes = options?.maxWaitMinutes || 5;
|
|
178
|
+
const maxAttempts = maxWaitMinutes * 6;
|
|
179
|
+
let attempts = 0;
|
|
180
|
+
while (attempts < maxAttempts) {
|
|
181
|
+
const queryResponse = await queryDashScopeTask(taskId);
|
|
182
|
+
const status = queryResponse.output.task_status;
|
|
183
|
+
if (status === "SUCCEEDED")
|
|
184
|
+
return queryResponse.output.results || [];
|
|
185
|
+
if (status === "FAILED")
|
|
186
|
+
throw new Error(`圖片生成失敗: ${queryResponse.output.message || "未知錯誤"}`);
|
|
187
|
+
if (status === "CANCELED")
|
|
188
|
+
throw new Error("任務已被取消");
|
|
189
|
+
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
190
|
+
attempts++;
|
|
726
191
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
try {
|
|
733
|
-
const response = await this.queryDashScopeTask(task_id);
|
|
734
|
-
const { output } = response;
|
|
735
|
-
let responseText = `通義萬相文生圖任務查詢結果:\n\n`;
|
|
736
|
-
responseText += `任務ID: ${output.task_id}\n`;
|
|
737
|
-
responseText += `任務狀態: ${output.task_status}\n`;
|
|
738
|
-
responseText += `請求ID: ${response.request_id}\n`;
|
|
739
|
-
if (output.submit_time) {
|
|
740
|
-
responseText += `提交時間: ${output.submit_time}\n`;
|
|
741
|
-
}
|
|
742
|
-
if (output.scheduled_time) {
|
|
743
|
-
responseText += `開始時間: ${output.scheduled_time}\n`;
|
|
744
|
-
}
|
|
745
|
-
if (output.end_time) {
|
|
746
|
-
responseText += `完成時間: ${output.end_time}\n`;
|
|
747
|
-
}
|
|
748
|
-
if (output.task_status === "SUCCEEDED" && output.results) {
|
|
749
|
-
responseText += `\n✅ 任務完成!生成了 ${output.results.length} 張圖片:\n`;
|
|
750
|
-
output.results.forEach((result, index) => {
|
|
751
|
-
responseText += `\n圖片 ${index + 1}:\n`;
|
|
752
|
-
responseText += `原始提示詞: ${result.orig_prompt}\n`;
|
|
753
|
-
if (result.actual_prompt) {
|
|
754
|
-
responseText += `優化後提示詞: ${result.actual_prompt}\n`;
|
|
755
|
-
}
|
|
756
|
-
responseText += `圖片URL: ${result.url}\n`;
|
|
757
|
-
});
|
|
758
|
-
if (output.task_metrics) {
|
|
759
|
-
responseText += `\n📊 任務統計:\n`;
|
|
760
|
-
responseText += `總計: ${output.task_metrics.TOTAL}\n`;
|
|
761
|
-
responseText += `成功: ${output.task_metrics.SUCCEEDED}\n`;
|
|
762
|
-
responseText += `失敗: ${output.task_metrics.FAILED}\n`;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
else if (output.task_status === "FAILED") {
|
|
766
|
-
responseText += `\n❌ 任務失敗\n`;
|
|
767
|
-
if (output.message) {
|
|
768
|
-
responseText += `錯誤訊息: ${output.message}\n`;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
else if (output.task_status === "PENDING") {
|
|
772
|
-
responseText += `\n⏳ 任務排隊中,請稍後再次查詢\n`;
|
|
773
|
-
}
|
|
774
|
-
else if (output.task_status === "RUNNING") {
|
|
775
|
-
responseText += `\n🔄 任務處理中,請稍後再次查詢\n`;
|
|
776
|
-
}
|
|
777
|
-
return {
|
|
778
|
-
content: [
|
|
779
|
-
{
|
|
780
|
-
type: "text",
|
|
781
|
-
text: responseText,
|
|
782
|
-
},
|
|
783
|
-
],
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
catch (error) {
|
|
787
|
-
throw new McpError(ErrorCode.InternalError, `查詢任務失敗: ${error instanceof Error ? error.message : String(error)}`);
|
|
788
|
-
}
|
|
192
|
+
throw new Error(`圖片生成超時,等待時間超過 ${maxWaitMinutes} 分鐘`);
|
|
193
|
+
}
|
|
194
|
+
async function createVideoGenerationTask(imgUrl, prompt, options) {
|
|
195
|
+
if (!dashScopeApiKey) {
|
|
196
|
+
throw new Error("ALI_API_KEY 或 DASHSCOPE_API_KEY 環境變數未設定");
|
|
789
197
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
});
|
|
814
|
-
let responseText = `通義萬相文生圖像生成完成!\n\n`;
|
|
815
|
-
responseText += `🎨 使用模型: ${modelDesc.name} (${modelDesc.feature})\n`;
|
|
816
|
-
responseText += `💰 計費: ${modelDesc.price}\n`;
|
|
817
|
-
responseText += `✅ 成功生成 ${results.length} 張圖片\n\n`;
|
|
818
|
-
results.forEach((result, index) => {
|
|
819
|
-
responseText += `圖片 ${index + 1}:\n`;
|
|
820
|
-
responseText += `原始提示詞: ${result.orig_prompt}\n`;
|
|
821
|
-
if (result.actual_prompt) {
|
|
822
|
-
responseText += `優化後提示詞: ${result.actual_prompt}\n`;
|
|
823
|
-
}
|
|
824
|
-
responseText += `圖片URL: ${result.url}\n\n`;
|
|
825
|
-
});
|
|
826
|
-
return {
|
|
827
|
-
content: [
|
|
828
|
-
{
|
|
829
|
-
type: "text",
|
|
830
|
-
text: responseText,
|
|
831
|
-
},
|
|
832
|
-
],
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
catch (error) {
|
|
836
|
-
throw new McpError(ErrorCode.InternalError, `生成圖片失敗: ${error instanceof Error ? error.message : String(error)}`);
|
|
837
|
-
}
|
|
198
|
+
const requestData = {
|
|
199
|
+
model: options?.model || "wanx2.1-i2v-turbo",
|
|
200
|
+
input: {
|
|
201
|
+
img_url: imgUrl,
|
|
202
|
+
prompt,
|
|
203
|
+
template: options?.template,
|
|
204
|
+
},
|
|
205
|
+
parameters: {
|
|
206
|
+
resolution: options?.resolution || "720P",
|
|
207
|
+
duration: options?.duration || 5,
|
|
208
|
+
prompt_extend: options?.promptExtend !== false,
|
|
209
|
+
seed: options?.seed,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
try {
|
|
213
|
+
const response = await axios.post("https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis", requestData, {
|
|
214
|
+
headers: {
|
|
215
|
+
"Content-Type": "application/json",
|
|
216
|
+
Authorization: `Bearer ${dashScopeApiKey}`,
|
|
217
|
+
"X-DashScope-Async": "enable",
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
return response.data;
|
|
838
221
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
throw new
|
|
843
|
-
}
|
|
844
|
-
try {
|
|
845
|
-
const response = await this.createVideoGenerationTask(img_url, prompt, {
|
|
846
|
-
model,
|
|
847
|
-
template,
|
|
848
|
-
resolution,
|
|
849
|
-
duration,
|
|
850
|
-
promptExtend: prompt_extend,
|
|
851
|
-
seed,
|
|
852
|
-
});
|
|
853
|
-
return {
|
|
854
|
-
content: [
|
|
855
|
-
{
|
|
856
|
-
type: "text",
|
|
857
|
-
text: `通義萬相圖生視頻任務創建成功!\n\n任務ID: ${response.output.task_id}\n任務狀態: ${response.output.task_status}\n使用模型: ${model || 'wanx2.1-i2v-turbo'}\n首幀圖像: ${img_url}\n請求ID: ${response.request_id}\n\n請使用 tongyi_wanxiang_query_task 工具查詢任務結果。`,
|
|
858
|
-
},
|
|
859
|
-
],
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
catch (error) {
|
|
863
|
-
throw new McpError(ErrorCode.InternalError, `創建視頻生成任務失敗: ${error instanceof Error ? error.message : String(error)}`);
|
|
222
|
+
catch (error) {
|
|
223
|
+
if (axios.isAxiosError(error)) {
|
|
224
|
+
const errorMessage = error.response?.data?.message || error.message;
|
|
225
|
+
throw new Error(`阿里雲 DashScope 視頻生成任務創建失敗: ${errorMessage}`);
|
|
864
226
|
}
|
|
227
|
+
throw new Error(`創建視頻生成任務時發生未知錯誤: ${error}`);
|
|
865
228
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
const
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
const videoUrl =
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
responseText += `⏱️ 預計生成時間: ${modelDesc.time}\n`;
|
|
891
|
-
responseText += `🖼️ 首幀圖像: ${img_url}\n`;
|
|
892
|
-
responseText += `📐 分辨率: ${resolution || '720P'}\n`;
|
|
893
|
-
responseText += `⏰ 視頻時長: ${duration || 5}秒\n`;
|
|
894
|
-
if (prompt) {
|
|
895
|
-
responseText += `💭 提示詞: ${prompt}\n`;
|
|
896
|
-
}
|
|
897
|
-
if (template) {
|
|
898
|
-
responseText += `✨ 特效模板: ${template}\n`;
|
|
899
|
-
}
|
|
900
|
-
responseText += `\n✅ 視頻生成成功!\n`;
|
|
901
|
-
responseText += `🔗 視頻URL: ${videoUrl}\n`;
|
|
902
|
-
responseText += `\n⚠️ 視頻URL有效期24小時,請及時保存`;
|
|
903
|
-
return {
|
|
904
|
-
content: [
|
|
905
|
-
{
|
|
906
|
-
type: "text",
|
|
907
|
-
text: responseText,
|
|
908
|
-
},
|
|
909
|
-
],
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
catch (error) {
|
|
913
|
-
throw new McpError(ErrorCode.InternalError, `視頻生成失敗: ${error instanceof Error ? error.message : String(error)}`);
|
|
914
|
-
}
|
|
229
|
+
}
|
|
230
|
+
async function generateVideoWithDashScope(imgUrl, prompt, options) {
|
|
231
|
+
const createResponse = await createVideoGenerationTask(imgUrl, prompt, options);
|
|
232
|
+
const taskId = createResponse.output.task_id;
|
|
233
|
+
if (!taskId)
|
|
234
|
+
throw new Error("創建視頻生成任務失敗,未獲取到任務ID");
|
|
235
|
+
const maxWaitMinutes = options?.maxWaitMinutes || 15;
|
|
236
|
+
const maxAttempts = maxWaitMinutes * 6;
|
|
237
|
+
let attempts = 0;
|
|
238
|
+
while (attempts < maxAttempts) {
|
|
239
|
+
const queryResponse = await queryDashScopeTask(taskId);
|
|
240
|
+
const status = queryResponse.output.task_status;
|
|
241
|
+
if (status === "SUCCEEDED") {
|
|
242
|
+
const videoUrl = queryResponse.output.video_url;
|
|
243
|
+
if (!videoUrl)
|
|
244
|
+
throw new Error("任務完成但未獲取到視頻URL");
|
|
245
|
+
return videoUrl;
|
|
246
|
+
}
|
|
247
|
+
if (status === "FAILED")
|
|
248
|
+
throw new Error(`視頻生成失敗: ${queryResponse.output.message || "未知錯誤"}`);
|
|
249
|
+
if (status === "CANCELED")
|
|
250
|
+
throw new Error("視頻生成任務已被取消");
|
|
251
|
+
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
252
|
+
attempts++;
|
|
915
253
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
254
|
+
throw new Error(`視頻生成超時,等待時間超過 ${maxWaitMinutes} 分鐘`);
|
|
255
|
+
}
|
|
256
|
+
// ===== MCP Server =====
|
|
257
|
+
const TONGYI_MODELS = ["wanx2.1-t2i-turbo", "wanx2.1-t2i-plus", "wanx2.0-t2i-turbo"];
|
|
258
|
+
const LAOZHANG_MODELS = ["sora_image", "gpt-4o-image", "gemini-2.5-flash-image-preview"];
|
|
259
|
+
const ALL_IMAGE_MODELS = [...TONGYI_MODELS, ...LAOZHANG_MODELS];
|
|
260
|
+
const server = new McpServer({ name: "ai-image-chat-mcp-server", version: packageJson.version }, { capabilities: { logging: {} } });
|
|
261
|
+
// ----- generate_image -----
|
|
262
|
+
server.tool("generate_image", "統一圖像生成工具,支援通義萬相和老張API多種模型。預設使用 sora_image。", {
|
|
263
|
+
prompt: z.string().describe("圖像生成的提示詞"),
|
|
264
|
+
model: z.enum(ALL_IMAGE_MODELS).default("sora_image").describe("選擇圖像生成模型(預設: sora_image)"),
|
|
265
|
+
negative_prompt: z.string().default("人物").describe("反向提示詞,僅通義萬相模型支援").optional(),
|
|
266
|
+
size: z.string().default("1024*1024").describe("輸出圖像的分辨率,僅通義萬相模型支援").optional(),
|
|
267
|
+
n: z.number().min(1).max(4).default(1).describe("生成圖片數量(1-4)").optional(),
|
|
268
|
+
seed: z.number().min(0).max(2147483647).describe("隨機數種子,僅通義萬相模型支援").optional(),
|
|
269
|
+
prompt_extend: z.boolean().default(true).describe("是否開啟 prompt 智能改寫,僅通義萬相模型支援").optional(),
|
|
270
|
+
watermark: z.boolean().default(false).describe("是否添加水印標識,僅通義萬相模型支援").optional(),
|
|
271
|
+
output_format: z.enum(["jpg", "jpeg", "png", "webp"]).default("jpg").describe("輸出圖片格式").optional(),
|
|
272
|
+
system_prompt: z.string().describe("系統提示詞,僅老張API模型支援").optional(),
|
|
273
|
+
aspect_ratio: z.string().describe("圖片比例,僅老張API模型支援,格式如:3:2, 16:9, 1:1").optional(),
|
|
274
|
+
max_wait_minutes: z.number().min(1).max(10).default(5).describe("最大等待時間(分鐘,通義萬相預設5分鐘,老張API即時回應)").optional(),
|
|
275
|
+
}, async (args) => {
|
|
276
|
+
const { prompt, model = "sora_image", negative_prompt, size, n = 1, seed, prompt_extend, watermark, output_format = "jpg", system_prompt, aspect_ratio, max_wait_minutes, } = args;
|
|
277
|
+
if (TONGYI_MODELS.includes(model)) {
|
|
278
|
+
return handleTongyiImageGeneration({
|
|
279
|
+
prompt, model, negative_prompt, size, n, seed,
|
|
280
|
+
prompt_extend, watermark, output_format, max_wait_minutes,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return handleLaozhangImageGeneration({
|
|
284
|
+
prompt, model, system_prompt, output_format, n, aspect_ratio,
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
// ----- tongyi_wanxiang_generate_video -----
|
|
288
|
+
server.tool("tongyi_wanxiang_generate_video", "使用通義萬相圖生視頻完整流程(創建任務 + 等待完成 + 返回視頻URL)", {
|
|
289
|
+
img_url: z.string().describe("首幀圖像的 URL(必需,需為公網可訪問地址)"),
|
|
290
|
+
prompt: z.string().describe("文本提示詞,支持中英文,長度不超過800字符").optional(),
|
|
291
|
+
model: z.enum(["wanx2.1-i2v-turbo", "wanx2.1-i2v-plus"]).default("wanx2.1-i2v-turbo").describe("模型名稱 - turbo生成快(3-5分鐘),plus品質高(7-10分鐘)").optional(),
|
|
292
|
+
template: z.enum(["squish", "flying", "carousel"]).describe("視頻特效模板:squish(解壓捏捏)、flying(魔法懸浮)、carousel(時光木馬)").optional(),
|
|
293
|
+
resolution: z.enum(["480P", "720P"]).default("720P").describe("視頻分辨率檔位").optional(),
|
|
294
|
+
duration: z.number().min(3).max(5).default(5).describe("視頻時長(秒)- turbo模型支持3-5秒,plus模型固定5秒").optional(),
|
|
295
|
+
prompt_extend: z.boolean().default(true).describe("是否開啟 prompt 智能改寫").optional(),
|
|
296
|
+
seed: z.number().min(0).max(2147483647).describe("隨機數種子").optional(),
|
|
297
|
+
max_wait_minutes: z.number().min(5).max(30).default(15).describe("最大等待時間(分鐘,預設15分鐘)").optional(),
|
|
298
|
+
}, async (args) => {
|
|
299
|
+
const { img_url, prompt, model = "wanx2.1-i2v-turbo", template, resolution = "720P", duration = 5, prompt_extend, seed, max_wait_minutes = 15, } = args;
|
|
300
|
+
const modelInfo = {
|
|
301
|
+
"wanx2.1-i2v-turbo": { name: "通義萬相2.1圖生視頻-Turbo", time: "3-5分鐘", feature: "生成速度快" },
|
|
302
|
+
"wanx2.1-i2v-plus": { name: "通義萬相2.1圖生視頻-Plus", time: "7-10分鐘", feature: "視頻品質高" },
|
|
303
|
+
};
|
|
304
|
+
const desc = modelInfo[model];
|
|
305
|
+
const videoUrl = await generateVideoWithDashScope(img_url, prompt, {
|
|
306
|
+
model, template, resolution, duration,
|
|
307
|
+
promptExtend: prompt_extend, seed, maxWaitMinutes: max_wait_minutes,
|
|
308
|
+
});
|
|
309
|
+
let text = `通義萬相圖生視頻生成完成!\n\n`;
|
|
310
|
+
text += `🎬 使用模型: ${desc.name} (${desc.feature})\n`;
|
|
311
|
+
text += `⏱️ 預計生成時間: ${desc.time}\n`;
|
|
312
|
+
text += `🖼️ 首幀圖像: ${img_url}\n`;
|
|
313
|
+
text += `📐 分辨率: ${resolution}\n`;
|
|
314
|
+
text += `⏰ 視頻時長: ${duration}秒\n`;
|
|
315
|
+
if (prompt)
|
|
316
|
+
text += `💭 提示詞: ${prompt}\n`;
|
|
317
|
+
if (template)
|
|
318
|
+
text += `✨ 特效模板: ${template}\n`;
|
|
319
|
+
text += `\n✅ 視頻生成成功!\n`;
|
|
320
|
+
text += `🔗 視頻URL: ${videoUrl}\n`;
|
|
321
|
+
text += `\n⚠️ 視頻URL有效期24小時,請及時保存`;
|
|
322
|
+
return { content: [{ type: "text", text }] };
|
|
323
|
+
});
|
|
324
|
+
// ----- chat_completion -----
|
|
325
|
+
server.tool("chat_completion", "使用 AI 進行對話", {
|
|
326
|
+
message: z.string().describe("用戶訊息"),
|
|
327
|
+
system_prompt: z.string().default("You are a helpful assistant.").describe("系統提示詞").optional(),
|
|
328
|
+
model: z.string().default("gpt-4o").describe("使用的模型名稱(預設: gpt-4o)").optional(),
|
|
329
|
+
}, async (args) => {
|
|
330
|
+
const { message, system_prompt = "You are a helpful assistant.", model = "gpt-4o" } = args;
|
|
331
|
+
const response = await axios.post(`${baseUrl}/chat/completions`, {
|
|
332
|
+
model,
|
|
333
|
+
messages: [
|
|
922
334
|
{ role: "system", content: system_prompt },
|
|
923
335
|
{ role: "user", content: message },
|
|
924
|
-
]
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
336
|
+
],
|
|
337
|
+
}, {
|
|
338
|
+
headers: {
|
|
339
|
+
"Content-Type": "application/json",
|
|
340
|
+
Authorization: `Bearer ${apiKey}`,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
const assistantMessage = response.data.choices[0]?.message?.content || "未收到回覆";
|
|
344
|
+
return { content: [{ type: "text", text: `助手回覆:\n\n${assistantMessage}` }] };
|
|
345
|
+
});
|
|
346
|
+
// ----- get_usage_guide -----
|
|
347
|
+
server.tool("get_usage_guide", "獲取工具使用指南,包含所有可用模型的詳細說明", {}, async () => {
|
|
348
|
+
return { content: [{ type: "text", text: buildUsageGuide() }] };
|
|
349
|
+
});
|
|
350
|
+
// ===== 處理函式 =====
|
|
351
|
+
async function handleTongyiImageGeneration(args) {
|
|
352
|
+
const modelInfo = {
|
|
353
|
+
"wanx2.1-t2i-turbo": { name: "通義萬相2.1-Turbo", price: "0.14元/張", feature: "生成速度更快" },
|
|
354
|
+
"wanx2.1-t2i-plus": { name: "通義萬相2.1-Plus", price: "0.20元/張", feature: "圖像細節更豐富" },
|
|
355
|
+
"wanx2.0-t2i-turbo": { name: "通義萬相2.0-Turbo", price: "0.04元/張", feature: "質感人像與創意設計" },
|
|
356
|
+
};
|
|
357
|
+
const desc = modelInfo[args.model];
|
|
358
|
+
const results = await generateImageWithDashScope(args.prompt, args.negative_prompt, {
|
|
359
|
+
model: args.model,
|
|
360
|
+
size: args.size,
|
|
361
|
+
n: args.n,
|
|
362
|
+
seed: args.seed,
|
|
363
|
+
promptExtend: args.prompt_extend,
|
|
364
|
+
watermark: args.watermark,
|
|
365
|
+
maxWaitMinutes: args.max_wait_minutes,
|
|
366
|
+
});
|
|
367
|
+
let text = `通義萬相文生圖像生成完成!\n\n`;
|
|
368
|
+
text += `🎨 使用模型: ${desc.name} (${desc.feature})\n`;
|
|
369
|
+
text += `💰 計費: ${desc.price}\n`;
|
|
370
|
+
text += `✅ 成功生成 ${results.length} 張圖片\n\n`;
|
|
371
|
+
for (const [i, result] of results.entries()) {
|
|
372
|
+
text += `圖片 ${i + 1}:\n`;
|
|
373
|
+
text += `原始提示詞: ${result.orig_prompt}\n`;
|
|
374
|
+
if (result.actual_prompt)
|
|
375
|
+
text += `優化後提示詞: ${result.actual_prompt}\n`;
|
|
376
|
+
text += `圖片URL: ${result.url}\n\n`;
|
|
955
377
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
378
|
+
return { content: [{ type: "text", text }] };
|
|
379
|
+
}
|
|
380
|
+
async function handleLaozhangImageGeneration(args) {
|
|
381
|
+
const { prompt, model, system_prompt, output_format = "jpg", n = 1, aspect_ratio } = args;
|
|
382
|
+
const modelInfo = {
|
|
383
|
+
sora_image: { name: "Sora圖像生成", feature: "基於Sora技術的圖像生成,$0.01/次" },
|
|
384
|
+
"gpt-4o-image": { name: "GPT-4o圖像生成", feature: "GPT-4o視覺模型,$0.01/次" },
|
|
385
|
+
"gemini-2.5-flash-image-preview": { name: "Gemini 2.5 Flash圖像生成", feature: "Google Gemini 2.5 Flash預覽版,高速生成,$0.01/次" },
|
|
386
|
+
};
|
|
387
|
+
const desc = modelInfo[model];
|
|
388
|
+
const finalPrompt = aspect_ratio ? `${prompt}【${aspect_ratio}】` : prompt;
|
|
389
|
+
const response = await axios.post(`${baseUrl}/chat/completions`, {
|
|
390
|
+
model,
|
|
391
|
+
n: Math.min(Math.max(n, 1), 4),
|
|
392
|
+
messages: [{ role: "user", content: [{ type: "text", text: finalPrompt }] }],
|
|
393
|
+
}, {
|
|
394
|
+
headers: {
|
|
395
|
+
"Content-Type": "application/json",
|
|
396
|
+
Authorization: `Bearer ${apiKey}`,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
const generatedContent = response.data.choices?.[0]?.message?.content || "未生成內容";
|
|
400
|
+
const imageData = await processImageContent(generatedContent, prompt, output_format);
|
|
401
|
+
let text = `${desc.name}圖像生成完成!\n\n`;
|
|
402
|
+
text += `🎨 使用模型: ${desc.name}\n`;
|
|
403
|
+
text += `💰 費用: ${desc.feature}\n`;
|
|
404
|
+
text += `📝 提示詞: ${finalPrompt}\n`;
|
|
405
|
+
text += `🖼️ 生成數量: ${n}張\n`;
|
|
406
|
+
if (system_prompt)
|
|
407
|
+
text += `🔧 系統提示: ${system_prompt}\n`;
|
|
408
|
+
if (imageData.savedImages.length > 0) {
|
|
409
|
+
text += `\n💾 圖片已保存到 ${formatPathForDisplay(saveDirectory)}:\n`;
|
|
410
|
+
for (const [i, filePath] of imageData.savedImages.entries()) {
|
|
411
|
+
text += `${i + 1}. ${path.basename(filePath)}\n 完整路徑: ${formatPathForDisplay(filePath)}\n`;
|
|
989
412
|
}
|
|
990
|
-
|
|
991
|
-
|
|
413
|
+
}
|
|
414
|
+
if (imageData.imageUrls.length > 0) {
|
|
415
|
+
text += `\n🔗 圖片URLs:\n`;
|
|
416
|
+
for (const [i, url] of imageData.imageUrls.entries()) {
|
|
417
|
+
text += `${i + 1}. ${url}\n`;
|
|
992
418
|
}
|
|
993
419
|
}
|
|
994
|
-
|
|
995
|
-
|
|
420
|
+
if (!imageData.hasBase64 && imageData.imageUrls.length === 0) {
|
|
421
|
+
text += `\n📄 API回應內容:\n${generatedContent}`;
|
|
422
|
+
}
|
|
423
|
+
return { content: [{ type: "text", text }] };
|
|
424
|
+
}
|
|
425
|
+
// ===== 使用指南 =====
|
|
426
|
+
function buildUsageGuide() {
|
|
427
|
+
return `
|
|
996
428
|
# AI Image Chat MCP 工具使用指南
|
|
997
429
|
|
|
998
430
|
**版本**: v${packageJson.version}
|
|
999
|
-
**最後更新**: ${new Date().toLocaleDateString(
|
|
431
|
+
**最後更新**: ${new Date().toLocaleDateString("zh-TW")}
|
|
1000
432
|
**支援模型**: 6 種圖像生成模型 + 2 種視頻生成模型
|
|
1001
433
|
|
|
1002
434
|
## 🎨 統一圖像生成工具
|
|
@@ -1006,50 +438,52 @@ class AIImageChatMCPServer {
|
|
|
1006
438
|
|
|
1007
439
|
**可用模型**:
|
|
1008
440
|
|
|
441
|
+
### 老張API模型(即時回應,$0.01/次)
|
|
442
|
+
|
|
443
|
+
1. **sora_image** ⭐ (預設)
|
|
444
|
+
- 提供商: 老張API
|
|
445
|
+
- 特點: 基於Sora技術的圖像生成
|
|
446
|
+
- 生成時間: 即時回應
|
|
447
|
+
- 適合: 快速圖像生成、創意探索、首選模型
|
|
448
|
+
|
|
449
|
+
2. **gpt-4o-image**
|
|
450
|
+
- 提供商: 老張API
|
|
451
|
+
- 特點: GPT-4o視覺模型
|
|
452
|
+
- 生成時間: 即時回應
|
|
453
|
+
- 適合: 智能圖像理解和生成
|
|
454
|
+
|
|
455
|
+
3. **gemini-2.5-flash-image-preview**
|
|
456
|
+
- 提供商: 老張API
|
|
457
|
+
- 特點: Google Gemini 2.5 Flash預覽版,高速生成
|
|
458
|
+
- 生成時間: 即時回應
|
|
459
|
+
- 適合: 高速生成、高效批量生成
|
|
460
|
+
|
|
1009
461
|
### 通義萬相模型(阿里雲DashScope)
|
|
1010
|
-
|
|
462
|
+
|
|
463
|
+
4. **wanx2.1-t2i-turbo**
|
|
1011
464
|
- 提供商: 阿里雲
|
|
1012
465
|
- 價格: 0.14元/張
|
|
1013
466
|
- 特點: 生成速度更快
|
|
1014
467
|
- 生成時間: 1-3分鐘
|
|
1015
468
|
- 適合: 快速原型和測試
|
|
1016
469
|
|
|
1017
|
-
|
|
470
|
+
5. **wanx2.1-t2i-plus**
|
|
1018
471
|
- 提供商: 阿里雲
|
|
1019
472
|
- 價格: 0.20元/張
|
|
1020
473
|
- 特點: 圖像細節更豐富
|
|
1021
474
|
- 生成時間: 1-3分鐘
|
|
1022
475
|
- 適合: 高品質作品和商業用途
|
|
1023
476
|
|
|
1024
|
-
|
|
477
|
+
6. **wanx2.0-t2i-turbo**
|
|
1025
478
|
- 提供商: 阿里雲
|
|
1026
479
|
- 價格: 0.04元/張
|
|
1027
480
|
- 特點: 質感人像與創意設計
|
|
1028
481
|
- 生成時間: 1-3分鐘
|
|
1029
482
|
- 適合: 成本控制和批量生成
|
|
1030
483
|
|
|
1031
|
-
### 老張API模型(即時回應,$0.01/次)
|
|
1032
|
-
4. **gemini-2.5-flash-image-preview** ⭐ (預設)
|
|
1033
|
-
- 提供商: 老張API
|
|
1034
|
-
- 特點: Google Gemini 2.5 Flash預覽版,高速生成
|
|
1035
|
-
- 生成時間: 即時回應
|
|
1036
|
-
- 適合: 快速原型設計、高效批量生成、首選模型
|
|
1037
|
-
|
|
1038
|
-
5. **sora_image**
|
|
1039
|
-
- 提供商: 老張API
|
|
1040
|
-
- 特點: 基於Sora技術的圖像生成
|
|
1041
|
-
- 生成時間: 即時回應
|
|
1042
|
-
- 適合: 快速圖像生成、創意探索
|
|
1043
|
-
|
|
1044
|
-
6. **gpt-4o-image**
|
|
1045
|
-
- 提供商: 老張API
|
|
1046
|
-
- 特點: GPT-4o視覺模型
|
|
1047
|
-
- 生成時間: 即時回應
|
|
1048
|
-
- 適合: 智能圖像理解和生成
|
|
1049
|
-
|
|
1050
484
|
**主要參數**:
|
|
1051
485
|
- \`prompt\`: 圖像生成提示詞 (必需)
|
|
1052
|
-
- \`model\`: 模型選擇 (可選,預設: "
|
|
486
|
+
- \`model\`: 模型選擇 (可選,預設: "sora_image")
|
|
1053
487
|
|
|
1054
488
|
**通義萬相模型專用參數**:
|
|
1055
489
|
- \`negative_prompt\`: 反向提示詞 (可選,預設: "人物")
|
|
@@ -1062,6 +496,7 @@ class AIImageChatMCPServer {
|
|
|
1062
496
|
|
|
1063
497
|
**老張API模型專用參數**:
|
|
1064
498
|
- \`system_prompt\`: 系統提示詞 (可選)
|
|
499
|
+
- \`aspect_ratio\`: 圖片比例 (可選,格式如 "3:2", "16:9")
|
|
1065
500
|
- \`output_format\`: 圖片格式 (可選,預設: "jpg")
|
|
1066
501
|
|
|
1067
502
|
---
|
|
@@ -1072,39 +507,10 @@ class AIImageChatMCPServer {
|
|
|
1072
507
|
**功能**: 使用通義萬相圖生視頻完整流程(創建任務 + 等待完成 + 返回視頻URL)
|
|
1073
508
|
|
|
1074
509
|
**可用模型**:
|
|
1075
|
-
1. **wanx2.1-i2v-turbo** (預設)
|
|
1076
|
-
|
|
1077
|
-
- 特點: 生成速度快
|
|
1078
|
-
- 支援時長: 3-5秒
|
|
1079
|
-
- 支援分辨率: 480P, 720P
|
|
510
|
+
1. **wanx2.1-i2v-turbo** (預設) - 生成快(3-5分鐘),支援3-5秒
|
|
511
|
+
2. **wanx2.1-i2v-plus** - 品質高(7-10分鐘),固定5秒
|
|
1080
512
|
|
|
1081
|
-
|
|
1082
|
-
- 生成時間: 7-10分鐘
|
|
1083
|
-
- 特點: 視頻品質高
|
|
1084
|
-
- 支援時長: 固定5秒
|
|
1085
|
-
- 支援分辨率: 僅720P
|
|
1086
|
-
|
|
1087
|
-
**主要參數**:
|
|
1088
|
-
- \`img_url\`: 首幀圖像URL (必需,需為公網可訪問地址)
|
|
1089
|
-
- \`prompt\`: 文本提示詞 (可選,長度不超過800字符)
|
|
1090
|
-
- \`model\`: 模型選擇 (可選)
|
|
1091
|
-
- \`template\`: 視頻特效模板 (可選)
|
|
1092
|
-
- \`resolution\`: 分辨率檔位 (可選,預設: "720P")
|
|
1093
|
-
- \`duration\`: 視頻時長 (可選,預設: 5秒)
|
|
1094
|
-
- \`prompt_extend\`: 智能改寫 (可選,預設: true)
|
|
1095
|
-
- \`seed\`: 隨機種子 (可選)
|
|
1096
|
-
- \`max_wait_minutes\`: 最大等待時間 (可選,預設: 15分鐘)
|
|
1097
|
-
|
|
1098
|
-
**視頻特效模板**:
|
|
1099
|
-
- \`squish\`: 解壓捏捏效果
|
|
1100
|
-
- \`flying\`: 魔法懸浮效果
|
|
1101
|
-
- \`carousel\`: 時光木馬效果
|
|
1102
|
-
|
|
1103
|
-
**圖像要求**:
|
|
1104
|
-
- 格式: JPEG、JPG、PNG、BMP、WEBP
|
|
1105
|
-
- 尺寸: 360-2000像素(寬度和高度)
|
|
1106
|
-
- 檔案大小: 不超過10MB
|
|
1107
|
-
- URL: 必須為公網可訪問地址
|
|
513
|
+
**視頻特效模板**: squish(解壓捏捏)、flying(魔法懸浮)、carousel(時光木馬)
|
|
1108
514
|
|
|
1109
515
|
---
|
|
1110
516
|
|
|
@@ -1112,276 +518,107 @@ class AIImageChatMCPServer {
|
|
|
1112
518
|
|
|
1113
519
|
### chat_completion
|
|
1114
520
|
**功能**: 使用 AI 進行對話
|
|
1115
|
-
|
|
1116
|
-
**主要參數**:
|
|
1117
521
|
- \`message\`: 用戶訊息 (必需)
|
|
1118
522
|
- \`system_prompt\`: 系統提示詞 (可選)
|
|
1119
|
-
- \`model\`: 模型名稱 (可選,預設: "gpt-
|
|
523
|
+
- \`model\`: 模型名稱 (可選,預設: "gpt-4o")
|
|
1120
524
|
|
|
1121
525
|
---
|
|
1122
526
|
|
|
1123
527
|
## 📋 使用範例
|
|
1124
528
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
#### 1. gemini-2.5-flash-image-preview (預設) - 高速生成
|
|
1128
|
-
**特點**: Gemini 2.5 Flash預覽版 | **價格**: $0.01/次 | **時間**: 即時回應
|
|
1129
|
-
\`\`\`json
|
|
1130
|
-
{
|
|
1131
|
-
"name": "generate_image",
|
|
1132
|
-
"arguments": {
|
|
1133
|
-
"prompt": "一隻可愛的橘貓在陽光下睡覺",
|
|
1134
|
-
"model": "gemini-2.5-flash-image-preview",
|
|
1135
|
-
"aspect_ratio": "16:9",
|
|
1136
|
-
"n": 2
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
\`\`\`
|
|
1140
|
-
|
|
1141
|
-
#### 2. wanx2.1-t2i-turbo - 速度優先
|
|
1142
|
-
**特點**: 生成速度快 | **價格**: 0.14元/張 | **時間**: 1-3分鐘
|
|
1143
|
-
\`\`\`json
|
|
1144
|
-
{
|
|
1145
|
-
"name": "generate_image",
|
|
1146
|
-
"arguments": {
|
|
1147
|
-
"prompt": "一隻可愛的橘貓在陽光下睡覺",
|
|
1148
|
-
"model": "wanx2.1-t2i-turbo",
|
|
1149
|
-
"size": "1024*1024",
|
|
1150
|
-
"n": 1,
|
|
1151
|
-
"negative_prompt": "人物,文字,低質量",
|
|
1152
|
-
"prompt_extend": true,
|
|
1153
|
-
"watermark": false,
|
|
1154
|
-
"max_wait_minutes": 5
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
\`\`\`
|
|
1158
|
-
|
|
1159
|
-
#### 2. wanx2.1-t2i-turbo - 速度優先
|
|
1160
|
-
**特點**: 生成速度快 | **價格**: 0.14元/張 | **時間**: 1-3分鐘
|
|
1161
|
-
\`\`\`json
|
|
1162
|
-
{
|
|
1163
|
-
"name": "generate_image",
|
|
1164
|
-
"arguments": {
|
|
1165
|
-
"prompt": "一隻可愛的橘貓在陽光下睡覺",
|
|
1166
|
-
"model": "wanx2.1-t2i-turbo",
|
|
1167
|
-
"size": "1024*1024",
|
|
1168
|
-
"n": 1,
|
|
1169
|
-
"negative_prompt": "人物,文字,低質量",
|
|
1170
|
-
"prompt_extend": true,
|
|
1171
|
-
"watermark": false,
|
|
1172
|
-
"max_wait_minutes": 5
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
\`\`\`
|
|
1176
|
-
|
|
1177
|
-
#### 3. wanx2.1-t2i-plus - 品質優先
|
|
1178
|
-
**特點**: 圖像細節豐富 | **價格**: 0.20元/張 | **時間**: 1-3分鐘
|
|
1179
|
-
\`\`\`json
|
|
1180
|
-
{
|
|
1181
|
-
"name": "generate_image",
|
|
1182
|
-
"arguments": {
|
|
1183
|
-
"prompt": "精美的日式庭院,櫻花飄落,寧靜祥和",
|
|
1184
|
-
"model": "wanx2.1-t2i-plus",
|
|
1185
|
-
"size": "1024*1024",
|
|
1186
|
-
"n": 2,
|
|
1187
|
-
"negative_prompt": "人物,文字,模糊",
|
|
1188
|
-
"seed": 12345,
|
|
1189
|
-
"prompt_extend": true,
|
|
1190
|
-
"output_format": "jpg"
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
\`\`\`
|
|
1194
|
-
|
|
1195
|
-
#### 4. wanx2.0-t2i-turbo - 性價比首選
|
|
1196
|
-
**特點**: 質感人像與創意設計 | **價格**: 0.04元/張 | **時間**: 1-3分鐘
|
|
1197
|
-
\`\`\`json
|
|
1198
|
-
{
|
|
1199
|
-
"name": "generate_image",
|
|
1200
|
-
"arguments": {
|
|
1201
|
-
"prompt": "時尚的年輕女性肖像,現代都市背景",
|
|
1202
|
-
"model": "wanx2.0-t2i-turbo",
|
|
1203
|
-
"size": "1024*1024",
|
|
1204
|
-
"n": 4,
|
|
1205
|
-
"negative_prompt": "醜陋,變形,低分辨率",
|
|
1206
|
-
"max_wait_minutes": 3
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
\`\`\`
|
|
1210
|
-
|
|
1211
|
-
#### 5. sora_image - 即時生成
|
|
1212
|
-
**特點**: 基於Sora技術 | **價格**: $0.01/次 | **時間**: 即時回應
|
|
529
|
+
#### 1. sora_image (預設) - 即時生成
|
|
1213
530
|
\`\`\`json
|
|
1214
531
|
{
|
|
1215
532
|
"name": "generate_image",
|
|
1216
533
|
"arguments": {
|
|
1217
534
|
"prompt": "夢幻般的未來城市,科技感十足,霓虹燈光",
|
|
1218
|
-
"model": "sora_image",
|
|
1219
|
-
"n": 4,
|
|
1220
535
|
"aspect_ratio": "16:9"
|
|
1221
536
|
}
|
|
1222
537
|
}
|
|
1223
538
|
\`\`\`
|
|
1224
539
|
|
|
1225
|
-
####
|
|
1226
|
-
**特點**: GPT-4o視覺模型 | **價格**: $0.01/次 | **時間**: 即時回應
|
|
540
|
+
#### 2. gpt-4o-image - 智能生成
|
|
1227
541
|
\`\`\`json
|
|
1228
542
|
{
|
|
1229
543
|
"name": "generate_image",
|
|
1230
544
|
"arguments": {
|
|
1231
545
|
"prompt": "可愛的卡通動物們在森林裡開派對",
|
|
1232
546
|
"model": "gpt-4o-image",
|
|
1233
|
-
"n": 2,
|
|
1234
547
|
"aspect_ratio": "3:2",
|
|
1235
548
|
"system_prompt": "Create a colorful, family-friendly cartoon style image"
|
|
1236
549
|
}
|
|
1237
550
|
}
|
|
1238
551
|
\`\`\`
|
|
1239
552
|
|
|
1240
|
-
|
|
553
|
+
#### 3. gemini-2.5-flash-image-preview - 高速生成
|
|
1241
554
|
\`\`\`json
|
|
1242
555
|
{
|
|
1243
556
|
"name": "generate_image",
|
|
1244
557
|
"arguments": {
|
|
1245
558
|
"prompt": "賽博朋克風格的東京街道,雨夜霓虹",
|
|
1246
559
|
"model": "gemini-2.5-flash-image-preview",
|
|
1247
|
-
"n": 3,
|
|
1248
560
|
"aspect_ratio": "16:9"
|
|
1249
561
|
}
|
|
1250
562
|
}
|
|
1251
563
|
\`\`\`
|
|
1252
564
|
|
|
1253
|
-
####
|
|
1254
|
-
**展示不同比例效果**
|
|
565
|
+
#### 4. wanx2.1-t2i-plus - 品質優先
|
|
1255
566
|
\`\`\`json
|
|
1256
567
|
{
|
|
1257
568
|
"name": "generate_image",
|
|
1258
569
|
"arguments": {
|
|
1259
|
-
"prompt": "
|
|
1260
|
-
"model": "
|
|
1261
|
-
"
|
|
1262
|
-
"n":
|
|
570
|
+
"prompt": "精美的日式庭院,櫻花飄落,寧靜祥和",
|
|
571
|
+
"model": "wanx2.1-t2i-plus",
|
|
572
|
+
"size": "1024*1024",
|
|
573
|
+
"n": 2,
|
|
574
|
+
"negative_prompt": "人物,文字,模糊"
|
|
1263
575
|
}
|
|
1264
576
|
}
|
|
1265
577
|
\`\`\`
|
|
1266
578
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
#### 🔧 通用參數 (所有模型)
|
|
1270
|
-
- \`**prompt**\` (必需): 圖像生成的描述詞
|
|
1271
|
-
- \`**model**\` (可選): 選擇生成模型,預設 "gemini-2.5-flash-image-preview"
|
|
1272
|
-
- \`**output_format**\` (可選): 圖片格式 ["jpg", "jpeg", "png", "webp"],預設 "jpg"
|
|
1273
|
-
|
|
1274
|
-
#### ⚙️ 通義萬相專用參數 (wanx2.x 模型)
|
|
1275
|
-
- \`**negative_prompt**\` (可選): 反向提示詞,描述不希望出現的內容,預設 "人物"
|
|
1276
|
-
- \`**size**\` (可選): 圖像尺寸,預設 "1024*1024"
|
|
1277
|
-
- 支援: "512*512", "768*768", "1024*1024", "1280*720", "720*1280"
|
|
1278
|
-
- \`**n**\` (可選): 生成圖片數量,範圍 1-4,預設 1
|
|
1279
|
-
- \`**seed**\` (可選): 隨機種子 (0-2147483647),用於重現相同結果
|
|
1280
|
-
- \`**prompt_extend**\` (可選): 智能改寫提示詞,預設 true
|
|
1281
|
-
- \`**watermark**\` (可選): 添加水印標識,預設 false
|
|
1282
|
-
- \`**max_wait_minutes**\` (可選): 最大等待時間 (1-10分鐘),預設 5分鐘
|
|
1283
|
-
|
|
1284
|
-
#### 🤖 老張API專用參數 (sora_image, gpt-4o-image, gemini-2.5-flash-image-preview)
|
|
1285
|
-
- \`**system_prompt**\` (可選): 系統提示詞,用於指導AI生成風格
|
|
1286
|
-
- \`**aspect_ratio**\` (可選): 圖片比例,格式如 "3:2", "16:9", "1:1" (會添加到提示詞末尾)
|
|
1287
|
-
- \`**n**\` (可選): 生成圖片數量,範圍 1-4,預設 1 (注意:老張API按次計費,每次$0.01)
|
|
1288
|
-
- \`**max_wait_minutes**\` (可選): 最大等待時間,預設 5分鐘 (通常即時回應)
|
|
1289
|
-
|
|
1290
|
-
### 💰 模型價格對比表
|
|
1291
|
-
|
|
1292
|
-
| 模型 | 提供商 | 價格 | 生成時間 | 適用場景 |
|
|
1293
|
-
|------|---------|------|----------|----------|
|
|
1294
|
-
| **wanx2.0-t2i-turbo** | 阿里雲 | **0.04元/張** | 1-3分鐘 | 💰 批量生成、成本控制 |
|
|
1295
|
-
| **wanx2.1-t2i-turbo** | 阿里雲 | 0.14元/張 | 1-3分鐘 | ⚡ 快速原型、測試 |
|
|
1296
|
-
| **wanx2.1-t2i-plus** | 阿里雲 | 0.20元/張 | 1-3分鐘 | 🎨 高品質、商業用途 |
|
|
1297
|
-
| **sora_image** | 老張API | **$0.01/次** | 即時 | 🚀 即時生成、創意探索 |
|
|
1298
|
-
| **gpt-4o-image** | 老張API | **$0.01/次** | 即時 | 🤖 智能理解、複雜場景 |
|
|
1299
|
-
| **gemini-2.5-flash-image-preview** | 老張API | **$0.01/次** | 即時 | ⚡ 高速生成、高效處理 |
|
|
1300
|
-
|
|
1301
|
-
### 💡 老張API使用說明
|
|
1302
|
-
- **計費方式**: 按次計費,每次調用 $0.01,不論生成幾張圖片
|
|
1303
|
-
- **比例控制**: 在提示詞末尾自動添加【比例】格式,如【3:2】
|
|
1304
|
-
- **重試機制**: 如果 sora_image 失敗,建議切換到 gpt-4o-image 或 gemini-2.5-flash-image-preview
|
|
1305
|
-
- **數量控制**: 使用 n 參數控制生成數量 (1-4張)
|
|
1306
|
-
- **格式特點**: 基於 ChatGPT PLUS 用戶的生圖請求模擬
|
|
1307
|
-
|
|
1308
|
-
### 🎯 模型選擇建議
|
|
1309
|
-
|
|
1310
|
-
**追求速度**: \`gemini-2.5-flash-image-preview\`, \`sora_image\`, \`gpt-4o-image\` (即時回應)
|
|
1311
|
-
**注重成本**: \`wanx2.0-t2i-turbo\` (最便宜,0.04元/張)
|
|
1312
|
-
**平衡選擇**: \`wanx2.1-t2i-turbo\` (速度與品質平衡)
|
|
1313
|
-
**追求品質**: \`wanx2.1-t2i-plus\` (最高品質)
|
|
1314
|
-
**創意實驗**: \`sora_image\` (Sora技術,獨特風格)
|
|
1315
|
-
|
|
1316
|
-
### 視頻生成範例
|
|
579
|
+
#### 5. 視頻生成範例
|
|
1317
580
|
\`\`\`json
|
|
1318
581
|
{
|
|
1319
582
|
"name": "tongyi_wanxiang_generate_video",
|
|
1320
583
|
"arguments": {
|
|
1321
584
|
"img_url": "https://example.com/cat.jpg",
|
|
1322
585
|
"prompt": "小貓在草地上慢慢伸懶腰",
|
|
1323
|
-
"model": "wanx2.1-i2v-turbo",
|
|
1324
586
|
"duration": 4,
|
|
1325
587
|
"resolution": "720P"
|
|
1326
588
|
}
|
|
1327
589
|
}
|
|
1328
590
|
\`\`\`
|
|
1329
591
|
|
|
1330
|
-
### 特效視頻範例
|
|
1331
|
-
\`\`\`json
|
|
1332
|
-
{
|
|
1333
|
-
"name": "tongyi_wanxiang_generate_video",
|
|
1334
|
-
"arguments": {
|
|
1335
|
-
"img_url": "https://example.com/object.jpg",
|
|
1336
|
-
"template": "flying",
|
|
1337
|
-
"resolution": "720P"
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
\`\`\`
|
|
1341
|
-
|
|
1342
592
|
---
|
|
1343
593
|
|
|
1344
|
-
##
|
|
594
|
+
## 💰 模型價格對比表
|
|
1345
595
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
5.
|
|
1351
|
-
|
|
596
|
+
| 模型 | 提供商 | 價格 | 生成時間 | 適用場景 |
|
|
597
|
+
|------|---------|------|----------|----------|
|
|
598
|
+
| **sora_image** ⭐ | 老張API | **$0.01/次** | 即時 | 🚀 首選,即時生成 |
|
|
599
|
+
| **gpt-4o-image** | 老張API | **$0.01/次** | 即時 | 🤖 智能理解、複雜場景 |
|
|
600
|
+
| **gemini-2.5-flash-image-preview** | 老張API | **$0.01/次** | 即時 | ⚡ 高速生成 |
|
|
601
|
+
| **wanx2.0-t2i-turbo** | 阿里雲 | **0.04元/張** | 1-3分鐘 | 💰 批量生成、成本控制 |
|
|
602
|
+
| **wanx2.1-t2i-turbo** | 阿里雲 | 0.14元/張 | 1-3分鐘 | ⚡ 快速原型、測試 |
|
|
603
|
+
| **wanx2.1-t2i-plus** | 阿里雲 | 0.20元/張 | 1-3分鐘 | 🎨 高品質、商業用途 |
|
|
1352
604
|
|
|
1353
605
|
---
|
|
1354
606
|
|
|
1355
607
|
## 🔧 環境配置
|
|
1356
608
|
|
|
1357
|
-
|
|
1358
|
-
- \`ALI_API_KEY\` 或 \`DASHSCOPE_API_KEY\`: 阿里雲DashScope API密鑰(通義萬相模型需要)
|
|
1359
|
-
- \`AI_API_KEY\`: 老張API密鑰(老張API模型和chat_completion需要)
|
|
609
|
+
- \`AI_API_KEY\`: 老張API密鑰(必需)
|
|
1360
610
|
- \`AI_API_BASE_URL\`: 老張API基礎URL(可選,預設: https://api.laozhang.ai/v1)
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
type: "text",
|
|
1371
|
-
text: guideText.trim(),
|
|
1372
|
-
},
|
|
1373
|
-
],
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
async run() {
|
|
1377
|
-
const transport = new StdioServerTransport();
|
|
1378
|
-
await this.server.connect(transport);
|
|
1379
|
-
console.error(`AI Image Chat MCP 服務器已啟動 - v${packageJson.version}`);
|
|
1380
|
-
}
|
|
611
|
+
- \`ALI_API_KEY\` 或 \`DASHSCOPE_API_KEY\`: 阿里雲DashScope API密鑰(使用通義萬相時需要)
|
|
612
|
+
- \`AI_IMAGE_SAVE_PATH\`: 圖片保存路徑(可選)
|
|
613
|
+
`.trim();
|
|
614
|
+
}
|
|
615
|
+
// ===== 啟動 =====
|
|
616
|
+
async function main() {
|
|
617
|
+
const transport = new StdioServerTransport();
|
|
618
|
+
await server.connect(transport);
|
|
619
|
+
console.error(`AI Image Chat MCP 服務器已啟動 - v${packageJson.version}`);
|
|
1381
620
|
}
|
|
1382
|
-
|
|
1383
|
-
const server = new AIImageChatMCPServer();
|
|
1384
|
-
server.run().catch((error) => {
|
|
621
|
+
main().catch((error) => {
|
|
1385
622
|
console.error("服務器啟動失敗:", error);
|
|
1386
623
|
process.exit(1);
|
|
1387
624
|
});
|