- deps textarea: rows=3, resize:none, overflow-y:auto - aiCheck (Python): после линтера проверяет внешние импорты если deps поле пустое — предупреждение с именами модулей - Python env: v1.0 → v1.1 (содержит boto3+requests+psycopg2-binary)
208 lines
8.9 KiB
JavaScript
208 lines
8.9 KiB
JavaScript
/* ai.js — AI/LLM функции: проверка, генерация, объяснение, ассистент */
|
||
|
||
// PYTHON_STDLIB — стандартная библиотека Python (не требуют pip install).
|
||
var PYTHON_STDLIB = new Set([
|
||
'os','sys','json','time','re','math','io','collections','itertools','functools',
|
||
'datetime','pathlib','urllib','http','threading','asyncio','logging','random',
|
||
'string','struct','hashlib','hmac','base64','uuid','copy','abc','typing',
|
||
'dataclasses','contextlib','enum','warnings','traceback','inspect','importlib',
|
||
'subprocess','socket','ssl','email','html','xml','csv','sqlite3','unittest',
|
||
'gc','weakref','array','queue','heapq','bisect','decimal','fractions',
|
||
'statistics','textwrap','difflib','shutil','glob','fnmatch','tempfile',
|
||
'zipfile','tarfile','gzip','bz2','lzma','pickle','shelve','codecs',
|
||
'unicodedata','ast','dis','types','builtins','operator','numbers','cmath',
|
||
'pprint','reprlib','platform','signal','mmap','ctypes','multiprocessing',
|
||
'concurrent','select','selectors','errno','atexit','sched','calendar',
|
||
'locale','gettext','argparse','getopt','shlex','configparser','tokenize',
|
||
'runpy','pkgutil','site','sysconfig','distutils','zipimport','abc','io',
|
||
'__future__','_thread','threading','contextvars','netrc','plistlib',
|
||
'html','xml','http','urllib','email','mailbox','mimetypes','encodings',
|
||
'codecs','unicodedata','readline','rlcompleter','curses','idlelib','tkinter',
|
||
]);
|
||
|
||
// detectExternalPythonImports — возвращает список модулей, которых нет в stdlib.
|
||
function detectExternalPythonImports(code) {
|
||
var external = [];
|
||
var seen = {};
|
||
var patterns = [/^import\s+([\w]+)/gm, /^from\s+([\w]+)/gm];
|
||
for (var pi = 0; pi < patterns.length; pi++) {
|
||
var m;
|
||
while ((m = patterns[pi].exec(code)) !== null) {
|
||
var mod = m[1];
|
||
if (!PYTHON_STDLIB.has(mod) && !seen[mod]) {
|
||
seen[mod] = true;
|
||
external.push(mod);
|
||
}
|
||
}
|
||
}
|
||
return external;
|
||
}
|
||
|
||
function llmGeneratedWarning(lang) {
|
||
var text = 'Сделано LLM. Не доверяй, проверяй!';
|
||
switch (lang) {
|
||
case 'php':
|
||
return '// ' + text;
|
||
case 'nodejs':
|
||
case 'go':
|
||
return '// ' + text;
|
||
case 'ruby':
|
||
case 'python':
|
||
default:
|
||
return '# ' + text;
|
||
}
|
||
}
|
||
|
||
function addLLMWarning(code, lang) {
|
||
var body = String(code || '').replace(/^\s+/, '');
|
||
var warning = llmGeneratedWarning(lang);
|
||
if (!body) return warning;
|
||
if (body.indexOf(warning) === 0) return body;
|
||
if (lang === 'php' && body.indexOf('<?php') === 0) {
|
||
var rest = body.slice(5);
|
||
rest = rest.replace(/^\r?\n/, '');
|
||
return '<?php\n' + warning + '\n' + rest;
|
||
}
|
||
return warning + '\n' + body;
|
||
}
|
||
|
||
async function aiCheck(codeId, langId, resultId) {
|
||
var code = document.getElementById(codeId).value.trim();
|
||
var lang = (document.getElementById(langId) ? document.getElementById(langId).value : '') || 'python';
|
||
var resEl = document.getElementById(resultId);
|
||
if (!code) { resEl.style.display = 'block'; resEl.style.background = 'var(--bg-alt)'; resEl.textContent = 'Введите код для проверки.'; return; }
|
||
var btnId = codeId === 'c-code' ? 'c-ai-btn' : 'e-ai-btn';
|
||
var btn = document.getElementById(btnId);
|
||
btn.disabled = true;
|
||
btn.textContent = '⏳ Проверяю...';
|
||
resEl.style.display = 'block';
|
||
resEl.style.background = 'var(--bg-alt)';
|
||
resEl.style.color = 'var(--fg)';
|
||
resEl.textContent = 'Проверяю код линтером...';
|
||
try {
|
||
var data = await requestJSON(API_BASE + '/ai/check', 'POST', { language: lang, code: code });
|
||
resEl.style.background = data.ok ? '#1a3a1a' : '#3a1a1a';
|
||
resEl.style.color = data.ok ? '#8f8' : '#f88';
|
||
var resultText = data.result || '(пустой ответ)';
|
||
|
||
// Для Python: проверяем внешние импорты vs поле зависимостей
|
||
if (lang === 'python') {
|
||
var depsId = codeId.replace('-code', '-deps'); // cc-code→cc-deps, e-code→e-deps
|
||
var depsEl = document.getElementById(depsId);
|
||
var depsVal = depsEl ? depsEl.value.trim() : '';
|
||
var external = detectExternalPythonImports(code);
|
||
if (external.length > 0 && !depsVal) {
|
||
resultText += '\n\n⚠️ Внешние библиотеки: ' + external.join(', ') +
|
||
'\nДобавьте их в поле "Зависимости (requirements.txt)" или убедитесь что они уже есть в Python-окружении.';
|
||
if (data.ok) {
|
||
resEl.style.background = '#3a2a00';
|
||
resEl.style.color = '#ffa';
|
||
}
|
||
}
|
||
}
|
||
|
||
resEl.textContent = resultText;
|
||
} catch (e) {
|
||
resEl.style.background = '#3a2a00';
|
||
resEl.style.color = '#ffa';
|
||
resEl.textContent = 'Ошибка: ' + e.message;
|
||
} finally {
|
||
btn.disabled = false;
|
||
btn.textContent = '🔍 Проверить синтаксис линтером';
|
||
}
|
||
}
|
||
|
||
var _assistantOpen = true;
|
||
|
||
function toggleAssistant() {
|
||
_assistantOpen = !_assistantOpen;
|
||
document.getElementById('assistant-body').style.display = _assistantOpen ? 'flex' : 'none';
|
||
document.getElementById('assistant-toggle-icon').textContent = _assistantOpen ? '▲' : '▼';
|
||
}
|
||
|
||
async function askAssistant() {
|
||
var inp = document.getElementById('assistant-input');
|
||
var msgs = document.getElementById('assistant-messages');
|
||
var q = inp.value.trim();
|
||
if (!q) return;
|
||
inp.value = '';
|
||
msgs.textContent += '\n\n👤 ' + q + '\n⏳ ...';
|
||
msgs.scrollTop = msgs.scrollHeight;
|
||
try {
|
||
var r = await fetch('/console/api/ai/ask', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', ...authHeaders() },
|
||
body: JSON.stringify({ mode: 'chat', question: q })
|
||
});
|
||
var d = await r.json();
|
||
msgs.textContent = msgs.textContent.replace('⏳ ...', '🤖 ' + (d.answer || d.error || 'Нет ответа'));
|
||
} catch (e) {
|
||
msgs.textContent = msgs.textContent.replace('⏳ ...', '❌ Ошибка: ' + e.message);
|
||
}
|
||
msgs.scrollTop = msgs.scrollHeight;
|
||
}
|
||
|
||
function showGenPrompt(prefix) {
|
||
prefix = prefix || 'cc';
|
||
var el = document.getElementById(prefix + '-gen-prompt');
|
||
el.style.display = 'flex';
|
||
document.getElementById(prefix + '-gen-desc').focus();
|
||
}
|
||
|
||
async function aiGenerate(prefix) {
|
||
prefix = prefix || 'cc';
|
||
var name = (document.getElementById(prefix + '-name').value || '').trim() || 'my-func';
|
||
var lang = document.getElementById(prefix + '-lang').value || 'python';
|
||
var desc = (document.getElementById(prefix + '-gen-desc').value || '').trim();
|
||
if (!desc) { document.getElementById(prefix + '-gen-desc').focus(); return; }
|
||
var btn = document.getElementById(prefix + '-gen-btn');
|
||
var resEl = document.getElementById(prefix + '-ai-result');
|
||
btn.disabled = true; btn.textContent = '⏳ Генерирую...';
|
||
resEl.style.display = 'block'; resEl.style.background = 'var(--bg-alt)'; resEl.style.color = 'var(--fg)';
|
||
resEl.textContent = 'Запрашиваю у LLM...';
|
||
try {
|
||
var d = await requestJSON('/console/api/ai/ask', 'POST', {
|
||
mode: 'codegen',
|
||
language: lang,
|
||
name: name,
|
||
description: desc
|
||
});
|
||
var code = (d.answer || '').replace(/^```[\w]*\n?/, '').replace(/\n?```$/, '');
|
||
document.getElementById(prefix + '-code').value = addLLMWarning(code, lang);
|
||
document.getElementById(prefix + '-gen-prompt').style.display = 'none';
|
||
resEl.style.background = '#1a3a1a'; resEl.style.color = '#8f8';
|
||
resEl.textContent = 'Код сгенерирован и вставлен.';
|
||
} catch (e) {
|
||
resEl.style.background = '#3a2a00'; resEl.style.color = '#ffa';
|
||
resEl.textContent = 'Ошибка: ' + e.message;
|
||
} finally {
|
||
btn.disabled = false; btn.textContent = '✨ Сгенерировать код LLM';
|
||
}
|
||
}
|
||
|
||
async function aiExplain(codeId, langId, resultId) {
|
||
var code = (document.getElementById(codeId).value || '').trim();
|
||
var lang = (document.getElementById(langId) ? document.getElementById(langId).value : '') || 'python';
|
||
var resEl = document.getElementById(resultId);
|
||
var btnId = codeId === 'c-code' ? 'c-exp-btn' : 'e-exp-btn';
|
||
var btn = document.getElementById(btnId);
|
||
if (!code) { resEl.style.display = 'block'; resEl.style.background = 'var(--bg-alt)'; resEl.textContent = 'Введите код.'; return; }
|
||
btn.disabled = true; btn.textContent = '⏳ Объясняю...';
|
||
resEl.style.display = 'block'; resEl.style.background = 'var(--bg-alt)'; resEl.style.color = 'var(--fg)';
|
||
resEl.textContent = 'Запрашиваю у LLM...';
|
||
try {
|
||
var d = await requestJSON('/console/api/ai/ask', 'POST', {
|
||
mode: 'explain',
|
||
language: lang,
|
||
code: code
|
||
});
|
||
resEl.style.background = '#1a2a3a'; resEl.style.color = '#8cf';
|
||
resEl.textContent = d.answer || '(пустой ответ)';
|
||
} catch (e) {
|
||
resEl.style.background = '#3a2a00'; resEl.style.color = '#ffa';
|
||
resEl.textContent = 'Ошибка: ' + e.message;
|
||
} finally {
|
||
btn.disabled = false; btn.textContent = '📖 LLM: Что делает?';
|
||
}
|
||
}
|