moved js files in public into js and css into css folder

This commit is contained in:
2026-09-22 21:55:02 +02:00
parent 99a3277e64
commit 08f5f3f8d9
40 changed files with 1772 additions and 1555 deletions
+52 -46
View File
@@ -1,11 +1,11 @@
import test from "node:test";
import assert from "node:assert/strict";
import {createServer} from "node:http";
import {createApp} from "../src/app.js";
import {createModelService} from "../src/services/modelService.js";
import {stream} from "../public/app/api.js";
import {createReasoningToggle, finishReasoning, updateReasoning} from "../public/app/reasoning.js";
import {getModelFamily, supportsReasoning} from "../public/app/modelCapabilities.js";
import { createServer } from "node:http";
import { createApp } from "../src/app.js";
import { createModelService } from "../src/services/modelService.js";
import { stream } from "../public/js/api.js";
import { createReasoningToggle, finishReasoning, updateReasoning } from "../public/js/reasoning.js";
import { getModelFamily, supportsReasoning } from "../public/js/modelCapabilities.js";
test("thinking streams separately and survives reload, retry, branch and import", async (t) => {
const requests = [];
@@ -13,30 +13,36 @@ test("thinking streams separately and survives reload, retry, branch and import"
const searchServer = createServer((_req, res) => {
searches++;
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({results: [{title: "Latest news", url: "https://example.org/news", content: "Current news: researchers announced a successful satellite launch."}]}));
res.end(JSON.stringify({ results: [{ title: "Latest news", url: "https://example.org/news", content: "Current news: researchers announced a successful satellite launch." }] }));
});
await new Promise(resolve => searchServer.listen(0, "127.0.0.1", resolve));
t.after(() => new Promise(resolve => { searchServer.closeAllConnections(); searchServer.close(resolve); }));
const modelService = createModelService({
modelApiBaseUrl: "http://fixture", modelRequestTimeoutMs: 1000, modelUnloadAfterMs: 60000
}, {fetch: async (_url, options) => {
requests.push(JSON.parse(options.body));
const chunks = [
'{"message":{"thinking":"Consider "}}\n{"message":{"thi',
'nking":"carefully.","content":""}}\n',
'{"message":{"content":"Answer"},"done":true}'
];
return new Response(new ReadableStream({start(controller) {
for (const chunk of chunks) controller.enqueue(new TextEncoder().encode(chunk));
controller.close();
}}));
}});
const app = createApp({modelService, config: {
dbPath: ":memory:", testMode: true, sessionSecret: "test-only",
searxngUrl: `http://127.0.0.1:${searchServer.address().port}`,
chatSummaryUpdateEveryMessages: 0, eventLoopLagMonitorEnabled: false,
structuredRequestLoggingEnabled: false, slowRequestLoggingEnabled: false
}});
}, {
fetch: async (_url, options) => {
requests.push(JSON.parse(options.body));
const chunks = [
'{"message":{"thinking":"Consider "}}\n{"message":{"thi',
'nking":"carefully.","content":""}}\n',
'{"message":{"content":"Answer"},"done":true}'
];
return new Response(new ReadableStream({
start(controller) {
for (const chunk of chunks) controller.enqueue(new TextEncoder().encode(chunk));
controller.close();
}
}));
}
});
const app = createApp({
modelService, config: {
dbPath: ":memory:", testMode: true, sessionSecret: "test-only",
searxngUrl: `http://127.0.0.1:${searchServer.address().port}`,
chatSummaryUpdateEveryMessages: 0, eventLoopLagMonitorEnabled: false,
structuredRequestLoggingEnabled: false, slowRequestLoggingEnabled: false
}
});
const server = createServer(app);
await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
t.after(async () => {
@@ -48,18 +54,18 @@ test("thinking streams separately and survives reload, retry, branch and import"
const api = async (path, body) => {
const response = await fetch(base + path, {
method: body ? "POST" : "GET",
headers: {"Content-Type": "application/json", ...(cookie ? {Cookie: cookie} : {})},
headers: { "Content-Type": "application/json", ...(cookie ? { Cookie: cookie } : {}) },
body: body ? JSON.stringify(body) : undefined
});
if (response.headers.get("set-cookie")) cookie = response.headers.get("set-cookie").split(";")[0];
assert.equal(response.status, 200);
return response.json();
};
await api("/register", {username: "thinker", password: "test-password"});
const chatId = (await api("/chats", {title: "Reasoning"})).chat.id;
await api("/register", { username: "thinker", password: "test-password" });
const chatId = (await api("/chats", { title: "Reasoning" })).chat.id;
const response = await fetch(base + "/chat/stream", {
method: "POST", headers: {"Content-Type": "application/json", Cookie: cookie},
body: JSON.stringify({chatId, message: "Hello", reasoning: true})
method: "POST", headers: { "Content-Type": "application/json", Cookie: cookie },
body: JSON.stringify({ chatId, message: "Hello", reasoning: true })
});
const wire = await response.text();
const events = [];
@@ -78,14 +84,14 @@ test("thinking streams separately and survives reload, retry, branch and import"
const retry = await api(`/chats/${chatId}/messages/${bot.id}/retry`, {});
assert.equal(retry.message.thinking, undefined);
await api(`/chats/${chatId}/messages/${bot.id}/versions/0`, {});
const branch = await api(`/chats/${chatId}/messages/${bot.id}/branch`, {confirmed: true});
const branch = await api(`/chats/${chatId}/messages/${bot.id}/branch`, { confirmed: true });
assert.equal(branch.messages.at(-1).thinking, bot.thinking);
const exported = await api(`/exports/chats/${chatId}?format=json`);
assert.equal(exported.messages.find(message => message.id === bot.id).thinking, bot.thinking);
const {workspace} = await api("/exports/workspace");
await api("/imports/workspace", {workspace, allowDuplicates: true});
const { workspace } = await api("/exports/workspace");
await api("/imports/workspace", { workspace, allowDuplicates: true });
assert.ok(app.locals.db.prepare("SELECT id FROM chat_messages WHERE thinking = ?").all(bot.thinking).length >= 4);
await api("/chat", {chatId, message: "Continue"});
await api("/chat", { chatId, message: "Continue" });
for (const request of requests) {
assert.ok(request.messages.every(message => !message.content.includes("Consider carefully.")));
assert.equal(request.stream, true);
@@ -95,8 +101,8 @@ test("thinking streams separately and survives reload, retry, branch and import"
assert.equal(requests.at(-1).think, false);
for (const webSearch of [false, true]) for (const reasoning of [false, true]) {
const response = await fetch(base + "/chat/stream", {
method: "POST", headers: {"Content-Type": "application/json", Cookie: cookie},
body: JSON.stringify({chatId, message: "What is the latest news today?", model: "qwen3:14b", webSearch, reasoning})
method: "POST", headers: { "Content-Type": "application/json", Cookie: cookie },
body: JSON.stringify({ chatId, message: "What is the latest news today?", model: "qwen3:14b", webSearch, reasoning })
});
const wire = await response.text();
assert.match(wire, /event: done/);
@@ -110,15 +116,15 @@ test("thinking streams separately and survives reload, retry, branch and import"
assert.equal(Boolean(saved.sources?.length), webSearch);
}
assert.equal(searches, 2);
await api("/chat", {chatId, message: "Hello", model: "mistral:latest", reasoning: true});
await api("/chat", { chatId, message: "Hello", model: "mistral:latest", reasoning: true });
assert.equal(requests.at(-1).think, false);
assert.equal((await api(`/chats/${chatId}/messages`)).messages.at(-1).thinking, undefined);
const persona = (await api("/personas", {personaType: "assistant", name: "Guide", details: "Patient and direct"})).persona;
const roleplay = await api("/roleplays/start", {assistantPersonaId: persona.id, model: "qwen3:14b"});
const persona = (await api("/personas", { personaType: "assistant", name: "Guide", details: "Patient and direct" })).persona;
const roleplay = await api("/roleplays/start", { assistantPersonaId: persona.id, model: "qwen3:14b" });
assert.equal(requests.at(-1).think, false);
const roleplayResponse = await fetch(base + "/chat/stream", {
method: "POST", headers: {"Content-Type": "application/json", Cookie: cookie},
body: JSON.stringify({chatId: roleplay.chat.id, message: "Hello Guide", model: "qwen3:14b", reasoning: true})
method: "POST", headers: { "Content-Type": "application/json", Cookie: cookie },
body: JSON.stringify({ chatId: roleplay.chat.id, message: "Hello Guide", model: "qwen3:14b", reasoning: true })
});
const roleplayWire = await roleplayResponse.text();
assert.match(roleplayWire, /event: thinking/);
@@ -129,8 +135,8 @@ test("thinking streams separately and survives reload, retry, branch and import"
const roleplayMessages = (await api(`/chats/${roleplay.chat.id}/messages`)).messages;
for (const target of [roleplayMessages[0], roleplayMessages.at(-1)]) {
const retryResponse = await fetch(base + `/chats/${roleplay.chat.id}/messages/${target.id}/retry/stream`, {
method: "POST", headers: {"Content-Type": "application/json", Cookie: cookie},
body: JSON.stringify({model: "qwen3:14b", reasoning: true})
method: "POST", headers: { "Content-Type": "application/json", Cookie: cookie },
body: JSON.stringify({ model: "qwen3:14b", reasoning: true })
});
const retryWire = await retryResponse.text();
assert.match(retryWire, /event: thinking/);
@@ -156,7 +162,7 @@ test("reasoning disclosure is collapsed, preserves open state and renders text s
}
}
const original = globalThis.document;
globalThis.document = {createElement: tag => new Element(tag)};
globalThis.document = { createElement: tag => new Element(tag) };
t.after(() => { globalThis.document = original; });
const message = new Element("article");
const body = new Element("div");
@@ -181,7 +187,7 @@ test("reasoning disclosure is collapsed, preserves open state and renders text s
});
test("reasoning toggle defaults off, resets, and rejects unsupported models", () => {
const button = {setAttribute() {}, addEventListener(_event, callback) { this.click = callback; }};
const button = { setAttribute() { }, addEventListener(_event, callback) { this.click = callback; } };
const toggle = createReasoningToggle(button);
toggle.setModel("qwen3:14b");
assert.equal(button.disabled, false);
@@ -211,7 +217,7 @@ test("tagged Qwen3 models enable the control without activating reasoning", () =
for (const model of [
"qwen3:14b", "qwen3:8b", "qwen3:30b", "qwen3:latest",
" QWEN3:14b ", "library/qwen3:14b",
{model: "qwen3:14b"}, {name: "qwen3:8b"}, {id: "qwen3:latest"}
{ model: "qwen3:14b" }, { name: "qwen3:8b" }, { id: "qwen3:latest" }
]) {
assert.equal(getModelFamily(model), "qwen3");
assert.equal(supportsReasoning(model), true);