161 lines
3.8 KiB
JavaScript
161 lines
3.8 KiB
JavaScript
import { createChat, sendUserMessage, openStream, fetchModels } from "./chatApi.svelte.js";
|
|
|
|
const STORAGE_KEY = "chatHistory";
|
|
|
|
function loadHistory() {
|
|
try {
|
|
return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function saveHistory(list) {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
|
|
}
|
|
|
|
export const chatStore = (() => {
|
|
let chatId = $state(null);
|
|
let messages = $state([]);
|
|
let loading = $state(false);
|
|
let input = $state("");
|
|
let model = $state("qwen/qwen3-235b-a22b-2507"); // default
|
|
let models = $state([]);
|
|
let loadingModels = $state(true);
|
|
|
|
// public helpers
|
|
const history = $derived(loadHistory());
|
|
|
|
function pushHistory(id, title, msgs) {
|
|
console.log(`push history: ${id} - ${title}`);
|
|
const h = history.filter((c) => c.id !== id);
|
|
h.unshift({ id, title, messages: msgs });
|
|
saveHistory(h.slice(0, 50)); // keep last 50
|
|
}
|
|
|
|
async function selectChat(id) {
|
|
if (id === chatId) return;
|
|
chatId = id;
|
|
const stored = loadHistory().find((c) => c.id === id);
|
|
messages = stored?.messages || [];
|
|
loading = true;
|
|
loading = false;
|
|
// Update URL with GET parameter
|
|
const url = new URL(window.location.href);
|
|
if (id) {
|
|
url.searchParams.set('chat', id);
|
|
} else {
|
|
url.searchParams.delete('chat');
|
|
}
|
|
window.history.replaceState({}, "", url);
|
|
}
|
|
|
|
async function createAndSelect() {
|
|
const { id } = await createChat(model);
|
|
console.log(id);
|
|
selectChat(id);
|
|
return id;
|
|
}
|
|
|
|
async function send() {
|
|
if (!input.trim()) return;
|
|
if (!chatId) await createAndSelect();
|
|
|
|
const userMsg = { id: crypto.randomUUID(), role: "user", text: input };
|
|
messages = [...messages, userMsg];
|
|
|
|
pushHistory(chatId, userMsg.text.slice(0, 30), messages);
|
|
|
|
loading = true;
|
|
const { message_id } = await sendUserMessage(chatId, input, model);
|
|
input = "";
|
|
|
|
let assistantMsg = { id: message_id, role: "assistant", text: "" };
|
|
messages = [...messages, assistantMsg];
|
|
|
|
const es = openStream(chatId, message_id);
|
|
es.onmessage = (e) => {
|
|
assistantMsg = { ...assistantMsg, text: assistantMsg.text + e.data };
|
|
messages = [...messages.slice(0, -1), assistantMsg];
|
|
};
|
|
es.onerror = () => {
|
|
es.close();
|
|
loading = false;
|
|
};
|
|
es.addEventListener("done", (e) => {
|
|
console.log(e);
|
|
es.close();
|
|
loading = false;
|
|
pushHistory(chatId, userMsg.text.slice(0, 30), messages);
|
|
});
|
|
}
|
|
|
|
async function loadModels() {
|
|
loadingModels = true;
|
|
models = await fetchModels();
|
|
loadingModels = false;
|
|
|
|
// Set default model if available and not already set
|
|
if (models.length > 0 && !model) {
|
|
model = models[0].id || models[0];
|
|
}
|
|
}
|
|
|
|
function handleKey(e) {
|
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
e.preventDefault();
|
|
send();
|
|
}
|
|
}
|
|
|
|
// initial route handling - use GET parameter instead of path
|
|
const params = new URLSearchParams(window.location.search);
|
|
const chatIdFromUrl = params.get('chat');
|
|
const storedHistory = loadHistory();
|
|
if (chatIdFromUrl && !storedHistory.find((c) => c.id === chatIdFromUrl)) {
|
|
createAndSelect();
|
|
} else if (chatIdFromUrl) {
|
|
selectChat(chatIdFromUrl);
|
|
}
|
|
|
|
// Load models on initialization
|
|
loadModels();
|
|
|
|
return {
|
|
get chatId() {
|
|
return chatId;
|
|
},
|
|
get messages() {
|
|
return messages;
|
|
},
|
|
get loading() {
|
|
return loading;
|
|
},
|
|
get input() {
|
|
return input;
|
|
},
|
|
set input(v) {
|
|
input = v;
|
|
},
|
|
get model() {
|
|
return model;
|
|
},
|
|
set model(v) {
|
|
model = v;
|
|
},
|
|
get models() {
|
|
return models;
|
|
},
|
|
get loadingModels() {
|
|
return loadingModels;
|
|
},
|
|
get history() {
|
|
return loadHistory();
|
|
},
|
|
selectChat,
|
|
send,
|
|
handleKey,
|
|
createAndSelect,
|
|
loadModels,
|
|
};
|
|
})();
|