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