chrome-ai-bridge 1.0.6 → 1.0.7
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 +72 -54
- package/package.json +1 -1
|
@@ -86,24 +86,53 @@ async function cropWatermark(inputPath, outputPath, margin = DEFAULT_CROP_MARGIN
|
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
88
|
* Wait for download to complete and return the file path
|
|
89
|
+
* Looks for new image files (png, jpg, jpeg) in the download directory
|
|
89
90
|
*/
|
|
90
91
|
async function waitForDownload(downloadDir, timeoutMs = 60000) {
|
|
91
92
|
const startTime = Date.now();
|
|
92
|
-
const checkInterval =
|
|
93
|
-
// Get initial files
|
|
94
|
-
const initialFiles = new
|
|
93
|
+
const checkInterval = 1000; // Check every second
|
|
94
|
+
// Get initial files with their mtimes
|
|
95
|
+
const initialFiles = new Map();
|
|
96
|
+
try {
|
|
97
|
+
const files = await fs.promises.readdir(downloadDir);
|
|
98
|
+
for (const f of files) {
|
|
99
|
+
if (/\.(png|jpg|jpeg)$/i.test(f)) {
|
|
100
|
+
const stat = await fs.promises.stat(path.join(downloadDir, f));
|
|
101
|
+
initialFiles.set(f, stat.mtime.getTime());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Directory might not exist, continue
|
|
107
|
+
}
|
|
95
108
|
while (Date.now() - startTime < timeoutMs) {
|
|
96
109
|
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
try {
|
|
111
|
+
const currentFiles = await fs.promises.readdir(downloadDir);
|
|
112
|
+
for (const f of currentFiles) {
|
|
113
|
+
// Only check image files
|
|
114
|
+
if (!/\.(png|jpg|jpeg)$/i.test(f))
|
|
115
|
+
continue;
|
|
116
|
+
// Skip incomplete downloads
|
|
117
|
+
if (f.endsWith('.crdownload') || f.endsWith('.tmp'))
|
|
118
|
+
continue;
|
|
119
|
+
const filePath = path.join(downloadDir, f);
|
|
120
|
+
const stat = await fs.promises.stat(filePath);
|
|
121
|
+
const mtime = stat.mtime.getTime();
|
|
122
|
+
// Check if this is a new file or modified after we started
|
|
123
|
+
const initialMtime = initialFiles.get(f);
|
|
124
|
+
if (!initialMtime || mtime > initialMtime) {
|
|
125
|
+
// Verify file is complete (size > 0 and not growing)
|
|
126
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
127
|
+
const stat2 = await fs.promises.stat(filePath);
|
|
128
|
+
if (stat2.size > 0 && stat2.size === stat.size) {
|
|
129
|
+
return filePath;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Continue on error
|
|
107
136
|
}
|
|
108
137
|
}
|
|
109
138
|
throw new Error(`Download timeout after ${timeoutMs}ms`);
|
|
@@ -140,15 +169,6 @@ export const askGeminiImage = defineTool({
|
|
|
140
169
|
handler: async (request, response, context) => {
|
|
141
170
|
const { prompt, outputPath, cropMargin = DEFAULT_CROP_MARGIN, skipCrop = false, } = request.params;
|
|
142
171
|
const page = await getOrCreateGeminiPage(context);
|
|
143
|
-
// Set up download directory
|
|
144
|
-
const downloadDir = path.join(os.tmpdir(), 'gemini-image-downloads');
|
|
145
|
-
await fs.promises.mkdir(downloadDir, { recursive: true });
|
|
146
|
-
// Configure download behavior
|
|
147
|
-
const client = await page.createCDPSession();
|
|
148
|
-
await client.send('Page.setDownloadBehavior', {
|
|
149
|
-
behavior: 'allow',
|
|
150
|
-
downloadPath: downloadDir,
|
|
151
|
-
});
|
|
152
172
|
try {
|
|
153
173
|
response.appendResponseLine('Geminiに接続中...');
|
|
154
174
|
// Navigate to Gemini
|
|
@@ -262,49 +282,47 @@ export const askGeminiImage = defineTool({
|
|
|
262
282
|
}
|
|
263
283
|
// Try to download the image
|
|
264
284
|
response.appendResponseLine('📥 画像をダウンロード中...');
|
|
265
|
-
//
|
|
285
|
+
// Click download button - Gemini uses "フルサイズの画像をダウンロード" button
|
|
266
286
|
const downloadClicked = await page.evaluate(() => {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
b.
|
|
287
|
+
const buttons = Array.from(document.querySelectorAll('button'));
|
|
288
|
+
// Look for "フルサイズの画像をダウンロード" or "フルサイズでダウンロード" button
|
|
289
|
+
const downloadBtn = buttons.find(b => {
|
|
290
|
+
const text = b.textContent || '';
|
|
291
|
+
const ariaLabel = b.getAttribute('aria-label') || '';
|
|
292
|
+
const description = b.getAttribute('aria-describedby')
|
|
293
|
+
? document.getElementById(b.getAttribute('aria-describedby'))?.textContent || ''
|
|
294
|
+
: '';
|
|
295
|
+
return (text.includes('フルサイズ') ||
|
|
296
|
+
text.includes('ダウンロード') ||
|
|
297
|
+
ariaLabel.includes('ダウンロード') ||
|
|
298
|
+
ariaLabel.includes('download') ||
|
|
299
|
+
description.includes('フルサイズ') ||
|
|
300
|
+
description.includes('ダウンロード'));
|
|
301
|
+
});
|
|
272
302
|
if (downloadBtn) {
|
|
273
303
|
downloadBtn.click();
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
|
-
// Try three-dot menu on generated images
|
|
277
|
-
const moreButtons = buttons.filter(b => b.getAttribute('aria-label')?.includes('more') ||
|
|
278
|
-
b.getAttribute('aria-label')?.includes('その他') ||
|
|
279
|
-
b.querySelector('mat-icon[data-mat-icon-name="more_vert"]'));
|
|
280
|
-
if (moreButtons.length > 0) {
|
|
281
|
-
// Click the last one (likely the image menu)
|
|
282
|
-
moreButtons[moreButtons.length - 1].click();
|
|
283
|
-
return 'menu-opened';
|
|
304
|
+
return true;
|
|
284
305
|
}
|
|
285
|
-
return
|
|
306
|
+
return false;
|
|
286
307
|
});
|
|
287
|
-
if (downloadClicked
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const menuItems = Array.from(document.querySelectorAll('[role="menuitem"], button'));
|
|
292
|
-
const downloadItem = menuItems.find(item => item.textContent?.includes('ダウンロード') ||
|
|
293
|
-
item.textContent?.includes('Download'));
|
|
294
|
-
if (downloadItem) {
|
|
295
|
-
downloadItem.click();
|
|
296
|
-
}
|
|
297
|
-
});
|
|
308
|
+
if (!downloadClicked) {
|
|
309
|
+
response.appendResponseLine('⚠️ ダウンロードボタンが見つかりません');
|
|
310
|
+
response.appendResponseLine('ヒント: ブラウザで画像を右クリックして保存してください');
|
|
311
|
+
return;
|
|
298
312
|
}
|
|
299
|
-
// Wait for download to
|
|
313
|
+
// Wait for download to start (Gemini shows progress bar)
|
|
314
|
+
response.appendResponseLine('⏳ ダウンロード処理を待機中...');
|
|
315
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
316
|
+
// Wait for download to complete - check user's Downloads folder
|
|
317
|
+
const userDownloadsDir = path.join(os.homedir(), 'Downloads');
|
|
300
318
|
let downloadedPath;
|
|
301
319
|
try {
|
|
302
|
-
downloadedPath = await waitForDownload(
|
|
320
|
+
downloadedPath = await waitForDownload(userDownloadsDir, 60000); // 60 seconds
|
|
303
321
|
response.appendResponseLine(`✅ ダウンロード完了: ${path.basename(downloadedPath)}`);
|
|
304
322
|
}
|
|
305
323
|
catch (error) {
|
|
306
|
-
response.appendResponseLine('❌ ダウンロード待機タイムアウト');
|
|
307
|
-
response.appendResponseLine('ヒント:
|
|
324
|
+
response.appendResponseLine('❌ ダウンロード待機タイムアウト (60秒)');
|
|
325
|
+
response.appendResponseLine('ヒント: ブラウザで画像を右クリックして「画像を保存」してください');
|
|
308
326
|
return;
|
|
309
327
|
}
|
|
310
328
|
// Ensure output directory exists
|
package/package.json
CHANGED