Skip to content

Commit 162e925

Browse files
committed
Fix gemini api error
1 parent 9829cc8 commit 162e925

7 files changed

Lines changed: 626 additions & 129 deletions

File tree

src/ai_dom_editor/editor/helpers/api_handler.js

Lines changed: 87 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export class ApiHandler {
154154
_buildAuthHeaders(modelConfig) {
155155
const apiKey = modelConfig?.apiKey || modelConfig?.key;
156156
const provider = (modelConfig?.provider || '').toLowerCase();
157+
const modelId = (modelConfig?.id || modelConfig?.model || '').toLowerCase();
157158

158159
if (!apiKey) return {};
159160

@@ -162,6 +163,11 @@ export class ApiHandler {
162163
return { 'X-API-Key': apiKey };
163164
}
164165

166+
// Gemini API uses x-goog-api-key header
167+
if (modelId.includes('gemini') || modelId.includes('gemma') || provider.includes('gemini')) {
168+
return { 'x-goog-api-key': apiKey };
169+
}
170+
165171
// common heuristics
166172
if (provider.includes('openai') || provider.includes('gpt') || provider.includes('anthropic')) {
167173
return { 'Authorization': `Bearer ${apiKey}` };
@@ -179,6 +185,18 @@ export class ApiHandler {
179185

180186
// Robust response content extractor for various provider shapes
181187
_extractContentFromResponse(data) {
188+
// Gemini API format: { candidates: [{ content: { parts: [{ text: "..." }] } }] }
189+
if (data?.candidates && Array.isArray(data.candidates) && data.candidates.length > 0) {
190+
const candidate = data.candidates[0];
191+
if (candidate?.content?.parts && Array.isArray(candidate.content.parts)) {
192+
const textParts = candidate.content.parts
193+
.filter(part => part.text)
194+
.map(part => part.text)
195+
.join('');
196+
if (textParts) return textParts;
197+
}
198+
}
199+
182200
// OpenAI Chat Completions
183201
if (data?.choices && Array.isArray(data.choices) && data.choices.length > 0) {
184202
const first = data.choices[0];
@@ -233,34 +251,68 @@ ${domSummary}
233251
- Combine selectors for precision (e.g., \`document.querySelector('div.container > h1.title')\`)
234252
235253
3. **GREASEMONKEY APIs AVAILABLE:**
236-
You have access to these powerful APIs - use them when appropriate:
237-
- \`GM_addStyle(css)\` - Inject CSS (preferred for styling changes)
238-
- \`GM_setValue(key, value)\` - Store persistent data
254+
You have access to these powerful APIs - USE THEM when they make the code better:
255+
256+
**STYLING (HIGHLY RECOMMENDED for CSS changes):**
257+
- \`GM_addStyle(css)\` - Inject CSS (ALWAYS prefer this over inline styles for multiple elements)
258+
259+
**STORAGE (for persistent data):**
260+
- \`GM_setValue(key, value)\` - Store data across page loads
239261
- \`GM_getValue(key, defaultValue)\` - Retrieve stored data
240-
- \`GM_xmlhttpRequest(details)\` - Make cross-origin requests
262+
- \`GM_deleteValue(key)\` - Delete stored data
263+
- \`GM_listValues()\` - List all stored keys
264+
265+
**NETWORK (for API calls and cross-origin requests):**
266+
- \`GM_xmlhttpRequest(details)\` - Make cross-origin HTTP requests
267+
268+
**UI ENHANCEMENTS:**
241269
- \`GM_notification(options)\` - Show desktop notifications
242-
- \`GM_addElement(parent, tag, attributes)\` - Create and insert elements
243-
- \`GM_registerMenuCommand(name, callback)\` - Add menu commands
244-
- And many more standard Tampermonkey APIs
270+
- \`GM_addElement(parent, tag, attributes)\` - Create and insert DOM elements
271+
- \`GM_registerMenuCommand(caption, callback)\` - Add custom menu commands
272+
- \`GM_openInTab(url, options)\` - Open URLs in new tabs
273+
274+
**UTILITIES:**
275+
- \`GM_setClipboard(data, type)\` - Copy data to clipboard
276+
- \`GM_download(url, name)\` - Download files
277+
- \`GM_getResourceText(name)\` - Get text resources
278+
- \`unsafeWindow\` - Direct access to page's window object (use cautiously)
245279
246280
4. **CODE QUALITY REQUIREMENTS:**
247-
- Write immediately executable code - no placeholders
248-
- Check element existence before manipulation
249-
- Use efficient selectors based on the actual DOM
250-
- Handle edge cases gracefully
251-
- Prefer GM_addStyle for CSS changes over inline styles
252-
- Use modern JavaScript (ES6+)
253-
254-
5. **OUTPUT FORMAT:**
255-
- Return ONLY the JavaScript code
256-
- NO markdown code fences
257-
- NO explanations or comments outside the code
258-
- NO \`// ==UserScript==\` headers
281+
- Write immediately executable code - no placeholders, no TODO comments
282+
- Always check element existence before manipulation (e.g., \`if (element) { ... }\`)
283+
- Use efficient selectors based on the actual DOM structure provided
284+
- Handle edge cases gracefully with try-catch where appropriate
285+
- Prefer GM_addStyle for ANY CSS changes affecting multiple elements
286+
- Use modern JavaScript (ES6+) with arrow functions, const/let, template literals
287+
- Add brief inline comments explaining complex logic
288+
- Wait for DOM if needed (DOMContentLoaded or wait functions)
289+
290+
5. **CRITICAL - ZERO METADATA GENERATION:**
291+
⚠️ NEVER generate userscript metadata - this is handled automatically ⚠️
292+
293+
**DO NOT INCLUDE:**
294+
- \`// ==UserScript==\` headers
295+
- \`// @name\`, \`@grant\`, \`@match\`, \`@run-at\`, etc.
296+
- Markdown code fences (\`\`\`javascript)
297+
- IIFE wrappers (function() {...})()
298+
299+
**ONLY PROVIDE:**
300+
- Pure JavaScript code that solves the problem
301+
- Code starts immediately (no metadata, no wrappers)
302+
303+
**WHY:** Our analyzer automatically:
304+
- Detects which GM APIs you use
305+
- Generates all @grant directives
306+
- Creates optimal @match patterns
307+
- Sets appropriate @run-at timing
308+
- Wraps your code properly
309+
310+
Your job: Write great code. Our job: Perfect metadata.
259311
260312
**USER'S REQUEST:**
261313
${userMessage}
262314
263-
**YOUR RESPONSE:** Generate the complete, executable JavaScript code now.`;
315+
**YOUR RESPONSE:** Pure JavaScript code only (absolutely NO metadata, NO wrappers, NO fences).`;
264316

265317
// Decide model config (selectedModel preferred, else global apiConfig)
266318
const modelConfig = this.selectedModel || this.apiConfig || {};
@@ -284,7 +336,6 @@ ${userMessage}
284336
const provider = (modelConfig.provider || '').toLowerCase();
285337
const isGoogleish = modelId.toLowerCase().includes('gemini') || modelId.toLowerCase().includes('gemma') || provider.includes('google') || provider.includes('vertex');
286338
const isAnthropic = provider.includes('anthropic') || modelId.toLowerCase().includes('claude');
287-
const isGemma = modelId.toLowerCase().includes('gemma');
288339

289340
// prefer settings on per-model config, fallback to global
290341
const temperature = (typeof modelConfig.temperature === 'number') ? modelConfig.temperature : (typeof this.apiConfig?.temperature === 'number' ? this.apiConfig.temperature : 0.7);
@@ -293,28 +344,22 @@ ${userMessage}
293344
// Construct request body for common Chat completions patterns (best-effort)
294345
let requestBody;
295346
if (isGoogleish) {
296-
// Gemma models don't support system instructions, combine into user message
297-
if (isGemma) {
298-
requestBody = {
299-
model: modelId,
300-
messages: [
301-
{ role: 'user', content: `${systemPrompt}\n\n${userMessage}` }
302-
],
303-
temperature,
304-
max_tokens
305-
};
306-
} else {
307-
// Gemini and other Google models support system instructions
308-
requestBody = {
309-
model: modelId,
310-
messages: [
311-
{ role: 'system', content: systemPrompt },
312-
{ role: 'user', content: userMessage }
313-
],
347+
// Gemini uses a different API format than OpenAI
348+
// Format: { contents: [{ parts: [{ text: "..." }] }] }
349+
const combinedPrompt = `${systemPrompt}\n\n${userMessage}`;
350+
requestBody = {
351+
contents: [
352+
{
353+
parts: [
354+
{ text: combinedPrompt }
355+
]
356+
}
357+
],
358+
generationConfig: {
314359
temperature,
315-
max_tokens
316-
};
317-
}
360+
maxOutputTokens: max_tokens
361+
}
362+
};
318363
} else if (isAnthropic) {
319364
// Claude-like: use a single input field (this is heuristic)
320365
requestBody = {
@@ -340,17 +385,13 @@ ${userMessage}
340385
if (modelConfig?.extraRequestFields && typeof modelConfig.extraRequestFields === 'object') {
341386
Object.assign(requestBody, modelConfig.extraRequestFields);
342387
}
343-
344-
console.log('Request', JSON.stringify(requestBody, null, 2));
345388

346389
// Timeout handling
347390
const controller = new AbortController();
348391
const timeout = modelConfig.requestTimeoutMs || this.defaultTimeoutMs;
349392
const timeoutId = setTimeout(() => controller.abort(), timeout);
350393

351394
try {
352-
const startTime = Date.now();
353-
354395
const res = await fetch(endpoint, {
355396
method: 'POST',
356397
headers: {

src/ai_dom_editor/editor/helpers/event_handler.js

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,9 @@ export class EventHandler {
8080
const message = this.editor.elements.userInput.value.trim();
8181
if (!message) return;
8282

83-
console.log('📨 User Message Sent:', message);
84-
8583
const hasModel = this.editor.apiHandler.selectedModel && this.editor.apiHandler.selectedModel.apiKey && this.editor.apiHandler.selectedModel.endpoint;
8684
const hasConfig = this.editor.apiHandler.apiConfig && this.editor.apiHandler.apiConfig.apiKey && this.editor.apiHandler.apiConfig.endpoint;
8785

88-
console.log('🔍 API Configuration Status:', {
89-
hasModel,
90-
hasConfig,
91-
selectedModel: this.editor.apiHandler.selectedModel ? {
92-
id: this.editor.apiHandler.selectedModel.id,
93-
provider: this.editor.apiHandler.selectedModel.provider
94-
} : null
95-
});
96-
9786
if (!hasModel && !hasConfig) {
9887
this.editor.chatManager.addMessage('assistant', 'Please configure your AI API settings first.');
9988
return;
@@ -120,22 +109,13 @@ export class EventHandler {
120109
}
121110

122111
const domSummary = response?.summary || '';
123-
console.log('📋 DOM Summary Received:', {
124-
length: domSummary.length,
125-
preview: domSummary.substring(0, 300) + '...'
126-
});
127112

128113
try {
129114
const aiResponse = await this.editor.apiHandler.callAIAPI(message, domSummary);
130-
console.log('✅ AI Response Received:', {
131-
type: aiResponse.type,
132-
codeLength: aiResponse.code?.length || 0
133-
});
134115
this.editor.chatManager.removeMessage(loadingId);
135116
this.handleAIResponse(aiResponse);
136117
} catch (error) {
137-
console.error('❌ AI Generation Error:', error);
138-
this.editor.chatManager.removeMessage(loadingId);
118+
this.editor.chatManager.removeMessage(loadingId);
139119
this.editor.chatManager.addMessage('assistant', `Error: ${error.message}`, { error: true });
140120
}
141121
});
@@ -146,27 +126,17 @@ export class EventHandler {
146126
}
147127

148128
handleAIResponse(response) {
149-
console.log('🎯 Handling AI Response:', {
150-
isArray: Array.isArray(response),
151-
type: response?.type,
152-
hasCode: !!response?.code,
153-
responseKeys: Object.keys(response || {})
154-
});
155-
156129
if (Array.isArray(response)) {
157-
console.log('📝 Displaying array response (actions)');
158130
this.editor.chatManager.addMessage('assistant', 'I\'ll apply these changes to the page:', {
159131
code: JSON.stringify(response, null, 2),
160132
actions: response
161133
});
162134
} else if (response.type === 'script' && response.code) {
163-
console.log('💻 Displaying script response, code length:', response.code.length);
164135
this.editor.chatManager.addMessage('assistant', 'I\'ve generated this code for you:', {
165136
code: response.code,
166137
isScript: true
167138
});
168139
} else {
169-
console.error('⚠️ Unexpected response format:', response);
170140
this.editor.chatManager.addMessage('assistant', 'Unexpected response format from AI', { error: true });
171141
}
172142
}

src/ai_dom_editor/editor/helpers/userscript_handler.js

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// src/ai_dom_editor/editor/helpers/userscript_handler.js
1+
import ScriptAnalyzer from '../../../utils/scriptAnalyzer.js';
22

33
export class UserscriptHandler {
44
constructor(editor) {
@@ -9,45 +9,59 @@ export class UserscriptHandler {
99
try {
1010
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
1111
const url = new URL(tab.url);
12+
let scriptCode = typeof code === 'string' ? code : this.convertActionsToScript(code);
13+
const userPrompt = this.editor.eventHandler.lastUserPrompt || '';
14+
15+
scriptCode = scriptCode.replace(/\/\/\s*==UserScript==[\s\S]*?\/\/\s*==\/UserScript==\n*/g, '').trim();
16+
17+
const detectedApis = ScriptAnalyzer.detectGMApiUsage(scriptCode);
18+
const suggestedRunAt = ScriptAnalyzer.suggestRunAt(scriptCode);
19+
const scriptName = ScriptAnalyzer.generateScriptName(url.hostname, userPrompt);
1220

13-
const scriptCode = typeof code === 'string' ? code : this.convertActionsToScript(code);
14-
15-
const siteName = url.hostname.replace('www.', '').split('.')[0];
16-
const capitalizedSite = siteName.charAt(0).toUpperCase() + siteName.slice(1);
17-
const date = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
18-
19-
const description = this.editor.eventHandler.lastUserPrompt || `Modifications for ${url.hostname}`;
21+
const metadata = {
22+
name: scriptName,
23+
namespace: 'https://codetweak.local',
24+
version: '1.0.0',
25+
description: userPrompt || `Modifications for ${url.hostname}`,
26+
author: 'CodeTweak AI',
27+
matches: [`${url.origin}/*`],
28+
gmApis: detectedApis,
29+
runAt: suggestedRunAt
30+
};
2031

21-
const userscript = `// ==UserScript==
22-
// @name ${capitalizedSite} - ${date}
23-
// @namespace http://tampermonkey.net/
24-
// @version 1.0.0
25-
// @description ${description}
26-
// @author AI Assistant
27-
// @match ${url.origin}/*
28-
// @grant GM_addStyle
29-
// @grant GM_getValue
30-
// @grant GM_setValue
31-
// @run-at document-end
32-
// ==/UserScript==
33-
34-
(function() {
35-
'use strict';
36-
37-
${scriptCode.split('\n').map(line => ' ' + line).join('\n')}
38-
})();`;
32+
const wrappedCode = this.wrapInIIFE(scriptCode);
33+
const finalScript = ScriptAnalyzer.rebuildWithEnhancedMetadata(wrappedCode, metadata);
3934

35+
// Send to editor
4036
chrome.runtime.sendMessage({
4137
action: 'createScriptFromAI',
42-
script: userscript,
38+
script: finalScript,
4339
url: tab.url
4440
});
4541

4642
this.editor.chatManager.addMessage('assistant', '✓ Script created and opened in editor!');
4743
} catch (error) {
44+
console.error('❌ Error creating userscript:', error);
4845
this.editor.chatManager.addMessage('assistant', `Error creating script: ${error.message}`, { error: true });
4946
}
5047
}
48+
49+
wrapInIIFE(code) {
50+
const isWrapped = /^\s*\(\s*function\s*\(/.test(code) || /^\s*\(function\s*\(/.test(code);
51+
52+
if (isWrapped) {
53+
return code;
54+
}
55+
56+
const lines = code.split('\n');
57+
const indentedLines = lines.map(line => ' ' + line);
58+
59+
return `(function() {
60+
'use strict';
61+
62+
${indentedLines.join('\n')}
63+
})();`;
64+
}
5165

5266
convertActionsToScript(actions) {
5367
const lines = [];

src/ai_dom_editor/settings/ai_settings.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ <h3>Getting Started</h3>
4141
<li>Obtain an API key from your chosen provider</li>
4242
<li>OpenAI: <a href="https://platform.openai.com/api-keys" target="_blank">platform.openai.com</a></li>
4343
<li>Anthropic: <a href="https://console.anthropic.com/" target="_blank">console.anthropic.com</a></li>
44-
<li>Google: <a href="https://makersuite.google.com/app/apikey" target="_blank">makersuite.google.com</a></li>
44+
<li>Google Gemini: <a href="https://aistudio.google.com/app/apikey" target="_blank">aistudio.google.com</a></li>
45+
<li>For Gemini: Select "Google (Gemini)" provider and your model - the endpoint will be configured automatically</li>
4546
</ul>
4647
</div>
4748
</div>

0 commit comments

Comments
 (0)