add generation cancellation middleware and refactor chat/message workflows to support HTTP client disconnections
CI / check (22) (push) Failing after 4m54s
CI / check (24) (push) Failing after 4m54s

This commit is contained in:
2026-10-05 23:21:42 +02:00
parent c48e1b17c4
commit 1c0f06716e
17 changed files with 620 additions and 172 deletions
+34 -11
View File
@@ -201,6 +201,7 @@ export function createRepositories(db, config) {
updateChatMessageDelivery: db.prepare("UPDATE chat_messages SET delivery_status = ?, error_message = ? WHERE id = ? AND chat_id = ?"),
updateChatMessageSources: db.prepare("UPDATE chat_messages SET sources = ? WHERE id = ? AND chat_id = ?"),
updateChatMessageThinking: db.prepare("UPDATE chat_messages SET thinking = ? WHERE id = ? AND chat_id = ?"),
updateChatMessageLinks: db.prepare("UPDATE chat_messages SET parent_message_id = ?, retry_prompt_message_id = ? WHERE id = ? AND chat_id = ?"),
getChatMessages: db.prepare(`
SELECT thinking, sources, id, role, content, model_name, retry_variants, retry_active_index, retry_retries_used, retry_prompt_message_id, delivery_status, error_message, parent_message_id, created_at
FROM chat_messages
@@ -821,6 +822,8 @@ export function createRepositories(db, config) {
const importWorkspaceTransaction = db.transaction((username, workspace) => {
const imported = {personas: 0, chats: 0, templates: 0};
const personaIds = new Map();
const importedChats = [];
for (const persona of workspace.personas || []) {
const personaId = statements.insertPersona.run(
username,
@@ -832,14 +835,15 @@ export function createRepositories(db, config) {
persona.example_dialogues ?? null,
persona.persona_type || "assistant"
).lastInsertRowid;
statements.insertPersonaVersion.run(personaId, username, 1, JSON.stringify(persona));
if (persona.sourceId != null) personaIds.set(persona.sourceId, Number(personaId));
statements.insertPersonaVersion.run(personaId, username, 1, buildPersonaSnapshot(statements.getPersona.get(personaId, username)));
imported.personas += 1;
}
for (const template of workspace.templates || []) {
statements.insertPromptTemplate.run(
username,
null,
personaIds.get(template.persona_id) ?? null,
template.name,
template.description ?? null,
template.category ?? null,
@@ -851,11 +855,13 @@ export function createRepositories(db, config) {
}
for (const chat of workspace.chats || []) {
const assistantPersonaId = personaIds.get(chat.assistant_persona_id) ?? null;
const userPersonaId = personaIds.get(chat.user_persona_id) ?? null;
const chatId = statements.insertChatSession.run(
username,
chat.stored_title || chat.title || "Imported chat",
null,
null,
assistantPersonaId,
userPersonaId,
chat.folder_name ?? null,
chat.is_pinned ? 1 : 0,
chat.archived_at ?? null,
@@ -863,9 +869,8 @@ export function createRepositories(db, config) {
chat.scenario_summary ?? null
).lastInsertRowid;
const importedMessageIds = new Map();
importedChats.push({chat, chatId, messageIds: importedMessageIds});
for (const message of chat.messages || []) {
const mappedRetryPromptId = importedMessageIds.get(message.retryPromptMessageId) ?? null;
const mappedParentId = importedMessageIds.get(message.parentMessageId) ?? null;
const result = statements.insertChatMessage.run(
chatId,
message.role,
@@ -874,10 +879,10 @@ export function createRepositories(db, config) {
JSON.stringify(message.retryVariants || [message.content || ""]),
Number(message.retryActiveIndex || 0),
Number(message.retryRetriesUsed || 0),
mappedRetryPromptId,
null,
message.deliveryStatus || "complete",
message.errorMessage || null,
mappedParentId
null
);
if (message.sourceId != null) importedMessageIds.set(message.sourceId, Number(result.lastInsertRowid));
if (message.sources) statements.updateChatMessageSources.run(JSON.stringify(message.sources), result.lastInsertRowid, chatId);
@@ -893,6 +898,15 @@ export function createRepositories(db, config) {
db.prepare("UPDATE attachments SET status='attached' WHERE id=? AND username=?").run(attachment.id, username);
}
}
// Resolve links after every message has an ID, including forward references.
for (const message of chat.messages || []) {
statements.updateChatMessageLinks.run(
importedMessageIds.get(message.parentMessageId) ?? null,
importedMessageIds.get(message.retryPromptMessageId) ?? null,
importedMessageIds.get(message.sourceId),
chatId
);
}
statements.updateChatGenerationSettings.run(
chat.preferred_model ?? null,
chat.temperature ?? null,
@@ -918,15 +932,17 @@ export function createRepositories(db, config) {
for (const memory of chat.memories || []) {
const fact = String(memory?.fact || "").trim();
if (!fact) continue;
statements.insertChatMemory.run(username, chatId, null, null, fact);
statements.insertChatMemory.run(username, chatId,
memory.assistant_persona_id === undefined ? assistantPersonaId : personaIds.get(memory.assistant_persona_id) ?? null,
memory.user_persona_id === undefined ? userPersonaId : personaIds.get(memory.user_persona_id) ?? null, fact);
}
const personaState = chat.persona_state || chat.personaChatState;
if (personaState) {
statements.upsertPersonaChatState.run(
username,
chatId,
null,
null,
personaIds.get(personaState.assistant_persona_id) ?? null,
personaIds.get(personaState.user_persona_id) ?? null,
personaState.relationship_notes ?? personaState.relationshipNotes ?? null,
personaState.tone ?? null,
personaState.current_location ?? personaState.currentLocation ?? null,
@@ -938,6 +954,13 @@ export function createRepositories(db, config) {
imported.chats += 1;
}
const chatsBySourceId = new Map(importedChats.filter(({chat}) => chat.sourceId != null).map(entry => [entry.chat.sourceId, entry]));
for (const {chat, chatId} of importedChats) {
const sourceChat = chatsBySourceId.get(chat.source_chat_id);
statements.updateChatBranchSource.run(sourceChat?.chatId ?? null,
sourceChat?.messageIds.get(chat.branched_from_message_id) ?? null, chatId, username);
}
return imported;
});