chrome-ai-bridge 1.0.9 → 1.0.11
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/build/src/tools/gemini-image.js +95 -69
- package/package.json +1 -1
|
@@ -188,64 +188,71 @@ export const askGeminiImage = defineTool({
|
|
|
188
188
|
response.appendResponseLine('⚠️ 送信ボタンが見つかりません (Enterキーを試行)');
|
|
189
189
|
}
|
|
190
190
|
response.appendResponseLine('🎨 画像生成中... (1-2分かかることがあります)');
|
|
191
|
-
// Wait for image generation using MutationObserver
|
|
191
|
+
// Wait for image generation using MutationObserver + polling hybrid approach
|
|
192
|
+
// MutationObserver provides instant detection, polling ensures we don't miss state
|
|
192
193
|
const startTime = Date.now();
|
|
193
194
|
const maxWaitTime = 180000; // 3 minutes
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
desc = descEl?.textContent || '';
|
|
210
|
-
}
|
|
211
|
-
return (text.includes('ダウンロード') ||
|
|
212
|
-
text.includes('Download') ||
|
|
213
|
-
text.includes('フルサイズ') ||
|
|
214
|
-
ariaLabel.toLowerCase().includes('download') ||
|
|
215
|
-
desc.includes('フルサイズ') ||
|
|
216
|
-
desc.includes('ダウンロード'));
|
|
217
|
-
});
|
|
218
|
-
if (images.length > 0 || hasDownload) {
|
|
219
|
-
return true;
|
|
220
|
-
}
|
|
221
|
-
return false;
|
|
222
|
-
};
|
|
223
|
-
// Initial check
|
|
224
|
-
if (checkCompletion()) {
|
|
225
|
-
resolve(true);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
// Set up MutationObserver for instant detection
|
|
229
|
-
const observer = new MutationObserver(() => {
|
|
230
|
-
if (checkCompletion()) {
|
|
231
|
-
observer.disconnect();
|
|
232
|
-
clearTimeout(timeoutId);
|
|
233
|
-
resolve(true);
|
|
195
|
+
// Set up MutationObserver in the page (stores result in window object)
|
|
196
|
+
await page.evaluate(() => {
|
|
197
|
+
// @ts-expect-error - window property
|
|
198
|
+
window.__geminiImageFound = false;
|
|
199
|
+
const checkCompletion = () => {
|
|
200
|
+
const images = document.querySelectorAll('img[src*="blob:"], img[src*="generated"]');
|
|
201
|
+
const buttons = Array.from(document.querySelectorAll('button, [role="menuitem"]'));
|
|
202
|
+
const hasDownload = buttons.some(b => {
|
|
203
|
+
const text = b.textContent || '';
|
|
204
|
+
const ariaLabel = b.getAttribute('aria-label') || '';
|
|
205
|
+
const describedBy = b.getAttribute('aria-describedby');
|
|
206
|
+
let desc = '';
|
|
207
|
+
if (describedBy) {
|
|
208
|
+
const descEl = document.getElementById(describedBy);
|
|
209
|
+
desc = descEl?.textContent || '';
|
|
234
210
|
}
|
|
211
|
+
return (text.includes('ダウンロード') ||
|
|
212
|
+
text.includes('Download') ||
|
|
213
|
+
text.includes('フルサイズ') ||
|
|
214
|
+
ariaLabel.toLowerCase().includes('download') ||
|
|
215
|
+
desc.includes('フルサイズ') ||
|
|
216
|
+
desc.includes('ダウンロード'));
|
|
235
217
|
});
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
218
|
+
return images.length > 0 || hasDownload;
|
|
219
|
+
};
|
|
220
|
+
// Initial check
|
|
221
|
+
if (checkCompletion()) {
|
|
222
|
+
// @ts-expect-error - window property
|
|
223
|
+
window.__geminiImageFound = true;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Set up MutationObserver
|
|
227
|
+
const observer = new MutationObserver(() => {
|
|
228
|
+
if (checkCompletion()) {
|
|
229
|
+
// @ts-expect-error - window property
|
|
230
|
+
window.__geminiImageFound = true;
|
|
244
231
|
observer.disconnect();
|
|
245
|
-
|
|
246
|
-
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
observer.observe(document.body, {
|
|
235
|
+
childList: true,
|
|
236
|
+
subtree: true,
|
|
237
|
+
attributes: true,
|
|
238
|
+
attributeFilter: ['src', 'aria-label', 'aria-describedby'],
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
// Poll for the result (short intervals to minimize latency)
|
|
242
|
+
let imageFound = false;
|
|
243
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
244
|
+
// Check if MutationObserver detected image
|
|
245
|
+
const found = await page.evaluate(() => {
|
|
246
|
+
// @ts-expect-error - window property
|
|
247
|
+
return window.__geminiImageFound === true;
|
|
247
248
|
});
|
|
248
|
-
|
|
249
|
+
if (found) {
|
|
250
|
+
imageFound = true;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
// Short wait before next check (500ms for responsiveness)
|
|
254
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
255
|
+
}
|
|
249
256
|
if (!imageFound) {
|
|
250
257
|
response.appendResponseLine('❌ 画像生成タイムアウト (3分)');
|
|
251
258
|
return;
|
|
@@ -275,6 +282,9 @@ export const askGeminiImage = defineTool({
|
|
|
275
282
|
downloadManager.on('started', (filename) => {
|
|
276
283
|
response.appendResponseLine(`📥 ダウンロード開始: ${filename}`);
|
|
277
284
|
});
|
|
285
|
+
// Get existing Gemini images BEFORE clicking download button
|
|
286
|
+
const existingFiles = await fs.promises.readdir(userDownloadsDir);
|
|
287
|
+
const existingGeminiImages = new Set(existingFiles.filter(f => f.startsWith('Gemini_Generated_Image_') && f.endsWith('.png')));
|
|
278
288
|
// Click download button - Gemini uses "フルサイズの画像をダウンロード" button
|
|
279
289
|
// Improved selector: prioritize aria-describedby for more reliable detection
|
|
280
290
|
const downloadClicked = await page.evaluate(() => {
|
|
@@ -318,25 +328,41 @@ export const askGeminiImage = defineTool({
|
|
|
318
328
|
response.appendResponseLine('ヒント: ブラウザで画像を右クリックして保存してください');
|
|
319
329
|
return;
|
|
320
330
|
}
|
|
321
|
-
// Wait for download to complete using
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
// Wait for download to complete using hybrid approach:
|
|
332
|
+
// 1. Try CDP events first (reliable for standard downloads)
|
|
333
|
+
// 2. Fall back to filesystem monitoring (for blob/JS downloads like Gemini)
|
|
334
|
+
response.appendResponseLine('⏳ ダウンロード完了を待機中...');
|
|
335
|
+
let downloadedPath = null;
|
|
336
|
+
const downloadStartTime = Date.now();
|
|
337
|
+
const downloadTimeout = 60000; // 60 seconds
|
|
338
|
+
// Try CDP-based detection with filesystem fallback
|
|
339
|
+
while (Date.now() - downloadStartTime < downloadTimeout) {
|
|
340
|
+
// Check for new Gemini image files (filesystem fallback)
|
|
341
|
+
const currentFiles = await fs.promises.readdir(userDownloadsDir);
|
|
342
|
+
const newGeminiImages = currentFiles.filter(f => f.startsWith('Gemini_Generated_Image_') &&
|
|
343
|
+
f.endsWith('.png') &&
|
|
344
|
+
!existingGeminiImages.has(f));
|
|
345
|
+
if (newGeminiImages.length > 0) {
|
|
346
|
+
// Found new image file
|
|
347
|
+
const newestImage = newGeminiImages.sort().pop();
|
|
348
|
+
downloadedPath = path.join(userDownloadsDir, newestImage);
|
|
349
|
+
// Wait a bit for file to be fully written
|
|
350
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
351
|
+
response.appendResponseLine(`✅ ダウンロード完了: ${newestImage}`);
|
|
352
|
+
break;
|
|
336
353
|
}
|
|
337
|
-
|
|
338
|
-
|
|
354
|
+
// Also check CDP events (for standard downloads)
|
|
355
|
+
const completedDownloads = Array.from(downloadManager.getPendingDownloads()).filter((d) => d.state === 'completed');
|
|
356
|
+
if (completedDownloads.length > 0) {
|
|
357
|
+
// CDP detected completion - but Gemini uses blob downloads so this rarely fires
|
|
358
|
+
break;
|
|
339
359
|
}
|
|
360
|
+
// Short wait before next check
|
|
361
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
362
|
+
}
|
|
363
|
+
if (!downloadedPath) {
|
|
364
|
+
response.appendResponseLine('❌ ダウンロードタイムアウト (60秒)');
|
|
365
|
+
response.appendResponseLine('💡 ヒント: ブラウザで画像を右クリックして「画像を保存」してください');
|
|
340
366
|
return;
|
|
341
367
|
}
|
|
342
368
|
// Ensure output directory exists
|
package/package.json
CHANGED