13.08.2026 20:25 перед внедрением коррекции поведения ИИ при использовании инструментов (создание флагов мусорных сообщений для очистки)

This commit is contained in:
2026-08-13 20:25:27 +03:00
parent fc1a608a5f
commit f481fffdb7
18 changed files with 1562 additions and 1254 deletions
+36
View File
@@ -18,3 +18,39 @@ body {
min-height: 100vh;
min-height: -webkit-fill-available;
}
/* Плавное исчезновение текста сверху при скролле */
.fade-scroll-top {
mask-image: linear-gradient(to bottom, transparent 0%, black 14px);
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 14px);
}
/* Скрытие стандартного скроллбара */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* Оптимизация под мобильный viewport */
body {
min-height: 100vh;
min-height: -webkit-fill-available;
}
/* Анимация появления плашки с кнопками подтверждения */
.confirm-action-bar {
animation: slideUp 0.25s ease-out forwards;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
+33 -8
View File
@@ -1,4 +1,4 @@
// Вспомогательная функция для автоматического изменения высоты текстового поля (1-3 строки)
// Вспомогательная функция для автоматического изменения высоты текстового поля
function updateInputHeight(el) {
if (!el) return;
el.style.height = "24px";
@@ -36,6 +36,15 @@ function clearAttachedFile() {
if (previewContainer) previewContainer.classList.add("hidden");
}
// Функция симуляции отправки системной команды по кнопке
function sendQuickAction(actionText) {
const input = document.getElementById("user-input");
if (input) {
input.value = actionText;
sendMessage();
}
}
async function sendMessage(e) {
if (e && e.preventDefault) e.preventDefault();
@@ -48,6 +57,9 @@ async function sendMessage(e) {
if (!text && !selectedFile) return;
// Удаляем старые активные плашки кнопок, чтобы не висели предыдущие подтверждения
document.querySelectorAll('.confirm-action-bar').forEach(el => el.remove());
let userDisplayHtml = escapeHtml(text);
if (selectedFile) {
userDisplayHtml = `<div class="font-bold border-b border-indigo-400/40 pb-1 mb-1 text-[11px] flex items-center gap-1.5">
@@ -107,12 +119,32 @@ async function sendMessage(e) {
const assistantTitle = isGuest ? "Локальная нейросеть (Гость)" : "ИИ-Ассистент SCUD Orion AI";
const replyText = data.reply || "Пустой ответ от нейросети";
// Кнопки генерируем СТРОГО если бэкенд вернул флаг data.needs_confirmation === true
let buttonsHtml = "";
if (data.needs_confirmation === true) {
buttonsHtml = `
<div class="confirm-action-bar mt-3 pt-2.5 border-t border-slate-200 flex items-center justify-end gap-2">
<button type="button" onclick="sendQuickAction('отмена')"
class="bg-slate-100 hover:bg-slate-200 active:bg-slate-300 text-slate-700 font-semibold px-3 py-1.5 rounded-xl text-xs transition border border-slate-300 flex items-center gap-1.5 shadow-sm">
<i class="fa-solid fa-xmark text-red-500"></i>
<span>Отменить</span>
</button>
<button type="button" onclick="sendQuickAction('подтверждаю')"
class="bg-emerald-600 hover:bg-emerald-700 active:bg-emerald-800 text-white font-semibold px-3.5 py-1.5 rounded-xl text-xs transition flex items-center gap-1.5 shadow-md">
<i class="fa-solid fa-check"></i>
<span>Подтвердить</span>
</button>
</div>
`;
}
const botMsgHtml = `
<div class="bg-white border border-slate-200 rounded-2xl p-3.5 shadow-sm max-w-2xl mb-3">
<p class="text-[11px] font-bold text-indigo-600 uppercase tracking-wider mb-1">
<i class="fa-solid fa-robot mr-1"></i> ${assistantTitle}
</p>
<p class="text-slate-800 text-xs sm:text-sm whitespace-pre-wrap leading-relaxed">${escapeHtml(replyText)}</p>
${buttonsHtml}
</div>
`;
chatWindow.insertAdjacentHTML("beforeend", botMsgHtml);
@@ -156,13 +188,11 @@ document.addEventListener("DOMContentLoaded", () => {
const dropZone = document.getElementById("chat-window")?.parentElement;
const dropOverlay = document.getElementById("drop-overlay");
// --- 1. УМНАЯ НАВИГАЦИЯ СТРЕЛКАМИ В МНОГОСТРОЧНОМ ТЕКСТЕ ---
if (input) {
let historyIndex = -1;
let localHistory = JSON.parse(localStorage.getItem("scud_chat_input_history") || "[]");
input.addEventListener("keydown", (e) => {
// Отправка по Enter без Shift
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
const text = input.value.trim();
@@ -179,12 +209,10 @@ document.addEventListener("DOMContentLoaded", () => {
return;
}
// Стрелка ВВЕРХ
if (e.key === "ArrowUp") {
const textBeforeCursor = input.value.substring(0, input.selectionStart);
const isFirstLine = !textBeforeCursor.includes("\n");
// Переключаем историю ТОЛЬКО когда курсор на 1-й строке И уперся в самое начало (позиция 0)
if (isFirstLine && input.selectionStart === 0 && localHistory.length > 0) {
if (historyIndex < localHistory.length - 1) {
e.preventDefault();
@@ -199,12 +227,10 @@ document.addEventListener("DOMContentLoaded", () => {
}
}
// Стрелка ВНИЗ
if (e.key === "ArrowDown") {
const textAfterCursor = input.value.substring(input.selectionEnd);
const isLastLine = !textAfterCursor.includes("\n");
// Переключаем историю ТОЛЬКО когда курсор на последней строке И уперся в самый конец
if (isLastLine && input.selectionEnd === input.value.length && historyIndex >= 0) {
e.preventDefault();
if (historyIndex > 0) {
@@ -221,7 +247,6 @@ document.addEventListener("DOMContentLoaded", () => {
});
}
// --- 2. ОБРАБОТКА DRAG-AND-DROP ФАЙЛОВ ---
if (dropZone && dropOverlay) {
["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => {
dropZone.addEventListener(eventName, (e) => {