feat(mcp): 新增高清原图下载和超时配置,优化生图流程
This commit is contained in:
2
SKILL.md
2
SKILL.md
@@ -48,7 +48,7 @@ MCP 工具调用(尤其是生图、等待回复等)可能耗时较长。**
|
|||||||
|
|
||||||
| 工具名 | 说明 | 入参 |
|
| 工具名 | 说明 | 入参 |
|
||||||
|--------|------|------|
|
|--------|------|------|
|
||||||
| `gemini_generate_image` | 完整生图流程:新建会话→发prompt→等待→提取图片→保存本地 | `prompt`,`newSession`(默认false),`referenceImages`(参考图路径数组,默认空) |
|
| `gemini_generate_image` | 完整生图流程:新建会话→发prompt→等待→提取图片→保存本地(耗时约 60~120 秒) | `prompt`,`newSession`(默认false),`referenceImages`(参考图路径数组),`fullSize`(默认false,true则下载高清原图),`timeout`(默认180000ms) |
|
||||||
|
|
||||||
**会话管理:**
|
**会话管理:**
|
||||||
|
|
||||||
|
|||||||
@@ -783,9 +783,9 @@ export function createOps(page) {
|
|||||||
|
|
||||||
// 4. hover 到图片上,触发工具栏显示
|
// 4. hover 到图片上,触发工具栏显示
|
||||||
await page.mouse.move(imgInfo.x, imgInfo.y);
|
await page.mouse.move(imgInfo.x, imgInfo.y);
|
||||||
await sleep(250);
|
await sleep(500);
|
||||||
|
|
||||||
// 5. 点击"下载完整尺寸"按钮
|
// 5. 点击"下载完整尺寸"按钮(带重试:hover 可能需要更长时间触发工具栏)
|
||||||
const btnSelector = 'button[data-test-id="download-enhanced-image-button"]';
|
const btnSelector = 'button[data-test-id="download-enhanced-image-button"]';
|
||||||
const clickResult = await op.click(btnSelector);
|
const clickResult = await op.click(btnSelector);
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const server = new McpServer({
|
|||||||
server.registerTool(
|
server.registerTool(
|
||||||
"gemini_generate_image",
|
"gemini_generate_image",
|
||||||
{
|
{
|
||||||
description: "调用后台的 Gemini 浏览器会话生成高质量图片",
|
description: "调用后台的 Gemini 浏览器会话生成高质量图片。注意:生图过程较慢(通常 60~120 秒),请耐心等待",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
prompt: z.string().describe("图片的详细描述词"),
|
prompt: z.string().describe("图片的详细描述词"),
|
||||||
newSession: z.boolean().default(false).describe(
|
newSession: z.boolean().default(false).describe(
|
||||||
@@ -27,9 +27,15 @@ server.registerTool(
|
|||||||
referenceImages: z.array(z.string()).default([]).describe(
|
referenceImages: z.array(z.string()).default([]).describe(
|
||||||
"参考图片的本地文件路径数组,例如 [\"/path/to/ref1.png\", \"/path/to/ref2.jpg\"]。图片会在发送 prompt 前上传到 Gemini 输入框"
|
"参考图片的本地文件路径数组,例如 [\"/path/to/ref1.png\", \"/path/to/ref2.jpg\"]。图片会在发送 prompt 前上传到 Gemini 输入框"
|
||||||
),
|
),
|
||||||
|
fullSize: z.boolean().default(false).describe(
|
||||||
|
"是否下载完整尺寸原图。true= 通过 CDP 拦截下载高清大图; false= 提取页面预览图"
|
||||||
|
),
|
||||||
|
timeout: z.number().default(180000).describe(
|
||||||
|
"等待 Gemini 生成回复的超时时间(毫秒),默认 180000(3 分钟)。生图较慢,建议不低于 120000"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ prompt, newSession, referenceImages }) => {
|
async ({ prompt, newSession, referenceImages, fullSize, timeout }) => {
|
||||||
try {
|
try {
|
||||||
const { ops } = await createGeminiSession();
|
const { ops } = await createGeminiSession();
|
||||||
|
|
||||||
@@ -42,25 +48,21 @@ server.registerTool(
|
|||||||
isError: true,
|
isError: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// 需要先处理新建会话(如果需要),因为 generateImage 内部的 newChat 会在上传之后才执行
|
|
||||||
if (newSession) {
|
// 确保是 pro 模型(生图需要 Pro)
|
||||||
|
await ops.ensureModelPro();
|
||||||
|
|
||||||
|
// 如果有参考图,需要先新建会话再上传,最后 generateImage 不再新建
|
||||||
|
if (referenceImages.length > 0) {
|
||||||
|
if (newSession) {
|
||||||
await ops.click('newChatBtn');
|
await ops.click('newChatBtn');
|
||||||
await sleep(250);
|
await sleep(250);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保是pro会话
|
|
||||||
const modelCheck = await ops.checkModel();
|
|
||||||
if (!modelCheck.ok || modelCheck.model !== 'pro') {
|
|
||||||
await ops.switchToModel('pro');
|
|
||||||
console.error(`[mcp] 已切换至 pro 模型`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有参考图,先上传
|
|
||||||
if (referenceImages.length > 0) {
|
|
||||||
for (const imgPath of referenceImages) {
|
for (const imgPath of referenceImages) {
|
||||||
console.error(`[mcp] 正在上传参考图: ${imgPath}`);
|
console.error(`[mcp] 正在上传参考图: ${imgPath}`);
|
||||||
const uploadResult = await ops.uploadImage(imgPath);
|
const uploadResult = await ops.uploadImage(imgPath);
|
||||||
if (!uploadResult.ok) {
|
if (!uploadResult.ok) {
|
||||||
|
disconnect();
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: `参考图上传失败: ${imgPath}\n错误: ${uploadResult.error}` }],
|
content: [{ type: "text", text: `参考图上传失败: ${imgPath}\n错误: ${uploadResult.error}` }],
|
||||||
isError: true,
|
isError: true,
|
||||||
@@ -70,9 +72,10 @@ server.registerTool(
|
|||||||
console.error(`[mcp] ${referenceImages.length} 张参考图上传完成`);
|
console.error(`[mcp] ${referenceImages.length} 张参考图上传完成`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果上传了参考图且已手动新建会话,则 generateImage 内部不再新建
|
// 如果有参考图,已在上面手动新建会话,generateImage 内部不再新建
|
||||||
|
// 如果没有参考图,newSession 直接传给 generateImage 内部处理
|
||||||
const needNewChat = referenceImages.length > 0 ? false : newSession;
|
const needNewChat = referenceImages.length > 0 ? false : newSession;
|
||||||
const result = await ops.generateImage(prompt, { newChat: needNewChat, fullSize });
|
const result = await ops.generateImage(prompt, { newChat: needNewChat, fullSize, timeout });
|
||||||
|
|
||||||
// 执行完毕立刻断开,交还给 Daemon 倒计时
|
// 执行完毕立刻断开,交还给 Daemon 倒计时
|
||||||
disconnect();
|
disconnect();
|
||||||
@@ -94,7 +97,7 @@ server.registerTool(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 低分辨率模式:base64 提取,写入本地文件
|
// base64 提取模式:写入本地文件,只返回文件路径(不返回 base64 数据,避免 MCP 协议校验问题)
|
||||||
const base64Data = result.dataUrl.split(',')[1];
|
const base64Data = result.dataUrl.split(',')[1];
|
||||||
const mimeMatch = result.dataUrl.match(/^data:(image\/\w+);/);
|
const mimeMatch = result.dataUrl.match(/^data:(image\/\w+);/);
|
||||||
const ext = mimeMatch ? mimeMatch[1].split('/')[1] : 'png';
|
const ext = mimeMatch ? mimeMatch[1].split('/')[1] : 'png';
|
||||||
@@ -109,11 +112,6 @@ server.registerTool(
|
|||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
{ type: "text", text: `图片生成成功!已保存至: ${filePath}` },
|
{ type: "text", text: `图片生成成功!已保存至: ${filePath}` },
|
||||||
{
|
|
||||||
type: "image",
|
|
||||||
data: base64Data,
|
|
||||||
mimeType: mimeMatch ? mimeMatch[1] : "image/png",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -339,7 +337,6 @@ server.registerTool(
|
|||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
{ type: "text", text: `图片提取成功,已保存至: ${filePath}` },
|
{ type: "text", text: `图片提取成功,已保存至: ${filePath}` },
|
||||||
{ type: "image", data: base64Data, mimeType: mimeMatch ? mimeMatch[1] : "image/png" },
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -368,7 +365,8 @@ server.registerTool(
|
|||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
let msg = `下载完整尺寸图片失败: ${result.error}`;
|
let msg = `下载完整尺寸图片失败: ${result.error}`;
|
||||||
if (result.total != null) msg += `(共 ${result.total} 张图片)`;
|
if (result.detail) msg += `\n${result.detail}`;
|
||||||
|
if (result.total != null) msg += `\n(共 ${result.total} 张图片)`;
|
||||||
if (result.error === 'index_out_of_range') msg += `,请求的索引: ${result.requestedIndex}`;
|
if (result.error === 'index_out_of_range') msg += `,请求的索引: ${result.requestedIndex}`;
|
||||||
return { content: [{ type: "text", text: msg }], isError: true };
|
return { content: [{ type: "text", text: msg }], isError: true };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user