Добавлена поддержка стриминга сообщений через yield токены. 'Деактивирован' метод generate_stream в ollamaapi

This commit is contained in:
2026-05-12 22:51:42 +03:00
parent 30ce868764
commit 1504c5fee7
6 changed files with 158 additions and 91 deletions
Binary file not shown.
+10 -10
View File
@@ -1,4 +1,4 @@
from flask import Flask, render_template, request, jsonify
from flask import Flask, Response, render_template, request, jsonify
from core.agent.agent import Agent
from core.llm.ollamaapi import OllamaProvider
from core.character.character import Character
@@ -39,18 +39,18 @@ def init():
#@ui.route("/stream", methods=["POST"])
def chat():
data = request.json
global last_prompt
last_prompt = request.json["message"]
return jsonify({"status": "ok"})
user_message = data["message"]
response = agent.respond(user_message)
@ui.route("/stream")
def stream():
def generate():
for content in agent.stream_responce(last_prompt):
yield f"data: {content}\n\n"
agent.save_memory()
return jsonify({
"response": response
})
return Response(generate(), mimetype="text/event-stream")
#список персонажей
@ui.route("/characters", methods=["GET"])
+142 -21
View File
@@ -1,15 +1,47 @@
const messages = document.getElementById("messages");
const input = document.getElementById("user-input");
const sendBtn = document.getElementById("send-btn");
loadChat();
sendBtn.addEventListener("click", async () => {
const input = document.getElementById("user-input");
const text = input.value;
let userName = "Alex";
const messages = document.getElementById("messages");
// текущий SSE поток (ВАЖНО: чтобы не плодить соединения)
let eventSource = null;
messages.innerHTML += `<p><b>You:</b> ${text}</p>`; //=> получаем имя пользователя
const response = await fetch("/chat", {
// ===============================
// 1. ЗАГРУЗКА ИСТОРИИ
// ===============================
async function loadHistory() {
const res = await fetch("/init");
const data = await res.json();
userName = data.user_name || "User";
data.messages.forEach(msg => {
renderMessage(msg.role, msg.content);
});
}
// ===============================
// 2. ОТПРАВКА СООБЩЕНИЯ
// ===============================
async function sendMessage() {
const text = input.value.trim();
if (!text) return;
renderMessage("user", text);
input.value = "";
// закрываем старый stream если есть
if (eventSource) {
eventSource.close();
eventSource = null;
}
// отправляем сообщение на backend (он сохраняет state)
await fetch("/chat", {
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -19,21 +51,110 @@ sendBtn.addEventListener("click", async () => {
})
});
const data = await response.json();
// создаём ПУСТОЕ сообщение для стрима
const aiMessageDiv = createAIMessagePlaceholder();
messages.innerHTML += `<p><b>AI:</b> ${data.response}</p>`;
// запускаем stream
startStream(aiMessageDiv);
}
input.value = "";
// ===============================
// 3. SSE STREAM
// ===============================
function startStream(aiMessageDiv) {
eventSource = new EventSource("/stream");
let fullText = "";
eventSource.onmessage = function(event) {
const token = event.data;
// иногда Ollama может слать [DONE]
if (token === "[DONE]") {
eventSource.close();
eventSource = null;
return;
}
fullText += token;
aiMessageDiv.innerHTML = `<b>AI:</b> ${formatText(fullText)}`;
messages.scrollTop = messages.scrollHeight;
};
eventSource.onerror = function(err) {
console.error("Stream error:", err);
eventSource.close();
eventSource = null;
};
}
// ===============================
// 4. РЕНДЕР СООБЩЕНИЙ
// ===============================
function renderMessage(role, text) {
const div = document.createElement("div");
if (role === "user") {
div.className = "user-msg";
div.innerHTML = `<b>${userName}:</b> ${text}`;
}
if (role === "assistant") {
div.className = "ai-msg";
div.innerHTML = `<b>AI:</b> ${formatText(text)}`;
}
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
}
// ===============================
// 5. ПУСТОЙ КОНТЕЙНЕР ДЛЯ STREAM
// ===============================
function createAIMessagePlaceholder() {
const div = document.createElement("div");
div.className = "ai-msg";
div.innerHTML = `<b>AI:</b> `;
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
return div;
}
// ===============================
// 6. ФОРМАТИРОВАНИЕ (*actions*)
// ===============================
function formatText(text) {
// действия *...*
return text.replace(/\*(.*?)\*/g, '<span class="action">*$1*</span>');
}
// ===============================
// 7. EVENTS
// ===============================
sendBtn.addEventListener("click", sendMessage);
input.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
sendMessage();
}
});
async function loadChat() {
const res = await fetch("/init");
const data = await res.json();
const messages = document.getElementById("messages");
data.messages.forEach(msg => {
messages.innerHTML += `<p><b>${msg.role}:</b> ${msg.content}</p>`; //добавить проверку role == user => получаем имя пользователя
});
}
// ===============================
// 8. INIT
// ===============================
loadHistory();