Skip to content

Commit 20b6a1f

Browse files
committed
Unstable
1 parent 5a0a1c0 commit 20b6a1f

File tree

12 files changed

+791
-648
lines changed

12 files changed

+791
-648
lines changed

src/ai_dom_editor/editor/helpers/api_handler.js

Lines changed: 34 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ export class ApiHandler {
163163
return { 'X-API-Key': apiKey };
164164
}
165165

166+
if (provider.includes('aimlapi')) {
167+
return { 'Authorization': `Bearer ${apiKey}` };
168+
}
169+
166170
// Gemini API uses x-goog-api-key header
167171
if (modelId.includes('gemini') || modelId.includes('gemma') || provider.includes('gemini')) {
168172
return { 'x-goog-api-key': apiKey };
@@ -227,98 +231,40 @@ export class ApiHandler {
227231
}
228232

229233
// Primary method to call AI APIs. Returns { type: 'script', code: '...' }
230-
async callAIAPI(userMessage, domSummary) {
231-
232-
const systemPrompt = `You are an expert JavaScript developer specializing in DOM manipulation and userscripts. Your task is to generate clean, efficient JavaScript code that modifies a webpage based on the user's request.
233-
234-
**CRITICAL CONTEXT - YOU MUST ANALYZE THIS:**
235-
236-
**Current Page Structure (DOM Summary):**
237-
${domSummary}
238-
239-
**INSTRUCTIONS:**
240-
241-
1. **DEEPLY ANALYZE THE DOM:** Study the DOM structure carefully. Look for:
242-
- Element tags, IDs, and class names you can target
243-
- The hierarchy and nesting of elements
244-
- Text content that helps identify elements
245-
- Attributes that can be used for precise selection
246-
247-
2. **USE SPECIFIC SELECTORS:** Based on your analysis:
248-
- Prefer IDs when available (e.g., \`document.getElementById('specific-id')\`)
249-
- Use class names with querySelector (e.g., \`document.querySelector('.specific-class')\`)
250-
- Use tag names with additional context (e.g., \`document.querySelector('div > h1')\`)
251-
- Combine selectors for precision (e.g., \`document.querySelector('div.container > h1.title')\`)
252-
253-
3. **GREASEMONKEY APIs AVAILABLE:**
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
261-
- \`GM_getValue(key, defaultValue)\` - Retrieve stored data
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:**
269-
- \`GM_notification(options)\` - Show desktop notifications
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)
279-
280-
4. **CODE QUALITY REQUIREMENTS:**
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.
311-
312-
**USER'S REQUEST:**
313-
${userMessage}
314-
315-
**YOUR RESPONSE:** Pure JavaScript code only (absolutely NO metadata, NO wrappers, NO fences).`;
234+
async callAIAPI(userMessage, domSummary, previousCode = null) {
235+
236+
let systemPrompt = `You are a JavaScript developer who writes userscripts.
237+
This is the current page structure (DOM summary):
238+
${domSummary}`;
239+
240+
if (previousCode) {
241+
systemPrompt += `
242+
This is the previously generated code:
243+
\`\`\`javascript
244+
${previousCode}
245+
\`\`\`
246+
The user wants to modify the above script.
247+
`;
248+
}
249+
250+
systemPrompt += `
251+
Your task is to generate JavaScript code to modify the page based on the user's request.
252+
- Use specific selectors. Prefer IDs, then classes, then tags.
253+
- You can use Greasemonkey APIs: GM_addStyle, GM_setValue, GM_getValue, GM_deleteValue, GM_listValues, GM_xmlhttpRequest, GM_notification, GM_addElement, GM_registerMenuCommand, GM_openInTab, GM_setClipboard, GM_download, GM_getResourceText, unsafeWindow.
254+
- Write immediately executable code.
255+
- Check if elements exist before using them.
256+
- Use modern JavaScript.
257+
- Do not include userscript metadata headers (like // ==UserScript==), markdown fences, or IIFE wrappers. Only provide pure JavaScript code.
258+
259+
User's request: ${userMessage}
260+
261+
Your response is only the JavaScript code.`;
316262

317263
// Decide model config (selectedModel preferred, else global apiConfig)
318264
const modelConfig = this.selectedModel || this.apiConfig || {};
319265
const modelId = modelConfig.id || modelConfig.model || this.apiConfig?.model || 'gpt-4';
320266
const endpoint = modelConfig.endpoint || this.apiConfig?.endpoint;
321-
const apiKey = modelConfig.apiKey || modelConfig.apiKey || this.apiConfig?.apiKey || this.apiConfig?.key;
267+
const apiKey = modelConfig.apiKey || modelConfig.key || this.apiConfig?.apiKey || this.apiConfig?.key;
322268

323269
if (!endpoint) {
324270
this.editor?.uiManager?.showConfigBanner?.();
@@ -334,7 +280,7 @@ ${userMessage}
334280

335281
// build messages depending on heuristics (some providers expect system+user, others expect just user)
336282
const provider = (modelConfig.provider || '').toLowerCase();
337-
const isGoogleish = modelId.toLowerCase().includes('gemini') || modelId.toLowerCase().includes('gemma') || provider.includes('google') || provider.includes('vertex');
283+
const isGoogleish = !provider.includes('aimlapi') && (modelId.toLowerCase().includes('gemini') || modelId.toLowerCase().includes('gemma') || provider.includes('google') || provider.includes('vertex'));
338284
const isAnthropic = provider.includes('anthropic') || modelId.toLowerCase().includes('claude');
339285

340286
// prefer settings on per-model config, fallback to global

src/ai_dom_editor/editor/helpers/chat_manager.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,26 @@ export class ChatManager {
77
this.editor = editor;
88
this.messages = [];
99
this.currentSiteUrl = '';
10+
this.scriptId = null; // Add scriptId property
11+
}
12+
13+
setScriptId(scriptId) {
14+
this.scriptId = scriptId;
15+
this.loadChatHistory();
1016
}
1117

1218
async loadChatHistory() {
1319
try {
14-
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
15-
if (!tab || !tab.url) return;
20+
if (!this.scriptId) {
21+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
22+
if (!tab || !tab.url) return;
1623

17-
const url = new URL(tab.url);
18-
this.currentSiteUrl = `${url.protocol}//${url.hostname}`;
24+
const url = new URL(tab.url);
25+
this.currentSiteUrl = `${url.protocol}//${url.hostname}`;
26+
this.scriptId = `chat_${this.currentSiteUrl}`;
27+
}
1928

20-
const storageKey = `aiChatHistory_${this.currentSiteUrl}`;
29+
const storageKey = `aiChatHistory_${this.scriptId}`;
2130
const { [storageKey]: history } = await chrome.storage.local.get(storageKey);
2231

2332
if (history && history.messages && history.messages.length > 0) {
@@ -39,11 +48,12 @@ export class ChatManager {
3948

4049
async saveChatHistory() {
4150
try {
42-
if (!this.currentSiteUrl) return;
51+
if (!this.scriptId) return;
4352

44-
const storageKey = `aiChatHistory_${this.currentSiteUrl}`;
53+
const storageKey = `aiChatHistory_${this.scriptId}`;
4554
await chrome.storage.local.set({
4655
[storageKey]: {
56+
id: this.scriptId,
4757
url: this.currentSiteUrl,
4858
messages: this.messages,
4959
lastUpdated: Date.now()
@@ -54,6 +64,11 @@ export class ChatManager {
5464
}
5565
}
5666

67+
getPreviousCode() {
68+
const lastAssistantMessage = this.messages.slice().reverse().find(msg => msg.role === 'assistant' && msg.data && msg.data.code);
69+
return lastAssistantMessage ? lastAssistantMessage.data.code : null;
70+
}
71+
5772
addMessage(role, text, data = {}) {
5873
this.messages.push({ role, text, timestamp: new Date(), data });
5974

@@ -73,13 +88,13 @@ export class ChatManager {
7388
}
7489

7590
async clearChat() {
76-
if (confirm('Clear conversation history for this site?')) {
91+
if (confirm('Clear conversation history for this script?')) {
7792
this.messages = [];
7893
this.editor.elements.messages.innerHTML = '';
7994
this.editor.uiManager.showWelcomeMessage();
8095

81-
if (this.currentSiteUrl) {
82-
const storageKey = `aiChatHistory_${this.currentSiteUrl}`;
96+
if (this.scriptId) {
97+
const storageKey = `aiChatHistory_${this.scriptId}`;
8398
await chrome.storage.local.remove(storageKey);
8499
}
85100
}

src/ai_dom_editor/editor/helpers/event_handler.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,21 @@ export class EventHandler {
111111
const domSummary = response?.summary || '';
112112

113113
try {
114-
const aiResponse = await this.editor.apiHandler.callAIAPI(message, domSummary);
114+
let previousCode = this.editor.chatManager.getPreviousCode();
115+
const scriptNameMatch = message.match(/@(\S+)/);
116+
if (scriptNameMatch) {
117+
const scriptName = scriptNameMatch[1];
118+
try {
119+
previousCode = await this.editor.userscriptHandler.getScriptContent(scriptName);
120+
this.editor.chatManager.setScriptId(scriptName);
121+
} catch (error) {
122+
this.editor.chatManager.addMessage('assistant', `Error: ${error.message}`);
123+
this.editor.chatManager.removeMessage(loadingId);
124+
return;
125+
}
126+
}
127+
128+
const aiResponse = await this.editor.apiHandler.callAIAPI(message, domSummary, previousCode);
115129
this.editor.chatManager.removeMessage(loadingId);
116130
this.handleAIResponse(aiResponse);
117131
} catch (error) {

src/ai_dom_editor/editor/helpers/userscript_handler.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ export class UserscriptHandler {
55
this.editor = editor;
66
}
77

8+
async getScriptContent(scriptName) {
9+
return new Promise((resolve, reject) => {
10+
chrome.runtime.sendMessage({ action: 'getScriptContent', scriptName }, (response) => {
11+
if (chrome.runtime.lastError) {
12+
return reject(new Error(chrome.runtime.lastError.message));
13+
}
14+
if (response.error) {
15+
return reject(new Error(response.error));
16+
}
17+
resolve(response.code);
18+
});
19+
});
20+
}
21+
822
async createUserscript(code) {
923
try {
1024
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

0 commit comments

Comments
 (0)