13.08.2026 налажена работа с ИИ. Подготовка к разбивке файлов agent и db_tools
This commit is contained in:
+9
-2
@@ -173,6 +173,13 @@
|
||||
<!-- Главный контейнер -->
|
||||
<div class="flex-1 flex flex-col min-h-0 w-full max-w-4xl mx-auto bg-white relative overflow-hidden">
|
||||
<div id="chat-window" class="flex-1 p-3.5 overflow-y-auto space-y-3 bg-slate-50/50">
|
||||
<div id="drop-overlay" class="absolute inset-0 bg-indigo-600/10 backdrop-blur-sm border-2 border-dashed border-indigo-600 rounded-2xl hidden flex-col items-center justify-center z-30 transition-all pointer-events-none">
|
||||
<div class="bg-white p-4 rounded-2xl shadow-xl flex flex-col items-center gap-2">
|
||||
<i class="fa-solid fa-cloud-arrow-up text-3xl text-indigo-600 animate-bounce"></i>
|
||||
<p class="text-sm font-bold text-slate-800">Перетащите файл сюда</p>
|
||||
<p class="text-xs text-slate-500">Поддерживаются PDF, изображения, таблицы, TXT</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white border border-slate-200 rounded-2xl p-3.5 shadow-sm">
|
||||
<p class="text-[11px] font-bold text-indigo-600 uppercase tracking-wider mb-1">
|
||||
<i class="fa-solid fa-robot mr-1"></i> ИИ-Ассистент
|
||||
@@ -205,8 +212,8 @@
|
||||
|
||||
<div class="flex-1 bg-slate-100 border border-slate-300 rounded-2xl px-3 py-1.5 focus-within:border-indigo-600 focus-within:bg-white transition">
|
||||
<textarea id="user-input" rows="1" autocomplete="off" autocorrect="off" spellcheck="false"
|
||||
placeholder="Команда, вопрос или описание файла..."
|
||||
class="w-full bg-transparent text-slate-900 text-sm focus:outline-none resize-none overflow-y-auto max-h-[80px] leading-normal no-scrollbar"></textarea>
|
||||
placeholder="Команда, вопрос или перетащите файл сюда..."
|
||||
class="w-full bg-transparent text-slate-900 text-sm focus:outline-none resize-none overflow-y-auto h-[24px] max-h-[120px] leading-[24px] fade-scroll-top no-scrollbar"></textarea>
|
||||
</div>
|
||||
<button type="button" id="send-btn" onclick="sendMessage()" class="bg-indigo-600 active:bg-indigo-800 text-white font-semibold px-3.5 py-2.5 rounded-2xl text-xs sm:text-sm transition flex items-center justify-center gap-1.5 shrink-0 shadow-sm">
|
||||
<span>Отправить</span>
|
||||
|
||||
+3
-52
@@ -15,58 +15,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
if (userInputEl) {
|
||||
userInputEl.addEventListener("input", function() {
|
||||
this.style.height = "auto";
|
||||
this.style.height = Math.min(this.scrollHeight, 80) + "px";
|
||||
});
|
||||
|
||||
userInputEl.addEventListener("keydown", function(e) {
|
||||
// Отправка по Enter
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
const val = this.value.trim();
|
||||
if (val) {
|
||||
// Сохраняем команду в историю
|
||||
if (inputHistory.length === 0 || inputHistory[inputHistory.length - 1] !== val) {
|
||||
inputHistory.push(val);
|
||||
if (inputHistory.length > 50) inputHistory.shift();
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(inputHistory));
|
||||
}
|
||||
historyIndex = -1;
|
||||
}
|
||||
|
||||
if (typeof sendMessage === "function") {
|
||||
sendMessage(e);
|
||||
}
|
||||
}
|
||||
// История: стрелка ВВЕРХ
|
||||
else if (e.key === "ArrowUp") {
|
||||
if (inputHistory.length > 0 && historyIndex < inputHistory.length - 1) {
|
||||
e.preventDefault();
|
||||
if (historyIndex === -1) {
|
||||
this.dataset.draft = this.value;
|
||||
}
|
||||
historyIndex++;
|
||||
this.value = inputHistory[inputHistory.length - 1 - historyIndex];
|
||||
this.dispatchEvent(new Event("input"));
|
||||
setTimeout(() => this.setSelectionRange(this.value.length, this.value.length), 0);
|
||||
}
|
||||
}
|
||||
// История: стрелка ВНИЗ
|
||||
else if (e.key === "ArrowDown") {
|
||||
if (historyIndex !== -1) {
|
||||
e.preventDefault();
|
||||
if (historyIndex > 0) {
|
||||
historyIndex--;
|
||||
this.value = inputHistory[inputHistory.length - 1 - historyIndex];
|
||||
} else {
|
||||
historyIndex = -1;
|
||||
this.value = this.dataset.draft || "";
|
||||
}
|
||||
this.dispatchEvent(new Event("input"));
|
||||
setTimeout(() => this.setSelectionRange(this.value.length, this.value.length), 0);
|
||||
}
|
||||
}
|
||||
this.style.height = "24px";
|
||||
const newHeight = Math.min(this.scrollHeight, 120);
|
||||
this.style.height = newHeight + "px";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+124
-3
@@ -1,3 +1,11 @@
|
||||
// Вспомогательная функция для автоматического изменения высоты текстового поля (1-3 строки)
|
||||
function updateInputHeight(el) {
|
||||
if (!el) return;
|
||||
el.style.height = "24px";
|
||||
const newHeight = Math.min(el.scrollHeight, 120);
|
||||
el.style.height = newHeight + "px";
|
||||
}
|
||||
|
||||
let selectedFile = null;
|
||||
|
||||
function handleFileSelect(e) {
|
||||
@@ -57,7 +65,7 @@ async function sendMessage(e) {
|
||||
chatWindow.insertAdjacentHTML("beforeend", userMsgHtml);
|
||||
|
||||
input.value = "";
|
||||
input.style.height = "auto";
|
||||
updateInputHeight(input);
|
||||
chatWindow.scrollTop = chatWindow.scrollHeight;
|
||||
|
||||
if (sendBtn) {
|
||||
@@ -70,7 +78,6 @@ async function sendMessage(e) {
|
||||
|
||||
const endpoint = isGuest ? "/api/v1/chat/guest" : "/api/v1/chat";
|
||||
|
||||
// Формируем единый FormData без дублирования
|
||||
const formData = new FormData();
|
||||
formData.append("session_id", "web_session_main");
|
||||
formData.append("message", text || "Проанализируй прикрепленный файл");
|
||||
@@ -142,4 +149,118 @@ function escapeHtml(text) {
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const input = document.getElementById("user-input");
|
||||
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();
|
||||
if (text) {
|
||||
if (localHistory.length === 0 || localHistory[0] !== text) {
|
||||
localHistory.unshift(text);
|
||||
if (localHistory.length > 50) localHistory.pop();
|
||||
localStorage.setItem("scud_chat_input_history", JSON.stringify(localHistory));
|
||||
}
|
||||
historyIndex = -1;
|
||||
}
|
||||
sendMessage(e);
|
||||
updateInputHeight(input);
|
||||
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();
|
||||
if (historyIndex === -1) {
|
||||
input.dataset.draft = input.value;
|
||||
}
|
||||
historyIndex++;
|
||||
input.value = localHistory[historyIndex];
|
||||
updateInputHeight(input);
|
||||
input.setSelectionRange(input.value.length, input.value.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Стрелка ВНИЗ
|
||||
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) {
|
||||
historyIndex--;
|
||||
input.value = localHistory[historyIndex];
|
||||
} else {
|
||||
historyIndex = -1;
|
||||
input.value = input.dataset.draft || "";
|
||||
}
|
||||
updateInputHeight(input);
|
||||
input.setSelectionRange(input.value.length, input.value.length);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- 2. ОБРАБОТКА DRAG-AND-DROP ФАЙЛОВ ---
|
||||
if (dropZone && dropOverlay) {
|
||||
["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}, false);
|
||||
});
|
||||
|
||||
["dragenter", "dragover"].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, () => {
|
||||
dropOverlay.classList.remove("hidden");
|
||||
dropOverlay.classList.add("flex");
|
||||
}, false);
|
||||
});
|
||||
|
||||
["dragleave", "drop"].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, (e) => {
|
||||
if (eventName === "drop" || e.target === dropZone || !dropZone.contains(e.relatedTarget)) {
|
||||
dropOverlay.classList.add("hidden");
|
||||
dropOverlay.classList.remove("flex");
|
||||
}
|
||||
}, false);
|
||||
});
|
||||
|
||||
dropZone.addEventListener("drop", (e) => {
|
||||
const dt = e.dataTransfer;
|
||||
const files = dt.files;
|
||||
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
handleFileSelect({ target: { files: [file] } });
|
||||
|
||||
const fileInput = document.getElementById("file-input");
|
||||
if (fileInput) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
fileInput.files = dataTransfer.files;
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user