refactor(ui): step9 - extract ai.js (llmGeneratedWarning, addLLMWarning, aiCheck, aiGenerate, aiExplain, askAssistant, toggleAssistant, showGenPrompt)
This commit is contained in:
+1
-216
@@ -20,6 +20,7 @@
|
|||||||
<script src="js/triggers.js"></script>
|
<script src="js/triggers.js"></script>
|
||||||
<script src="js/modals.js"></script>
|
<script src="js/modals.js"></script>
|
||||||
<script src="js/functions.js"></script>
|
<script src="js/functions.js"></script>
|
||||||
|
<script src="js/ai.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -416,131 +417,6 @@
|
|||||||
currentInvoke: null
|
currentInvoke: null
|
||||||
};
|
};
|
||||||
|
|
||||||
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 reloadAll() {
|
|
||||||
try {
|
|
||||||
const [envs, pkgs, fns, http, times] = await Promise.all([
|
|
||||||
getJSON(API_BASE + '/environments'),
|
|
||||||
getJSON(API_BASE + '/packages'),
|
|
||||||
getJSON(API_BASE + '/functions'),
|
|
||||||
getJSON(API_BASE + '/httptriggers'),
|
|
||||||
getJSON(API_BASE + '/timetriggers')
|
|
||||||
]);
|
|
||||||
|
|
||||||
S.envs = envs || [];
|
|
||||||
S.fns = fns || [];
|
|
||||||
S.httpTriggers = http || [];
|
|
||||||
S.timeTriggers = times || [];
|
|
||||||
|
|
||||||
setText('env-count', envs.length || 0);
|
|
||||||
setText('pkg-count', pkgs.length || 0);
|
|
||||||
setText('fn-count', fns.length || 0);
|
|
||||||
setText('http-count', http.length || 0);
|
|
||||||
setText('cron-count', new Set((times || []).map(function (t) {
|
|
||||||
return t && t.spec && t.spec.functionref && t.spec.functionref.name;
|
|
||||||
}).filter(Boolean)).size);
|
|
||||||
|
|
||||||
const rows = (fns || []).map(function (f) {
|
|
||||||
const spec = f.spec || {};
|
|
||||||
const meta = f.metadata || {};
|
|
||||||
const ann = meta.annotations || {};
|
|
||||||
const env = (spec.environment && spec.environment.name) || '-';
|
|
||||||
const pkg = (spec.package && spec.package.packageref && spec.package.packageref.name) || '-';
|
|
||||||
const name = (f.metadata && f.metadata.name) || '-';
|
|
||||||
const trig = httpTriggerByFn(name) || {};
|
|
||||||
const timeTrig = timeTriggerByFn(name) || {};
|
|
||||||
const route = (trig.spec && trig.spec.relativeurl) || '-';
|
|
||||||
const methods = (trig.spec && trig.spec.methods) || [];
|
|
||||||
const chips = methods.map(function (m) { return '<span class="chip">' + h(m) + '</span>'; }).join('');
|
|
||||||
const cron = (timeTrig.spec && timeTrig.spec.cron) || '';
|
|
||||||
const cronCell = cron ? '<span class="chip">' + h(cron) + '</span>' : '<span class="mono" style="color:var(--text-secondary)">—</span>';
|
|
||||||
const createdAt = ann['fission-console/created-at'] || meta.creationTimestamp || '-';
|
|
||||||
const updatedAt = ann['fission-console/updated-at'] || createdAt;
|
|
||||||
var isGo = /go[-_]env/.test(env);
|
|
||||||
var isTf = /^tf-/.test(name);
|
|
||||||
var tfBadge = (isGo || isTf) ? '<span class="chip" style="background:#555;color:#ffa" title="Управляется Terraform. Изменения могут быть перезаписаны при terraform apply.">TF</span> ' : '';
|
|
||||||
var actions = tfBadge +
|
|
||||||
'<button class="btn ghost" onclick="openEdit(\'' + h(name) + '\')">\u0420\u0435\u0434.</button> ' +
|
|
||||||
'<button class="btn ghost" onclick="openInvoke(\'' + h(name) + '\')">\u0412\u044b\u0437\u043e\u0432</button> ' +
|
|
||||||
'<button class="btn danger" onclick="removeFn(\'' + h(name) + '\')">\u0423\u0434\u0430\u043b\u0438\u0442\u044c</button>';
|
|
||||||
return '<tr>' +
|
|
||||||
'<td class="mono">' + h(name) + '</td>' +
|
|
||||||
'<td>' + h(env) + '</td>' +
|
|
||||||
'<td class="mono">' + h(pkg) + '</td>' +
|
|
||||||
'<td>' + timestampCell(createdAt) + '</td>' +
|
|
||||||
'<td>' + timestampCell(updatedAt) + '</td>' +
|
|
||||||
'<td class="mono">' + h(route) + '</td>' +
|
|
||||||
'<td>' + chips + '</td>' +
|
|
||||||
'<td>' + cronCell + '</td>' +
|
|
||||||
'<td class="nowrap">' + actions + '</td>' +
|
|
||||||
'</tr>';
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
document.getElementById('fn-rows').innerHTML = rows || '<tr><td colspan="9">\u041d\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0439</td></tr>';
|
|
||||||
if (!rows) {
|
|
||||||
document.getElementById('fn-rows').innerHTML = '<tr><td colspan="9">\u041d\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0439</td></tr>';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
document.getElementById('fn-rows').innerHTML = '<tr><td colspan="9">Load error: ' + e.message + '</td></tr>';
|
|
||||||
showStatus('\u041e\u0448\u0438\u0431\u043a\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438: ' + e.message, 'err');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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';
|
|
||||||
resEl.textContent = data.result || '(пустой ответ)';
|
|
||||||
} catch (e) {
|
|
||||||
resEl.style.background = '#3a2a00';
|
|
||||||
resEl.style.color = '#ffa';
|
|
||||||
resEl.textContent = 'Ошибка: ' + e.message;
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = '🔍 Проверить синтаксис линтером';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _initPolling = false;
|
var _initPolling = false;
|
||||||
var _initTickBusy = false;
|
var _initTickBusy = false;
|
||||||
var _initStartedAt = 0;
|
var _initStartedAt = 0;
|
||||||
@@ -758,97 +634,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
|
||||||
// ai/ask feature
|
|
||||||
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() {
|
|
||||||
var el = document.getElementById('c-gen-prompt');
|
|
||||||
el.style.display = 'flex';
|
|
||||||
document.getElementById('c-gen-desc').focus();
|
|
||||||
}
|
|
||||||
async function aiGenerate() {
|
|
||||||
var name = (document.getElementById('c-name').value || '').trim() || 'my-func';
|
|
||||||
var lang = document.getElementById('c-lang').value || 'python';
|
|
||||||
var desc = (document.getElementById('c-gen-desc').value || '').trim();
|
|
||||||
if (!desc) { document.getElementById('c-gen-desc').focus(); return; }
|
|
||||||
var btn = document.getElementById('c-gen-btn');
|
|
||||||
var resEl = document.getElementById('c-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('c-code').value = addLLMWarning(code, lang);
|
|
||||||
document.getElementById('c-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: Что делает?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// end ai/ask feature
|
|
||||||
</script>
|
|
||||||
<!-- end ai/ask feature -->
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
/* ai.js — AI/LLM функции: проверка, генерация, объяснение, ассистент */
|
||||||
|
|
||||||
|
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';
|
||||||
|
resEl.textContent = data.result || '(пустой ответ)';
|
||||||
|
} 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() {
|
||||||
|
var el = document.getElementById('c-gen-prompt');
|
||||||
|
el.style.display = 'flex';
|
||||||
|
document.getElementById('c-gen-desc').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function aiGenerate() {
|
||||||
|
var name = (document.getElementById('c-name').value || '').trim() || 'my-func';
|
||||||
|
var lang = document.getElementById('c-lang').value || 'python';
|
||||||
|
var desc = (document.getElementById('c-gen-desc').value || '').trim();
|
||||||
|
if (!desc) { document.getElementById('c-gen-desc').focus(); return; }
|
||||||
|
var btn = document.getElementById('c-gen-btn');
|
||||||
|
var resEl = document.getElementById('c-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('c-code').value = addLLMWarning(code, lang);
|
||||||
|
document.getElementById('c-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: Что делает?';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user