Files
KrisHD_AI/server.js
T

177 lines
5.1 KiB
JavaScript

import express from "express";
import session from "express-session";
import bcrypt from "bcrypt";
import fs from "fs";
import path from "path";
import bodyParser from "body-parser";
const app = express();
const PORT = 3000;
const DATA_PATH = "./data/users.json";
const CHAT_PATH = "./data/chats.json";
import FileStoreFactory from "session-file-store";
const FileStore = FileStoreFactory(session);
app.use(bodyParser.json());
app.use(express.static("public"));
app.use(
session({
store: new FileStore({path: "./sessions"}),
secret: "-secret-key-here-",
resave: false,
saveUninitialized: false,
cookie: {
secure: false,
httpOnly: true,
maxAge: 7 * 24 * 60 * 60 * 1000
}
})
);
let chats = JSON.parse(fs.readFileSync(CHAT_PATH));
const saveChats = () => fs.writeFileSync(CHAT_PATH, JSON.stringify(chats, null, 2));
if (!fs.existsSync(DATA_PATH)) fs.writeFileSync(DATA_PATH, "{}");
const users = JSON.parse(fs.readFileSync(DATA_PATH));
const saveUsers = () => fs.writeFileSync(DATA_PATH, JSON.stringify(users, null, 2));
app.post("/register", async (req, res) => {
const {username, password} = req.body;
if (users[username]) return res.status(400).json({error: "User already exists"});
const hashed = await bcrypt.hash(password, 10);
users[username] = {password: hashed};
saveUsers();
res.json({message: "Registration successful"});
});
app.post("/login", async (req, res) => {
const {username, password} = req.body;
const user = users[username];
if (!user) return res.status(400).json({error: "User not found"});
const valid = await bcrypt.compare(password, user.password);
if (!valid) return res.status(403).json({error: "Invalid password"});
req.session.user = username;
res.json({message: "Login successful"});
});
app.post("/logout", (req, res) => {
req.session.destroy(() => res.json({message: "Logged out"}));
});
const requireLogin = (req, res, next) => {
if (!req.session.user) return res.status(403).json({error: "Not logged in"});
next();
};
app.get("/models", async (req, res) => {
const response = await fetch("https://ai.krishd.ch/api/tags");
res.json(await response.json());
})
app.post("/chat", requireLogin, async (req, res) => {
const user = req.session.user;
const { message, model } = req.body;
const selectedModel = model || "mistral:latest";
try {
if (!chats[user]) chats[user] = [];
chats[user].push({ role: "user", content: message });
if (chats[user].length > 20) chats[user] = chats[user].slice(-20);
const messagesPayload = chats[user].map(m => ({
role: m.role === "bot" ? "assistant" : "user",
content: m.content
}));
const response = await fetch("https://ai.krishd.ch/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: selectedModel,
messages: messagesPayload,
stream: true
})
});
if (!response.ok) {
throw new Error(`AI HTTP ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let fullReply = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let lines = buffer.split("\n");
buffer = lines.pop(); // keep incomplete JSON
for (const line of lines) {
if (!line.trim()) continue;
const json = JSON.parse(line);
if (json.done) {
break;
}
if (json.message?.content) {
fullReply += json.message.content;
}
}
}
chats[user].push({ role: "bot", content: fullReply });
saveChats();
console.log("Reply length:", fullReply.length);
res.json({ reply: fullReply });
} catch (err) {
console.error("AI chat error:", err);
res.status(500).json({ error: "AI request failed" });
}
});
async function checkSession() {
const res = await get('/session');
if (res.user) {
authDiv.style.display = "none";
chatDiv.style.display = "grid";
loadChatHistory();
msgInput.focus();
return true;
}
return false;
}
app.get("/session", (req, res) => {
if (req.session.user) res.json({user: req.session.user});
else res.json({user: null});
});
app.get("/chat/history", requireLogin, (req, res) => {
const user = req.session.user;
res.json({history: chats[user] || []});
});
app.post("/chat/clear", requireLogin, (req, res) => {
const user = req.session.user;
chats[user] = [];
saveChats();
res.json({message: "Chat cleared"});
});
app.get("/", (req, res) => {
res.sendFile(path.resolve("public/index.html"));
});
app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));