History Polling: Message-ID-basiert, neuer /api/history/poll Endpoint
This commit is contained in:
1 parent
19f926da05
commit
fb2c3a0720
5 files changed
+90
-25
No files matched your search
@@ -14,6 +14,7 @@ public static class HistoryEndpoint
|
|||||||
public static void MapHistoryEndpoint(this IEndpointRouteBuilder app)
|
public static void MapHistoryEndpoint(this IEndpointRouteBuilder app)
|
||||||
{
|
{
|
||||||
app.MapGet("/api/history", HandleHistory);
|
app.MapGet("/api/history", HandleHistory);
|
||||||
|
app.MapGet("/api/history/poll", HandlePoll);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleHistory(HttpContext ctx, DatabaseService db)
|
private static async Task HandleHistory(HttpContext ctx, DatabaseService db)
|
||||||
@@ -37,4 +38,13 @@ public static class HistoryEndpoint
|
|||||||
|
|
||||||
await ctx.Response.WriteAsJsonAsync(result);
|
await ctx.Response.WriteAsJsonAsync(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task HandlePoll(HttpContext ctx, DatabaseService db)
|
||||||
|
{
|
||||||
|
var afterStr = ctx.Request.Query["afterId"].FirstOrDefault();
|
||||||
|
var afterId = long.TryParse(afterStr, out var a) ? a : 0;
|
||||||
|
|
||||||
|
var messages = await db.GetMessagesAfterId(afterId);
|
||||||
|
await ctx.Response.WriteAsJsonAsync(messages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -135,4 +135,13 @@ public class DatabaseService
|
|||||||
"SELECT * FROM messages WHERE chain_id = @chainId ORDER BY id", new { chainId }
|
"SELECT * FROM messages WHERE chain_id = @chainId ORDER BY id", new { chainId }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<MessageRecord>> GetMessagesAfterId(long afterId, int limit = 200)
|
||||||
|
{
|
||||||
|
using var conn = CreateConnection();
|
||||||
|
return await conn.QueryAsync<MessageRecord>(
|
||||||
|
"SELECT * FROM messages WHERE id > @afterId ORDER BY id LIMIT @limit",
|
||||||
|
new { afterId, limit }
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,3 +12,8 @@ async function apiHistory(sessionId = 'null', limit = 50) {
|
|||||||
const resp = await fetch(`/api/history?sessionId=${sessionId}&limit=${limit}`);
|
const resp = await fetch(`/api/history?sessionId=${sessionId}&limit=${limit}`);
|
||||||
return resp.json();
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function apiHistoryPoll(afterId) {
|
||||||
|
const resp = await fetch(`/api/history/poll?afterId=${afterId}`);
|
||||||
|
return resp.json();
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// ── Chat State ──
|
// ── Chat State ──
|
||||||
|
|
||||||
const messages = [];
|
const messages = [];
|
||||||
|
let isStreaming = false;
|
||||||
|
|
||||||
function clearChat() {
|
function clearChat() {
|
||||||
messages.length = 0;
|
messages.length = 0;
|
||||||
@@ -24,6 +25,7 @@ async function sendMessage() {
|
|||||||
input.value = '';
|
input.value = '';
|
||||||
input.style.height = 'auto';
|
input.style.height = 'auto';
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
isStreaming = true;
|
||||||
setMetaHtml('<span style="color:#7c83ff">⏳ Streaming...</span>');
|
setMetaHtml('<span style="color:#7c83ff">⏳ Streaming...</span>');
|
||||||
|
|
||||||
let fullText = '';
|
let fullText = '';
|
||||||
@@ -103,6 +105,7 @@ async function sendMessage() {
|
|||||||
addErrorBubble('Netzwerk-Fehler: ' + e.message);
|
addErrorBubble('Netzwerk-Fehler: ' + e.message);
|
||||||
} finally {
|
} finally {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
|
isStreaming = false;
|
||||||
endStreamBubble();
|
endStreamBubble();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+63
-25
@@ -1,40 +1,78 @@
|
|||||||
// ── History Loading ──
|
// ── History Loading ──
|
||||||
|
|
||||||
|
let lastKnownMessageId = 0;
|
||||||
|
let historyPollInterval = null;
|
||||||
|
|
||||||
async function loadHistory() {
|
async function loadHistory() {
|
||||||
try {
|
try {
|
||||||
const data = await apiHistory('null');
|
const data = await apiHistory('null');
|
||||||
data.reverse(); // DESC → chronologisch
|
data.reverse(); // DESC → chronologisch
|
||||||
|
|
||||||
for (const entry of data) {
|
for (const entry of data) {
|
||||||
for (const msg of entry.messages) {
|
renderMessages(entry.messages, true);
|
||||||
switch (msg.type) {
|
|
||||||
case 'text':
|
|
||||||
if (msg.role === 'user') {
|
|
||||||
addUserBubble(msg.content);
|
|
||||||
messages.push({ role: 'user', content: msg.content });
|
|
||||||
} else if (msg.role === 'assistant') {
|
|
||||||
addAssistantBubble(msg.content);
|
|
||||||
messages.push({ role: 'assistant', content: msg.content });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tool_use':
|
|
||||||
addToolBoxStatic(msg.toolName, msg.content, null, false);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tool_result':
|
|
||||||
addToolBoxStatic(null, null, msg.content, msg.isError);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'error':
|
|
||||||
addErrorBubble(msg.content);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollDown();
|
scrollDown();
|
||||||
|
startHistoryPolling();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('History laden fehlgeschlagen:', e);
|
console.error('History laden fehlgeschlagen:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderMessages(msgs, addToConversation) {
|
||||||
|
for (const msg of msgs) {
|
||||||
|
if (msg.id > lastKnownMessageId) lastKnownMessageId = msg.id;
|
||||||
|
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'text':
|
||||||
|
if (msg.role === 'user') {
|
||||||
|
addUserBubble(msg.content);
|
||||||
|
if (addToConversation) messages.push({ role: 'user', content: msg.content });
|
||||||
|
} else if (msg.role === 'assistant') {
|
||||||
|
addAssistantBubble(msg.content);
|
||||||
|
if (addToConversation) messages.push({ role: 'assistant', content: msg.content });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tool_use':
|
||||||
|
addToolBoxStatic(msg.toolName, msg.content, null, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tool_result':
|
||||||
|
addToolBoxStatic(null, null, msg.content, msg.isError);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
addErrorBubble(msg.content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Polling ──
|
||||||
|
|
||||||
|
function startHistoryPolling(intervalMs = 3000) {
|
||||||
|
if (historyPollInterval) return;
|
||||||
|
historyPollInterval = setInterval(pollHistory, intervalMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopHistoryPolling() {
|
||||||
|
if (historyPollInterval) {
|
||||||
|
clearInterval(historyPollInterval);
|
||||||
|
historyPollInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollHistory() {
|
||||||
|
if (isStreaming) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newMessages = await apiHistoryPoll(lastKnownMessageId);
|
||||||
|
if (newMessages.length > 0) {
|
||||||
|
renderMessages(newMessages, true);
|
||||||
|
scrollDown();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('History poll fehlgeschlagen:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user