Files
gemini-skill/src/demo.js

213 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* demo.js — 使用示例
*
* 两种启动方式:
*
* 方式 1推荐先手动启动浏览器再运行 demo
* chrome --remote-debugging-port=9223 --user-data-dir="~/.gemini-skill/browser-data"
* (也可以用 Edgemsedge --remote-debugging-port=9223 --user-data-dir=...
* node src/demo.js
*
* 方式 2让 skill 自动检测并启动浏览器
* node src/demo.js
* 或指定路径BROWSER_PATH="C:/..." node src/demo.js
*
* 所有配置项见 .env可直接编辑或通过命令行设环境变量。
*/
import { execSync } from 'node:child_process';
import { platform, homedir } from 'node:os';
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { createGeminiSession, disconnect } from './index.js';
const prompt = 'Gemini你好请你仿造这个风格给我生成更多表情包吧来一张玩手机中。。。';
// ── Demo 专用:杀掉所有 Chromium 系浏览器进程 ──
function killAllBrowserProcesses() {
const os = platform();
const commands = os === 'win32'
? [
'taskkill /F /IM msedge.exe /T',
'taskkill /F /IM chrome.exe /T',
'taskkill /F /IM chromium.exe /T',
]
: [
'pkill -f msedge || true',
'pkill -f chrome || true',
'pkill -f chromium || true',
];
for (const cmd of commands) {
try {
execSync(cmd, { stdio: 'ignore', timeout: 5000 });
} catch {
// 进程不存在时会报错,忽略
}
}
console.log('[demo] 已清理所有浏览器进程');
}
/** 异步等待 */
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
/**
* 创建会话,如果因浏览器目录被锁而失败,自动杀掉全部浏览器进程后重试一次
*/
async function createSessionWithRetry() {
// 禁止 Puppeteer 在 Ctrl+C 等信号时自动杀浏览器进程,
// 由 demo 自己处理 SIGINT → disconnect浏览器保持运行可复用。
const opts = {
debugOpts: {
handleSIGINT: false,
handleSIGTERM: false,
handleSIGHUP: false,
},
};
try {
return await createGeminiSession(opts);
} catch (err) {
const msg = err.message || '';
const isLocked = msg.includes('EPERM') || msg.includes('lock') || msg.includes('already');
if (!isLocked) throw err;
console.warn(
`[demo] 浏览器数据目录被占用,正在清理所有浏览器进程后重试...\n` +
` 原始错误:${msg}`
);
killAllBrowserProcesses();
await sleep(2000);
// 重试一次,还失败就直接抛出
return await createGeminiSession(opts);
}
}
async function main() {
console.log('=== Gemini Skill Demo ===\n');
// 创建会话:自带杀进程重试逻辑
const { ops } = await createSessionWithRetry();
// ── Ctrl+C 时只断开连接,不杀浏览器进程(下次可复用) ──
process.on('SIGINT', () => {
console.log('\n[demo] Ctrl+C 收到,断开浏览器连接(浏览器保持运行)...');
disconnect();
process.exit(0);
});
try {
// 1. 进入临时会话(不留聊天记录,保持账号干净)
console.log('[1] 进入临时会话...');
const tempResult = await ops.clickTempChat();
if (!tempResult.ok) {
console.warn('[1] 临时会话按钮未找到,跳过(可能已在临时模式或页面结构变化)');
} else {
console.log('[1] 已进入临时会话');
}
// 2. 探测页面状态
console.log('\n[2] 探测页面元素...');
const probe = await ops.probe();
console.log('probe:', JSON.stringify(probe, null, 2));
// 3. 确保使用 Pro 模型
console.log('\n[3] 检查模型...');
if (probe.currentModel.toLowerCase() === 'pro') {
console.log('[3] 当前已是 Pro 模型,跳过');
} else {
console.log(`[3] 当前模型: ${probe.currentModel || '未知'},切换到 Pro...`);
const switchResult = await ops.ensureModelPro();
if (switchResult.ok) {
console.log(`[3] 已切换到 Pro之前: ${switchResult.previousModel || '未知'}`);
} else {
console.warn(`[3] 切换 Pro 失败: ${switchResult.error},继续使用当前模型`);
}
}
// 4. 上传图片
console.log('\n[4] 上传图片...');
const uploadResult = await ops.uploadImage('./gemini-image/miku_fighting.jpg');
if (uploadResult.ok) {
console.log(`[4] ✅ 图片上传完成 (${uploadResult.elapsed}ms)`);
if (uploadResult.warning) console.warn(`[4] ⚠ ${uploadResult.warning}`);
} else {
console.warn(`[4] ⚠ 图片上传失败: ${uploadResult.error}${uploadResult.detail}`);
}
// 5. 发送一句话
console.log('\n[5] 发送提示词...');
const result = await ops.sendAndWait(prompt, {
timeout: 120_000,
onPoll(poll) {
console.log(` polling... status=${poll.status}`);
},
});
console.log('result:', JSON.stringify(result, null, 2));
// 6. 等待图片加载完成
if (result.ok) {
console.log('\n[6] 等待图片加载完成...');
const imgLoadStart = Date.now();
while (Date.now() - imgLoadStart < 30_000) {
const { loaded } = await ops.checkImageLoaded();
if (loaded) break;
console.log(' 图片加载中...');
await sleep(500);
}
console.log(`[6] 图片加载完成 (${Date.now() - imgLoadStart}ms)`);
// 7. 获取最新图片并保存到本地
console.log('\n[7] 查找最新生成的图片...');
const imgInfo = await ops.getLatestImage();
console.log('imgInfo:', JSON.stringify(imgInfo, null, 2));
if (imgInfo.ok && imgInfo.src) {
console.log(`[7] 找到图片 (${imgInfo.width}x${imgInfo.height}, isNew=${imgInfo.isNew})`);
// 提取 base64 数据
console.log(`[7] 提取图片数据 (src=${imgInfo.src})...`);
const b64Result = await ops.extractImageBase64(imgInfo.src);
if (b64Result.ok && b64Result.dataUrl) {
// dataUrl 格式: data:image/png;base64,iVBOR...
const matches = b64Result.dataUrl.match(/^data:image\/(\w+);base64,(.+)$/);
if (matches) {
const ext = matches[1] === 'jpeg' ? 'jpg' : matches[1];
const base64Data = matches[2];
const buffer = Buffer.from(base64Data, 'base64');
// 保存到 ./gemini-image/
const outputDir = './gemini-image';
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
const filename = `gemini_${Date.now()}.${ext}`;
const filepath = join(outputDir, filename);
writeFileSync(filepath, buffer);
console.log(`[7] ✅ 图片已保存: ${filepath} (${(buffer.length / 1024).toFixed(1)} KB, method=${b64Result.method})`);
} else {
console.warn('[7] ⚠ dataUrl 格式无法解析');
}
} else {
console.warn(`[7] ⚠ 提取图片数据失败: ${b64Result.error || 'unknown'}`);
}
} else {
console.log('[7] 未找到图片(可能本次回答不含图片)');
}
}
} catch (err) {
console.error('Error:', err);
}
console.log('\n[done] 功能执行完毕,浏览器保持运行。按 Ctrl+C 退出。');
}
main().catch(console.error);