restructure: console→client-console, add admin-console skeleton, move docs to doc/

This commit is contained in:
“Naeel”
2026-05-25 09:48:55 +04:00
parent 3e36ff13a8
commit 34a8068da5
110 changed files with 615 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
/* 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: Что делает?';
}
}