NEON / backend /src /ai /engine.ts
picklefried706's picture
Upload folder using huggingface_hub
40a9423 verified
import { ChatMessage, ChatRequest, SearchSummary } from "../types.js";
import { memoryStore } from "./memory.js";
import { summarizeExtractive, totalTokens } from "./context.js";
import { buildSystemPrompt, shouldSearch, trimHistory } from "./router.js";
import { searchWeb } from "../search/search.js";
import { streamChatCompletion } from "../utils/stream.js";
const MAX_CONTEXT_TOKENS = 2600;
export type EngineEvents = {
onStatus: (message: string) => void;
onToken: (token: string) => void;
onDone: () => void;
onError: (message: string) => void;
};
function buildSearchContext(summary: SearchSummary) {
const lines: string[] = [];
summary.results.forEach((r, idx) => {
lines.push(`Source ${idx + 1}: ${r.title} (${r.url})`);
lines.push(r.snippet);
});
summary.topContent.forEach((c, idx) => {
lines.push(`Content ${idx + 1}: ${c.title}`);
lines.push(c.content.slice(0, 1200));
});
return lines.join("\n");
}
export async function handleChat(request: ChatRequest, events: EngineEvents) {
const session = memoryStore.get(request.sessionId);
const userMessage: ChatMessage = {
id: `msg_${Date.now()}`,
role: "user",
content: request.message,
createdAt: Date.now()
};
session.messages.push(userMessage);
const useSearch = shouldSearch(request.message, request.mode ?? "auto");
let searchSummary: SearchSummary | null = null;
if (useSearch) {
events.onStatus("Searching web...");
try {
searchSummary = await searchWeb(request.message);
events.onStatus("Summarizing sources...");
} catch (err) {
const msg = err instanceof Error ? err.message : "Search failed";
events.onStatus("Search failed, continuing without web context.");
events.onError(msg);
}
}
if (totalTokens(session.messages) > MAX_CONTEXT_TOKENS) {
const trimmed = trimHistory(session.messages, 12);
session.summary = summarizeExtractive(trimmed, 6);
session.messages = trimmed;
}
const searchContext = searchSummary ? buildSearchContext(searchSummary) : null;
const systemPrompt = buildSystemPrompt(session.summary, searchContext);
const payloadMessages = [
{ role: "system", content: systemPrompt },
...session.messages.map((m) => ({ role: m.role, content: m.content }))
];
const assistantMessage: ChatMessage = {
id: `msg_${Date.now()}_assistant`,
role: "assistant",
content: "",
createdAt: Date.now()
};
try {
await streamChatCompletion(payloadMessages, (token) => {
assistantMessage.content += token;
events.onToken(token);
});
session.messages.push(assistantMessage);
memoryStore.set(request.sessionId, session);
events.onDone();
} catch (err) {
const msg = err instanceof Error ? err.message : "LLM call failed";
events.onError(msg);
}
}