Плавающий AI-чат с поддержкой диалога, анализа окружения и self-correction
Size
32.1 KB
Version
2.3.0
Created
Dec 12, 2025
Updated
3 months ago
1// ==UserScript==
2// @name AI Script Generator & Manager
3// @description Плавающий AI-чат с поддержкой диалога, анализа окружения и self-correction
4// @version 2.3.0
5// @match *://*/*
6// @icon https://st.ozone.ru/s3/seller-ui-static/icon/favicon32.png
7// @grant GM.setValue
8// @grant GM.getValue
9// @grant GM.deleteValue
10// @grant GM.xmlHttpRequest
11// @grant GM_addElement
12// @grant unsafeWindow
13// ==/UserScript==
14
15(function() {
16 'use strict';
17
18 console.log('[AI Script Manager] Starting v2.3.0 (Thinking AI + Self-Correction)...');
19
20 // Конфигурация
21 const CONFIG = {
22 STORAGE_KEYS: {
23 SCRIPTS: 'aiScriptManager_scripts',
24 API_KEYS: 'aiScriptManager_apiKeys',
25 CHAT_HISTORY: 'aiScriptManager_chatHistory' // новое
26 },
27 MAX_DOM_LENGTH: 3000,
28 REQUEST_TIMEOUT: 45000, // увеличено — self-correction требует времени
29 SELF_CORRECTION_ATTEMPTS: 2,
30 SYSTEM_PROMPT: `You are an expert JavaScript code generator and debugger for Tampermonkey/Violentmonkey userscripts.
31
32YOU ARE A THINKING AI — ANALYZE BEFORE YOU GENERATE.
33
34CRITICAL RULES:
351. Output ONLY pure JavaScript — NO markdown, NO explanations, NO code blocks.
362. ALWAYS consider the page context: URL, existing elements (e.g., .banner, .ad), frameworks.
373. If previous code failed, read the error and FIX it — don’t repeat the same mistake.
384. If user gives feedback (e.g., "не работает на мобильной версии"), ADAPT the code.
395. Prefer robust selectors (e.g., [class*="banner"], not just .banner).
406. Use try-catch and validate existence before DOM manipulation.
417. Avoid eval(), new Function(), or anything blocked by CSP.
42
43SELF-CORRECTION PROTOCOL:
44- If execution failed, analyze the error.
45- Think: Why did it fail? Missing element? TypeError? CSP?
46- Generate a safer, more defensive version.
47
48FEEDBACK PROTOCOL:
49- User says: "Исправь: [замечание]"
50- You must: (1) read current code, (2) apply feedback, (3) return ONLY fixed code.
51
52OUTPUT FORMAT:
53Return ONLY executable JavaScript.
54Do NOT wrap in \`\`\`js.
55Do NOT add comments unless they explain non-obvious logic.
56
57Example correct output:
58const ads = document.querySelectorAll('.banner, .ad, [class*="promo"], [data-ad]');
59console.log('Found', ads.length, 'ads');
60ads.forEach(el => {
61 if (el && el.parentNode) el.parentNode.removeChild(el);
62});`
63 };
64
65 // Утилиты
66 const Utils = {
67 generateId() {
68 return 'msg_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 6);
69 },
70 getPageContext() {
71 // Анализируем окружение — безопасно и быстро
72 const selectorsToCheck = [
73 '.ad', '.banner', '[data-ad]', '.promo', '.popup',
74 '.modal', 'iframe[src*="ads"]', 'ins.adsbygoogle',
75 '.close', '.dismiss', 'button[type="submit"]',
76 '.product', '.price', '.cart', '.search'
77 ];
78 const foundSelectors = selectorsToCheck.filter(sel => {
79 try { return !!document.querySelector(sel); } catch { return false; }
80 });
81
82 const stats = {
83 totalElements: Math.min(document.querySelectorAll('*').length, 10000),
84 buttons: document.querySelectorAll('button, [role="button"]').length,
85 inputs: document.querySelectorAll('input, textarea').length,
86 links: document.querySelectorAll('a').length,
87 hasJQuery: typeof $ === 'function',
88 hasReact: !!document.querySelector('[data-reactroot], [data-reactid], [data-testid]'),
89 viewport: { w: innerWidth, h: innerHeight }
90 };
91
92 return {
93 url: location.href,
94 title: document.title,
95 foundSelectors,
96 stats,
97 bodyPreview: (document.body?.innerText || '').substring(0, CONFIG.MAX_DOM_LENGTH)
98 };
99 },
100 escapeHtml(text) {
101 const div = document.createElement('div');
102 div.textContent = text;
103 return div.innerHTML;
104 },
105 formatDate(timestamp) {
106 return new Date(timestamp).toLocaleString('ru-RU', {
107 hour: '2-digit', minute: '2-digit', second: '2-digit'
108 });
109 }
110 };
111
112 // Хранилище (без устаревшего SETTINGS — используем только CHAT_HISTORY)
113 const Storage = {
114 get(key, defaultValue) {
115 try {
116 const data = localStorage.getItem(key);
117 return data ? JSON.parse(data) : defaultValue;
118 } catch (e) {
119 console.error('[Storage] Error:', e);
120 return defaultValue;
121 }
122 },
123 set(key, value) {
124 try {
125 localStorage.setItem(key, JSON.stringify(value));
126 } catch (e) {
127 console.error('[Storage] Error:', e);
128 }
129 },
130 getChatHistory() {
131 return this.get(CONFIG.STORAGE_KEYS.CHAT_HISTORY, []);
132 },
133 saveChatHistory(history) {
134 this.set(CONFIG.STORAGE_KEYS.CHAT_HISTORY, history);
135 },
136 clearChatHistory() {
137 this.saveChatHistory([]);
138 },
139 getScripts() {
140 return this.get('aiScriptManager_scripts', []);
141 },
142 saveScripts(scripts) {
143 this.set('aiScriptManager_scripts', scripts);
144 },
145 addScript(scriptData) {
146 const scripts = this.getScripts();
147 const newScript = {
148 id: Utils.generateId(),
149 name: scriptData.name || 'Unnamed',
150 createdAt: Date.now(),
151 prompt: scriptData.prompt,
152 code: scriptData.code,
153 status: 'active',
154 logs: []
155 };
156 scripts.push(newScript);
157 this.saveScripts(scripts);
158 return newScript;
159 },
160 getApiKeys() {
161 return this.get('aiScriptManager_apiKeys', { openai: '', openrouter: '' });
162 },
163 saveApiKeys(keys) {
164 this.set('aiScriptManager_apiKeys', keys);
165 }
166 };
167
168 // AI Сервис с поддержкой истории и self-correction
169 const AIService = {
170 async generateCode(userPrompt, chatHistory = []) {
171 const pageContext = Utils.getPageContext();
172
173 const contextSummary = `
174[PAGE CONTEXT]
175URL: ${pageContext.url}
176Title: ${pageContext.title}
177Found selectors: ${pageContext.foundSelectors.length ? pageContext.foundSelectors.join(', ') : 'none'}
178Stats: ${pageContext.stats.totalElements} elements, ${pageContext.stats.buttons} buttons, viewport ${pageContext.stats.viewport.w}×${pageContext.stats.viewport.h}
179Has jQuery: ${pageContext.stats.hasJQuery}, React: ${pageContext.stats.hasReact}
180Body preview: ${pageContext.bodyPreview}
181`;
182
183 // Подготавливаем историю сообщений для LLM
184 const messages = [
185 { role: 'system', content: CONFIG.SYSTEM_PROMPT + '\n\n' + contextSummary }
186 ];
187
188 // Добавляем историю чата (если есть)
189 chatHistory.slice(-6).forEach(msg => { // ограничим 6 последними сообщениями
190 if (msg.role === 'assistant' && msg.generatedCode) {
191 // Для AI важно видеть не только текст, но и код
192 messages.push({
193 role: 'assistant',
194 content: msg.content + '\n\nGenerated code:\n' + msg.generatedCode
195 });
196 } else if (msg.role === 'system' && msg.executionResult) {
197 const res = msg.executionResult;
198 const execInfo = res.success
199 ? `✅ Success. Logs: ${res.logs ? res.logs.slice(0, 3).join('; ') : 'none'}`
200 : `❌ Error: ${res.error}`;
201 messages.push({ role: 'system', content: execInfo });
202 } else {
203 messages.push({ role: msg.role, content: msg.content });
204 }
205 });
206
207 // Добавляем новый запрос
208 messages.push({ role: 'user', content: userPrompt });
209
210 // Пробуем RM.aiCall → OpenRouter → OpenAI
211 for (let i = 0; i < 3; i++) {
212 try {
213 if (i === 0 && typeof RM?.aiCall === 'function') {
214 const code = await RM.aiCall(JSON.stringify(messages));
215 return this.cleanCode(code);
216 } else if (i === 1) {
217 const keys = Storage.getApiKeys();
218 if (keys.openrouter) {
219 return await this.callAPI('https://openrouter.ai/api/v1/chat/completions', 'anthropic/claude-3.5-sonnet', messages, keys.openrouter, true);
220 }
221 } else if (i === 2) {
222 const keys = Storage.getApiKeys();
223 if (keys.openai) {
224 return await this.callAPI('https://api.openai.com/v1/chat/completions', 'gpt-4o-mini', messages, keys.openai, false);
225 }
226 }
227 } catch (e) {
228 console.warn(`[AI] Backend ${['RM', 'OpenRouter', 'OpenAI'][i]} failed:`, e.message);
229 }
230 }
231
232 throw new Error('Все AI backends недоступны. Добавьте API-ключ в настройки → ⚙️.');
233 },
234
235 async callAPI(url, model, messages, apiKey, isOpenRouter) {
236 return new Promise((resolve, reject) => {
237 const timeoutId = setTimeout(() => reject(new Error('timeout')), CONFIG.REQUEST_TIMEOUT);
238
239 const headers = {
240 'Content-Type': 'application/json',
241 'Authorization': 'Bearer ' + apiKey
242 };
243 if (isOpenRouter) {
244 headers['HTTP-Referer'] = location.href;
245 headers['X-Title'] = 'AI Script Manager';
246 }
247
248 GM.xmlHttpRequest({
249 method: 'POST',
250 url: url,
251 headers: headers,
252 data: JSON.stringify({
253 model: model,
254 messages: messages,
255 temperature: 0.3, // ↓ меньше "креатива", больше стабильности
256 max_tokens: 1500
257 }),
258 onload: (res) => {
259 clearTimeout(timeoutId);
260 try {
261 const data = JSON.parse(res.responseText);
262 if (data.error) throw new Error(data.error.message || data.error);
263 const content = data.choices[0]?.message?.content || '';
264 resolve(this.cleanCode(content));
265 } catch (e) {
266 reject(new Error('Parse: ' + e.message));
267 }
268 },
269 onerror: () => {
270 clearTimeout(timeoutId);
271 reject(new Error('network'));
272 }
273 });
274 });
275 },
276
277 cleanCode(code) {
278 return code
279 .replace(/^```(javascript|js)?\s*\n?/gm, '')
280 .replace(/\n?```$/gm, '')
281 .replace(/^\s*\/\/.*$/gm, '') // убираем комментарии-объяснения
282 .trim();
283 }
284 };
285
286 // Выполнение — с логированием в консоль и перехватом ошибок
287 const ScriptExecutor = {
288 // Выполняет код и возвращает результат (для анализа AI)
289 executeScriptTemp(code) {
290 return new Promise((resolve) => {
291 const logs = [];
292 const originalLog = console.log;
293 const originalError = console.error;
294
295 // Перехватываем console.log/error
296 console.log = (...args) => { logs.push(args.map(String).join(' ')); originalLog.apply(console, args); };
297 console.error = (...args) => { logs.push('[ERROR] ' + args.map(String).join(' ')); originalError.apply(console, args); };
298
299 try {
300 const wrapper = `
301 (() => {
302 "use strict";
303 try { ${code} }
304 catch (e) {
305 console.error('Runtime error:', e.message, e.stack);
306 throw e;
307 }
308 })();
309 `;
310
311 if (typeof GM_addElement === 'function') {
312 GM_addElement('script', { text: wrapper });
313 } else {
314 const s = document.createElement('script');
315 s.textContent = wrapper;
316 (document.head || document.documentElement).appendChild(s);
317 s.remove();
318 }
319
320 setTimeout(() => {
321 console.log = originalLog;
322 console.error = originalError;
323 resolve({ success: true, logs });
324 }, 100); // дадим время на выполнение
325
326 } catch (error) {
327 console.log = originalLog;
328 console.error = originalError;
329 resolve({
330 success: false,
331 error: error.message || String(error),
332 logs
333 });
334 }
335 });
336 },
337
338 // Сохранённые скрипты — как раньше
339 executeScript(script) {
340 console.log('[Exec] Running saved script:', script.name);
341 // Обёртка без логгера — для production
342 const wrapper = `(() => {"use strict";try{${script.code}}catch(e){console.error('[AI Script]',e)}})();`;
343 if (typeof GM_addElement === 'function') {
344 GM_addElement('script', { text: wrapper });
345 } else {
346 const s = document.createElement('script');
347 s.textContent = wrapper;
348 (document.head || document.documentElement).appendChild(s);
349 s.remove();
350 }
351 },
352
353 executeActiveScripts() {
354 Storage.getScripts()
355 .filter(s => s.status === 'active')
356 .forEach(s => this.executeScript(s));
357 }
358 };
359
360 // UI — с поддержкой диалога и редактирования
361 const UI = {
362 elements: {},
363 chatHistory: [],
364
365 init() {
366 if (!document.body) return setTimeout(() => this.init(), 100);
367 this.injectStyles();
368 this.createWidget();
369 this.loadChatHistory();
370 console.log('[UI] Ready with thinking AI!');
371 },
372
373 injectStyles() { /* ... (стили из v2.2.0, без изменений) ... */
374 const style = document.createElement('style');
375 style.textContent = `
376 /* ... прежние стили ... */
377 .ai-code-actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
378 .ai-modal-edit { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; z-index: 2147483647; }
379 .ai-modal-edit.visible { display: flex; }
380 .ai-modal-edit-content {
381 background: white; border-radius: 16px; padding: 24px; width: 90%; max-width: 700px; max-height: 85vh; overflow: hidden; display: flex; flex-direction: column;
382 }
383 .ai-modal-edit-header { font-weight: 600; margin-bottom: 16px; color: #1f2937; }
384 .ai-modal-edit-body { flex: 1; display: flex; flex-direction: column; gap: 16px; overflow: auto; }
385 .ai-modal-edit-textarea {
386 width: 100%; height: 150px; font-family: monospace; padding: 12px; border: 1px solid #d1d5db; border-radius: 8px;
387 }
388 .ai-modal-edit-feedback {
389 width: 100%; height: 60px; padding: 10px; border: 1px solid #d1d5db; border-radius: 8px;
390 }
391 .ai-modal-edit-footer { display: flex; gap: 10px; justify-content: flex-end; margin-top: 16px; }
392 `;
393 (document.head || document.documentElement).appendChild(style);
394 },
395
396 createWidget() {
397 const container = document.createElement('div');
398 container.className = 'ai-manager';
399 container.innerHTML = `
400 <button class="ai-toggle-btn" id="aiToggle">🤖</button>
401 <div class="ai-window" id="aiWindow">
402 <!-- ... прежний HTML ... -->
403 <div class="ai-view active" id="chatView">
404 <div class="ai-messages" id="messages">
405 <div class="ai-message system">
406 <div class="ai-avatar">🧠</div>
407 <div class="ai-content">
408 Я — думающий AI. Могу:<br>
409 • Анализировать страницу перед генерацией<br>
410 • Исправлять код по вашим замечаниям<br>
411 • Автоисправлять ошибки после выполнения<br>
412 <br>
413 Пример запроса: «Сделай кнопку "Скрыть всё", которая удаляет все баннеры и модалки»
414 </div>
415 </div>
416 </div>
417 <div class="ai-input-area">
418 <div class="ai-input-wrapper">
419 <textarea class="ai-input" id="input" placeholder="Опишите задачу или дайте обратную связь..."></textarea>
420 <button class="ai-send-btn" id="send">➤</button>
421 </div>
422 </div>
423 </div>
424 <!-- scriptsView, settingsView — без изменений -->
425 </div>
426
427 <!-- Modal для редактирования -->
428 <div class="ai-modal-edit" id="editModal">
429 <div class="ai-modal-edit-content">
430 <div class="ai-modal-edit-header">✏️ Исправить код</div>
431 <div class="ai-modal-edit-body">
432 <label>Текущий код:</label>
433 <textarea class="ai-modal-edit-textarea" id="editCode"></textarea>
434 <label>Ваш комментарий (что не так?):</label>
435 <textarea class="ai-modal-edit-feedback" id="editFeedback" placeholder="Например: не работает на мобильной версии, не находит кнопку 'Закрыть' и т.д."></textarea>
436 </div>
437 <div class="ai-modal-edit-footer">
438 <button class="ai-btn ai-btn-secondary" id="editCancel">Отмена</button>
439 <button class="ai-btn ai-btn-primary" id="editSubmit">Отправить на исправление</button>
440 </div>
441 </div>
442 </div>
443 `;
444
445 (document.body || document.documentElement).appendChild(container);
446 this.elements = {
447 toggle: container.querySelector('#aiToggle'),
448 window: container.querySelector('#aiWindow'),
449 close: container.querySelector('#aiClose'),
450 messages: container.querySelector('#messages'),
451 input: container.querySelector('#input'),
452 send: container.querySelector('#send'),
453 // modals
454 editModal: container.querySelector('#editModal'),
455 editCode: container.querySelector('#editCode'),
456 editFeedback: container.querySelector('#editFeedback'),
457 editCancel: container.querySelector('#editCancel'),
458 editSubmit: container.querySelector('#editSubmit')
459 };
460
461 // События
462 this.elements.toggle.onclick = () => this.toggleWindow();
463 this.elements.close.onclick = () => this.toggleWindow();
464 this.elements.send.onclick = () => this.sendMessage();
465 this.elements.input.onkeydown = (e) => {
466 if (e.key === 'Enter' && !e.shiftKey) {
467 e.preventDefault(); this.sendMessage();
468 }
469 };
470
471 // Edit modal
472 this.elements.editCancel.onclick = () => this.hideEditModal();
473 this.elements.editSubmit.onclick = () => this.submitEdit();
474 container.querySelectorAll('.ai-nav-btn').forEach(btn => {
475 btn.onclick = () => this.switchView(btn.dataset.view);
476 });
477 document.getElementById('saveSettings').onclick = () => this.saveSettings();
478 },
479
480 loadChatHistory() {
481 this.chatHistory = Storage.getChatHistory();
482 this.renderChatHistory();
483 },
484
485 renderChatHistory() {
486 const messagesEl = this.elements.messages;
487 messagesEl.innerHTML = '';
488 if (this.chatHistory.length === 0) {
489 messagesEl.innerHTML = `
490 <div class="ai-message system">
491 <div class="ai-avatar">🧠</div>
492 <div class="ai-content">Начните диалог — опишите, что хотите сделать.</div>
493 </div>`;
494 return;
495 }
496
497 this.chatHistory.forEach(msg => {
498 if (msg.role === 'user') {
499 this.addMessageToDOM('user', msg.content);
500 } else if (msg.role === 'assistant' && msg.generatedCode) {
501 this.addCodeMessageToDOM(msg.generatedCode, msg.content, msg.executionResult);
502 } else if (msg.role === 'system') {
503 this.addMessageToDOM('system', msg.content);
504 }
505 });
506 messagesEl.scrollTop = messagesEl.scrollHeight;
507 },
508
509 addMessageToDOM(role, content) {
510 const msg = document.createElement('div');
511 msg.className = 'ai-message ' + role;
512 const avatar = { user: '👤', assistant: '🤖', system: '🧠', error: '⚠️' }[role] || 'ℹ️';
513 msg.innerHTML = `<div class="ai-avatar">${avatar}</div><div class="ai-content">${Utils.escapeHtml(content)}</div>`;
514 this.elements.messages.appendChild(msg);
515 },
516
517 addCodeMessageToDOM(code, prompt, executionResult = {}) {
518 const msg = document.createElement('div');
519 msg.className = 'ai-message assistant';
520 const escapedCode = Utils.escapeHtml(code);
521 const statusIcon = executionResult.success ? '✅' : executionResult.error ? '⚠️' : '';
522 const statusText = executionResult.success ? 'Выполнен' : executionResult.error ? 'Ошибка' : '';
523
524 msg.innerHTML = `
525 <div class="ai-avatar">🤖</div>
526 <div class="ai-content">
527 <div>${statusIcon} ${prompt} ${statusText ? `(${statusText})` : ''}</div>
528 <div class="ai-code">${escapedCode}</div>
529 <div class="ai-code-actions">
530 <button class="ai-btn ai-btn-primary" data-action="execute">▶️ Выполнить</button>
531 ${!executionResult.error ? `<button class="ai-btn ai-btn-secondary" data-action="save">💾 Сохранить</button>` : ''}
532 <button class="ai-btn ai-btn-secondary" data-action="edit">✏️ Исправить</button>
533 </div>
534 ${executionResult.error ? `<div class="ai-message error"><div class="ai-avatar">⚠️</div><div class="ai-content">Ошибка: ${Utils.escapeHtml(executionResult.error)}</div></div>` : ''}
535 </div>
536 `;
537 this.elements.messages.appendChild(msg);
538 this.setupCodeActions(msg, code, prompt, executionResult);
539 },
540
541 setupCodeActions(container, code, prompt, executionResult) {
542 const executeBtn = container.querySelector('[data-action="execute"]');
543 const saveBtn = container.querySelector('[data-action="save"]');
544 const editBtn = container.querySelector('[data-action="edit"]');
545
546 executeBtn?.addEventListener('click', () => this.executeAndLog(code, prompt));
547 saveBtn?.addEventListener('click', () => this.saveCode(code, prompt));
548 editBtn?.addEventListener('click', () => this.showEditModal(code));
549 },
550
551 async executeAndLog(code, prompt) {
552 // 1. Выполняем
553 const result = await ScriptExecutor.executeScriptTemp(code);
554 // 2. Сохраняем в историю
555 const msg = {
556 id: Utils.generateId(),
557 role: 'system',
558 content: result.success ? '✅ Код выполнен успешно' : '❌ Ошибка выполнения',
559 executionResult: result,
560 timestamp: Date.now()
561 };
562 this.chatHistory.push(msg);
563 Storage.saveChatHistory(this.chatHistory);
564 // 3. Показываем результат
565 if (result.success) {
566 this.addMessageToDOM('assistant', '✅ Выполнено! Логи: ' + (result.logs?.slice(0, 3).join('; ') || '—'));
567 } else {
568 this.addMessageToDOM('error', '❌ Ошибка: ' + result.error);
569 }
570 },
571
572 async sendMessage() {
573 const prompt = this.elements.input.value.trim();
574 if (!prompt) return;
575
576 this.elements.input.value = '';
577 this.addMessageToDOM('user', prompt);
578
579 // Сохраняем в историю
580 const userMsg = { id: Utils.generateId(), role: 'user', content: prompt, timestamp: Date.now() };
581 this.chatHistory.push(userMsg);
582 Storage.saveChatHistory(this.chatHistory);
583
584 // Отправляем в AI
585 try {
586 const code = await AIService.generateCode(prompt, this.chatHistory);
587
588 // Self-correction loop (до 2 попыток)
589 let result = await ScriptExecutor.executeScriptTemp(code);
590 let attempts = 0;
591 while (!result.success && attempts < CONFIG.SELF_CORRECTION_ATTEMPTS) {
592 attempts++;
593 this.addMessageToDOM('system', `🔁 Попытка ${attempts} автокоррекции...`);
594
595 const correctionPrompt = `Предыдущий код вызвал ошибку:\n${result.error}\n\nИсправь его. Верни ТОЛЬКО JavaScript.`;
596 const fixedCode = await AIService.generateCode(correctionPrompt, [
597 ...this.chatHistory,
598 { role: 'assistant', content: 'Generated code', generatedCode: code },
599 { role: 'system', content: 'Execution failed', executionResult: result }
600 ]);
601 code = fixedCode;
602 result = await ScriptExecutor.executeScriptTemp(code);
603 }
604
605 // Сохраняем ответ AI
606 const aiMsg = {
607 id: Utils.generateId(),
608 role: 'assistant',
609 content: prompt,
610 generatedCode: code,
611 executionResult: result,
612 timestamp: Date.now()
613 };
614 this.chatHistory.push(aiMsg);
615 Storage.saveChatHistory(this.chatHistory);
616
617 // Отображаем
618 this.addCodeMessageToDOM(code, prompt, result);
619
620 } catch (error) {
621 console.error('[UI] Generation failed:', error);
622 const errMsg = { id: Utils.generateId(), role: 'error', content: '❌ ' + error.message, timestamp: Date.now() };
623 this.chatHistory.push(errMsg);
624 Storage.saveChatHistory(this.chatHistory);
625 this.addMessageToDOM('error', errMsg.content);
626 }
627 },
628
629 showEditModal(code) {
630 this.elements.editCode.value = code;
631 this.elements.editFeedback.value = '';
632 this.elements.editModal.classList.add('visible');
633 this.currentEditingCode = code;
634 },
635
636 hideEditModal() {
637 this.elements.editModal.classList.remove('visible');
638 },
639
640 async submitEdit() {
641 const feedback = this.elements.editFeedback.value.trim();
642 const code = this.elements.editCode.value;
643 if (!feedback) return alert('Опишите, что нужно исправить.');
644
645 this.hideEditModal();
646 this.addMessageToDOM('user', `Исправь: «${feedback}»`);
647
648 const editPrompt = `Исправь следующий код по моему комментарию:\n\nКОММЕНТАРИЙ: ${feedback}\n\nТЕКУЩИЙ КОД:\n${code}`;
649 try {
650 const fixedCode = await AIService.generateCode(editPrompt, this.chatHistory);
651 const result = await ScriptExecutor.executeScriptTemp(fixedCode);
652
653 const aiMsg = {
654 id: Utils.generateId(),
655 role: 'assistant',
656 content: `Исправлено по запросу: ${feedback}`,
657 generatedCode: fixedCode,
658 executionResult: result,
659 timestamp: Date.now()
660 };
661 this.chatHistory.push(aiMsg);
662 Storage.saveChatHistory(this.chatHistory);
663
664 this.addCodeMessageToDOM(fixedCode, `Исправлено: ${feedback}`, result);
665 } catch (e) {
666 this.addMessageToDOM('error', '❌ Не удалось исправить: ' + e.message);
667 }
668 },
669
670 saveCode(code, prompt) {
671 const name = prompt.substring(0, 50).trim() || 'Скрипт';
672 Storage.addScript({ name, code, prompt });
673 ScriptExecutor.executeScript({ code, name });
674 this.addMessageToDOM('assistant', `💾 «${name}» сохранён и запущен!`);
675 },
676
677 // ... остальные методы (switchView, loadSettings и т.д.) без изменений ...
678 toggleWindow() { this.elements.window.classList.toggle('visible'); },
679 switchView(v) { /* как раньше */ },
680 loadSettings() { /* как раньше */ },
681 saveSettings() { /* как раньше */ },
682 loadScripts() { /* как раньше */ },
683 pauseScript() { /* как раньше */ },
684 activateScript() { /* как раньше */ },
685 viewScript() { /* как раньше */ },
686 deleteScript() { /* как раньше */ }
687 };
688
689 // Инициализация
690 function init() {
691 UI.init();
692 ScriptExecutor.executeActiveScripts();
693 }
694
695 window.UI = UI;
696 if (document.readyState === 'loading') {
697 document.addEventListener('DOMContentLoaded', init);
698 } else {
699 init();
700 }
701
702})();