Files
hermes-chat-v2/src/routes/api/hermes/chat/+server.ts
Hermes Agent 511793772a Initial commit: Hermes Chat v2
Svelte 5 + shadcn-svelte + Tailwind CSS v4 chat interface
for Hermes Agent with Monaco editor, file drag/drop,
artifacts, image preview, and real session management.

- SSE streaming via hermes CLI proxy
- File upload + drag/drop with image preview
- Code artifacts with Monaco editor
- Markdown + highlight.js syntax highlighting
- Full settings (model, provider, temp, system prompt)
- 12 shadcn UI components
- Dark/light theme
2026-05-22 05:21:37 +02:00

99 lines
2.7 KiB
TypeScript

import { json } from '@sveltejs/kit';
import { spawn } from 'child_process';
import type { RequestEvent } from '@sveltejs/kit';
export async function POST({ request }: RequestEvent) {
try {
const { messages, config } = await request.json();
const lastMsg = messages?.[messages.length - 1]?.content || '';
const modelFlag = config?.model ? ['-m', config.model] : [];
const providerFlag = config?.provider ? ['--provider', config.provider] : [];
const tempFlag = config?.temperature ? ['--temperature', String(config.temperature)] : [];
const maxTokensFlag = config?.maxTokens ? ['--max-tokens', String(config.maxTokens)] : [];
const hermes = spawn('hermes', [
'chat',
'-q', lastMsg,
...modelFlag,
...providerFlag,
...tempFlag,
...maxTokensFlag,
'-Q',
'--source', 'hermes-chat-v2'
], {
timeout: 120000,
env: { ...process.env }
});
const stream = new ReadableStream({
async start(controller) {
const decoder = new TextDecoder();
let buffer = '';
const processChunk = (chunk: Buffer) => {
buffer += decoder.decode(chunk, { stream: true });
while (buffer.includes('\n')) {
const idx = buffer.indexOf('\n');
const line = buffer.slice(0, idx);
buffer = buffer.slice(idx + 1);
if (line.trim()) {
const payload = JSON.stringify({
choices: [{ delta: { content: line + '\n' } }]
});
controller.enqueue(new TextEncoder().encode(`data: ${payload}\n\n`));
}
}
};
hermes.stdout?.on('data', processChunk);
hermes.stderr?.on('data', (data: Buffer) => {
const text = decoder.decode(data, { stream: true });
if (text.trim()) {
const payload = JSON.stringify({
choices: [{ delta: { content: `*${text.trim()}* ` } }]
});
controller.enqueue(new TextEncoder().encode(`data: ${payload}\n\n`));
}
});
hermes.on('close', () => {
if (buffer.trim()) {
const payload = JSON.stringify({
choices: [{ delta: { content: buffer } }]
});
controller.enqueue(new TextEncoder().encode(`data: ${payload}\n\n`));
}
controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
controller.close();
});
hermes.on('error', (err) => {
controller.enqueue(
new TextEncoder().encode(
`data: ${JSON.stringify({ error: err.message })}\n\n`
)
);
controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
controller.close();
});
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
}
});
} catch (err) {
return json(
{ error: err instanceof Error ? err.message : 'Internal server error' },
{ status: 500 }
);
}
}