refactor: proper websocket message types

This commit is contained in:
Elian Doran 2025-09-13 12:54:53 +03:00
parent 998688573d
commit 4cd0702cbb
No known key found for this signature in database
19 changed files with 164 additions and 170 deletions

View File

@ -116,7 +116,7 @@ export type CommandMappings = {
openedFileUpdated: CommandData & { openedFileUpdated: CommandData & {
entityType: string; entityType: string;
entityId: string; entityId: string;
lastModifiedMs: number; lastModifiedMs?: number;
filePath: string; filePath: string;
}; };
focusAndSelectTitle: CommandData & { focusAndSelectTitle: CommandData & {

View File

@ -210,7 +210,7 @@ function makeToast(id: string, message: string): ToastOptions {
} }
ws.subscribeToMessages(async (message) => { ws.subscribeToMessages(async (message) => {
if (message.taskType !== "deleteNotes") { if (!("taskType" in message) || message.taskType !== "deleteNotes") {
return; return;
} }
@ -228,7 +228,7 @@ ws.subscribeToMessages(async (message) => {
}); });
ws.subscribeToMessages(async (message) => { ws.subscribeToMessages(async (message) => {
if (message.taskType !== "undeleteNotes") { if (!("taskType" in message) || message.taskType !== "undeleteNotes") {
return; return;
} }

View File

@ -1,16 +1,8 @@
import ws from "./ws.js"; import ws from "./ws.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import { OpenedFileUpdateStatus } from "@triliumnext/commons";
// TODO: Deduplicate const fileModificationStatus: Record<string, Record<string, OpenedFileUpdateStatus>> = {
interface Message {
type: string;
entityType: string;
entityId: string;
lastModifiedMs: number;
filePath: string;
}
const fileModificationStatus: Record<string, Record<string, Message>> = {
notes: {}, notes: {},
attachments: {} attachments: {}
}; };
@ -39,7 +31,7 @@ function ignoreModification(entityType: string, entityId: string) {
delete fileModificationStatus[entityType][entityId]; delete fileModificationStatus[entityType][entityId];
} }
ws.subscribeToMessages(async (message: Message) => { ws.subscribeToMessages(async message => {
if (message.type !== "openedFileUpdated") { if (message.type !== "openedFileUpdated") {
return; return;
} }

View File

@ -4,6 +4,7 @@ import ws from "./ws.js";
import utils from "./utils.js"; import utils from "./utils.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import { t } from "./i18n.js"; import { t } from "./i18n.js";
import { WebSocketMessage } from "@triliumnext/commons";
type BooleanLike = boolean | "true" | "false"; type BooleanLike = boolean | "true" | "false";
@ -66,7 +67,7 @@ function makeToast(id: string, message: string): ToastOptions {
} }
ws.subscribeToMessages(async (message) => { ws.subscribeToMessages(async (message) => {
if (message.taskType !== "importNotes") { if (!("taskType" in message) || message.taskType !== "importNotes") {
return; return;
} }
@ -81,14 +82,14 @@ ws.subscribeToMessages(async (message) => {
toastService.showPersistent(toast); toastService.showPersistent(toast);
if (message.result.importedNoteId) { if (typeof message.result === "object" && message.result.importedNoteId) {
await appContext.tabManager.getActiveContext()?.setNote(message.result.importedNoteId); await appContext.tabManager.getActiveContext()?.setNote(message.result.importedNoteId);
} }
} }
}); });
ws.subscribeToMessages(async (message) => { ws.subscribeToMessages(async (message: WebSocketMessage) => {
if (message.taskType !== "importAttachments") { if (!("taskType" in message) || message.taskType !== "importAttachments") {
return; return;
} }
@ -103,7 +104,7 @@ ws.subscribeToMessages(async (message) => {
toastService.showPersistent(toast); toastService.showPersistent(toast);
if (message.result.parentNoteId) { if (typeof message.result === "object" && message.result.parentNoteId) {
await appContext.tabManager.getActiveContext()?.setNote(message.result.importedNoteId, { await appContext.tabManager.getActiveContext()?.setNote(message.result.importedNoteId, {
viewScope: { viewScope: {
viewMode: "attachments" viewMode: "attachments"

View File

@ -107,7 +107,7 @@ function makeToast(message: Message, title: string, text: string): ToastOptions
} }
ws.subscribeToMessages(async (message) => { ws.subscribeToMessages(async (message) => {
if (message.taskType !== "protectNotes") { if (!("taskType" in message) || message.taskType !== "protectNotes") {
return; return;
} }

View File

@ -6,8 +6,9 @@ import frocaUpdater from "./froca_updater.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import { t } from "./i18n.js"; import { t } from "./i18n.js";
import type { EntityChange } from "../server_types.js"; import type { EntityChange } from "../server_types.js";
import { WebSocketMessage } from "@triliumnext/commons";
type MessageHandler = (message: any) => void; type MessageHandler = (message: WebSocketMessage) => void;
const messageHandlers: MessageHandler[] = []; const messageHandlers: MessageHandler[] = [];
let ws: WebSocket; let ws: WebSocket;

View File

@ -140,7 +140,7 @@ ws.subscribeToMessages(async (message) => {
}; };
} }
if (message.taskType !== "export") { if (!("taskType" in message) || message.taskType !== "export") {
return; return;
} }
@ -155,4 +155,4 @@ ws.subscribeToMessages(async (message) => {
toastService.showPersistent(toast); toastService.showPersistent(toast);
} }
}); });

View File

@ -5,6 +5,7 @@ import options from "../services/options.js";
import syncService from "../services/sync.js"; import syncService from "../services/sync.js";
import { escapeQuotes } from "../services/utils.js"; import { escapeQuotes } from "../services/utils.js";
import { Tooltip } from "bootstrap"; import { Tooltip } from "bootstrap";
import { WebSocketMessage } from "@triliumnext/commons";
const TPL = /*html*/` const TPL = /*html*/`
<div class="sync-status-widget launcher-button"> <div class="sync-status-widget launcher-button">
@ -117,8 +118,7 @@ export default class SyncStatusWidget extends BasicWidget {
this.$widget.find(`.sync-status-${className}`).show(); this.$widget.find(`.sync-status-${className}`).show();
} }
// TriliumNextTODO: Use Type Message from "services/ws.ts" processMessage(message: WebSocketMessage) {
processMessage(message: { type: string; lastSyncedPush: number; data: { lastSyncedPush: number } }) {
if (message.type === "sync-pull-in-progress") { if (message.type === "sync-pull-in-progress") {
this.syncState = "in-progress"; this.syncState = "in-progress";
this.lastSyncedPush = message.lastSyncedPush; this.lastSyncedPush = message.lastSyncedPush;

View File

@ -73,13 +73,13 @@ export default class WatchedFileUpdateStatusWidget extends NoteContextAwareWidge
async refreshWithNote(note: FNote) { async refreshWithNote(note: FNote) {
const { entityType, entityId } = this.getEntity(); const { entityType, entityId } = this.getEntity();
if (!entityType || !entityId) { if (!entityType || !entityId) return;
return;
}
const status = fileWatcher.getFileModificationStatus(entityType, entityId); const status = fileWatcher.getFileModificationStatus(entityType, entityId);
this.$filePath.text(status.filePath); this.$filePath.text(status.filePath);
this.$fileLastModified.text(dayjs.unix(status.lastModifiedMs / 1000).format("HH:mm:ss")); if (status.lastModifiedMs) {
this.$fileLastModified.text(dayjs.unix(status.lastModifiedMs / 1000).format("HH:mm:ss"));
}
} }
getEntity() { getEntity() {

View File

@ -1,18 +1,9 @@
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import log from "../../services/log.js"; import log from "../../services/log.js";
import options from "../../services/options.js";
import restChatService from "../../services/llm/rest_chat_service.js"; import restChatService from "../../services/llm/rest_chat_service.js";
import chatStorageService from '../../services/llm/chat_storage_service.js'; import chatStorageService from '../../services/llm/chat_storage_service.js';
import { WebSocketMessage } from "@triliumnext/commons";
// Define basic interfaces
interface ChatMessage {
role: 'user' | 'assistant' | 'system';
content: string;
timestamp?: Date;
}
/** /**
* @swagger * @swagger
@ -419,7 +410,7 @@ async function sendMessage(req: Request, res: Response) {
*/ */
async function streamMessage(req: Request, res: Response) { async function streamMessage(req: Request, res: Response) {
log.info("=== Starting streamMessage ==="); log.info("=== Starting streamMessage ===");
try { try {
const chatNoteId = req.params.chatNoteId; const chatNoteId = req.params.chatNoteId;
const { content, useAdvancedContext, showThinking, mentions } = req.body; const { content, useAdvancedContext, showThinking, mentions } = req.body;
@ -434,7 +425,7 @@ async function streamMessage(req: Request, res: Response) {
(res as any).triliumResponseHandled = true; (res as any).triliumResponseHandled = true;
return; return;
} }
// Send immediate success response // Send immediate success response
res.status(200).json({ res.status(200).json({
success: true, success: true,
@ -442,12 +433,12 @@ async function streamMessage(req: Request, res: Response) {
}); });
// Mark response as handled to prevent further processing // Mark response as handled to prevent further processing
(res as any).triliumResponseHandled = true; (res as any).triliumResponseHandled = true;
// Start background streaming process after sending response // Start background streaming process after sending response
handleStreamingProcess(chatNoteId, content, useAdvancedContext, showThinking, mentions) handleStreamingProcess(chatNoteId, content, useAdvancedContext, showThinking, mentions)
.catch(error => { .catch(error => {
log.error(`Background streaming error: ${error.message}`); log.error(`Background streaming error: ${error.message}`);
// Send error via WebSocket since HTTP response was already sent // Send error via WebSocket since HTTP response was already sent
import('../../services/ws.js').then(wsModule => { import('../../services/ws.js').then(wsModule => {
wsModule.default.sendMessageToAllClients({ wsModule.default.sendMessageToAllClients({
@ -460,11 +451,11 @@ async function streamMessage(req: Request, res: Response) {
log.error(`Could not send WebSocket error: ${wsError}`); log.error(`Could not send WebSocket error: ${wsError}`);
}); });
}); });
} catch (error) { } catch (error) {
// Handle any synchronous errors // Handle any synchronous errors
log.error(`Synchronous error in streamMessage: ${error}`); log.error(`Synchronous error in streamMessage: ${error}`);
if (!res.headersSent) { if (!res.headersSent) {
res.status(500).json({ res.status(500).json({
success: false, success: false,
@ -481,21 +472,21 @@ async function streamMessage(req: Request, res: Response) {
* This is separate from the HTTP request/response cycle * This is separate from the HTTP request/response cycle
*/ */
async function handleStreamingProcess( async function handleStreamingProcess(
chatNoteId: string, chatNoteId: string,
content: string, content: string,
useAdvancedContext: boolean, useAdvancedContext: boolean,
showThinking: boolean, showThinking: boolean,
mentions: any[] mentions: any[]
) { ) {
log.info("=== Starting background streaming process ==="); log.info("=== Starting background streaming process ===");
// Get or create chat directly from storage // Get or create chat directly from storage
let chat = await chatStorageService.getChat(chatNoteId); let chat = await chatStorageService.getChat(chatNoteId);
if (!chat) { if (!chat) {
chat = await chatStorageService.createChat('New Chat'); chat = await chatStorageService.createChat('New Chat');
log.info(`Created new chat with ID: ${chat.id} for stream request`); log.info(`Created new chat with ID: ${chat.id} for stream request`);
} }
// Add the user message to the chat immediately // Add the user message to the chat immediately
chat.messages.push({ chat.messages.push({
role: 'user', role: 'user',
@ -544,9 +535,9 @@ async function handleStreamingProcess(
thinking: showThinking ? 'Initializing streaming LLM response...' : undefined thinking: showThinking ? 'Initializing streaming LLM response...' : undefined
}); });
// Instead of calling the complex handleSendMessage service, // Instead of calling the complex handleSendMessage service,
// let's implement streaming directly to avoid response conflicts // let's implement streaming directly to avoid response conflicts
try { try {
// Check if AI is enabled // Check if AI is enabled
const optionsModule = await import('../../services/options.js'); const optionsModule = await import('../../services/options.js');
@ -570,7 +561,7 @@ async function handleStreamingProcess(
// Get selected model // Get selected model
const { getSelectedModelConfig } = await import('../../services/llm/config/configuration_helpers.js'); const { getSelectedModelConfig } = await import('../../services/llm/config/configuration_helpers.js');
const modelConfig = await getSelectedModelConfig(); const modelConfig = await getSelectedModelConfig();
if (!modelConfig) { if (!modelConfig) {
throw new Error("No valid AI model configuration found"); throw new Error("No valid AI model configuration found");
} }
@ -590,7 +581,7 @@ async function handleStreamingProcess(
chatNoteId: chatNoteId chatNoteId: chatNoteId
}, },
streamCallback: (data, done, rawChunk) => { streamCallback: (data, done, rawChunk) => {
const message = { const message: WebSocketMessage = {
type: 'llm-stream' as const, type: 'llm-stream' as const,
chatNoteId: chatNoteId, chatNoteId: chatNoteId,
done: done done: done
@ -634,7 +625,7 @@ async function handleStreamingProcess(
// Execute the pipeline // Execute the pipeline
await pipeline.execute(pipelineInput); await pipeline.execute(pipelineInput);
} catch (error: any) { } catch (error: any) {
log.error(`Error in direct streaming: ${error.message}`); log.error(`Error in direct streaming: ${error.message}`);
wsService.sendMessageToAllClients({ wsService.sendMessageToAllClients({

View File

@ -2,8 +2,7 @@
import mimeTypes from "mime-types"; import mimeTypes from "mime-types";
import path from "path"; import path from "path";
import type { TaskData } from "../task_context_interface.js"; import type { NoteType, TaskData } from "@triliumnext/commons";
import type { NoteType } from "@triliumnext/commons";
const CODE_MIME_TYPES = new Set([ const CODE_MIME_TYPES = new Set([
"application/json", "application/json",

View File

@ -4,7 +4,6 @@
import log from "../../../log.js"; import log from "../../../log.js";
import type { Response } from "express"; import type { Response } from "express";
import type { StreamChunk } from "../../ai_interface.js"; import type { StreamChunk } from "../../ai_interface.js";
import type { LLMStreamMessage } from "../../interfaces/chat_ws_messages.js";
import type { ChatSession } from "../../interfaces/chat_session.js"; import type { ChatSession } from "../../interfaces/chat_session.js";
/** /**
@ -46,7 +45,7 @@ export class StreamHandler {
type: 'llm-stream', type: 'llm-stream',
chatNoteId, chatNoteId,
thinking: 'Preparing response...' thinking: 'Preparing response...'
} as LLMStreamMessage); });
try { try {
// Import the tool handler // Import the tool handler
@ -66,7 +65,7 @@ export class StreamHandler {
type: 'llm-stream', type: 'llm-stream',
chatNoteId, chatNoteId,
thinking: 'Analyzing tools needed for this request...' thinking: 'Analyzing tools needed for this request...'
} as LLMStreamMessage); });
try { try {
// Execute the tools // Execute the tools
@ -82,7 +81,7 @@ export class StreamHandler {
tool: toolResult.name, tool: toolResult.name,
result: toolResult.content.substring(0, 100) + (toolResult.content.length > 100 ? '...' : '') result: toolResult.content.substring(0, 100) + (toolResult.content.length > 100 ? '...' : '')
} }
} as LLMStreamMessage); });
} }
// Make follow-up request with tool results // Make follow-up request with tool results
@ -123,7 +122,7 @@ export class StreamHandler {
chatNoteId, chatNoteId,
error: `Error executing tools: ${toolError instanceof Error ? toolError.message : 'Unknown error'}`, error: `Error executing tools: ${toolError instanceof Error ? toolError.message : 'Unknown error'}`,
done: true done: true
} as LLMStreamMessage); });
} }
} else if (response.stream) { } else if (response.stream) {
// Handle standard streaming through the stream() method // Handle standard streaming through the stream() method
@ -152,7 +151,7 @@ export class StreamHandler {
chatNoteId, chatNoteId,
content: messageContent, content: messageContent,
done: true done: true
} as LLMStreamMessage); });
log.info(`Complete response sent`); log.info(`Complete response sent`);
@ -174,14 +173,14 @@ export class StreamHandler {
type: 'llm-stream', type: 'llm-stream',
chatNoteId, chatNoteId,
error: `Error generating response: ${streamingError instanceof Error ? streamingError.message : 'Unknown error'}` error: `Error generating response: ${streamingError instanceof Error ? streamingError.message : 'Unknown error'}`
} as LLMStreamMessage); });
// Signal completion // Signal completion
wsService.sendMessageToAllClients({ wsService.sendMessageToAllClients({
type: 'llm-stream', type: 'llm-stream',
chatNoteId, chatNoteId,
done: true done: true
} as LLMStreamMessage); });
} }
} }
@ -218,7 +217,7 @@ export class StreamHandler {
done: !!chunk.done, // Include done flag with each chunk done: !!chunk.done, // Include done flag with each chunk
// Include any raw data from the provider that might contain thinking/tool info // Include any raw data from the provider that might contain thinking/tool info
...(chunk.raw ? { raw: chunk.raw } : {}) ...(chunk.raw ? { raw: chunk.raw } : {})
} as LLMStreamMessage); });
// Log the first chunk (useful for debugging) // Log the first chunk (useful for debugging)
if (messageContent.length === chunk.text.length) { if (messageContent.length === chunk.text.length) {
@ -232,7 +231,7 @@ export class StreamHandler {
type: 'llm-stream', type: 'llm-stream',
chatNoteId, chatNoteId,
thinking: chunk.raw.thinking thinking: chunk.raw.thinking
} as LLMStreamMessage); });
} }
// If the provider indicates tool execution, relay that // If the provider indicates tool execution, relay that
@ -241,7 +240,7 @@ export class StreamHandler {
type: 'llm-stream', type: 'llm-stream',
chatNoteId, chatNoteId,
toolExecution: chunk.raw.toolExecution toolExecution: chunk.raw.toolExecution
} as LLMStreamMessage); });
} }
// Handle direct tool_calls in the response (for OpenAI) // Handle direct tool_calls in the response (for OpenAI)
@ -252,7 +251,7 @@ export class StreamHandler {
wsService.sendMessageToAllClients({ wsService.sendMessageToAllClients({
type: 'tool_execution_start', type: 'tool_execution_start',
chatNoteId chatNoteId
} as LLMStreamMessage); });
// Process each tool call // Process each tool call
for (const toolCall of chunk.tool_calls) { for (const toolCall of chunk.tool_calls) {
@ -277,7 +276,7 @@ export class StreamHandler {
toolCallId: toolCall.id, toolCallId: toolCall.id,
args: args args: args
} }
} as LLMStreamMessage); });
} }
} }
@ -337,7 +336,7 @@ export class StreamHandler {
type: 'llm-stream', type: 'llm-stream',
chatNoteId, chatNoteId,
done: true done: true
} as LLMStreamMessage); });
} }
// Store the full response in the session // Store the full response in the session
@ -360,7 +359,7 @@ export class StreamHandler {
chatNoteId, chatNoteId,
error: `Error during streaming: ${streamError instanceof Error ? streamError.message : 'Unknown error'}`, error: `Error during streaming: ${streamError instanceof Error ? streamError.message : 'Unknown error'}`,
done: true done: true
} as LLMStreamMessage); });
throw streamError; throw streamError;
} }

View File

@ -7,7 +7,6 @@ import { ToolHandler } from './handlers/tool_handler.js';
import { StreamHandler } from './handlers/stream_handler.js'; import { StreamHandler } from './handlers/stream_handler.js';
import * as messageFormatter from './utils/message_formatter.js'; import * as messageFormatter from './utils/message_formatter.js';
import type { ChatSession, ChatMessage, NoteSource } from '../interfaces/chat_session.js'; import type { ChatSession, ChatMessage, NoteSource } from '../interfaces/chat_session.js';
import type { LLMStreamMessage } from '../interfaces/chat_ws_messages.js';
// Export components // Export components
export { export {
@ -22,6 +21,5 @@ export {
export type { export type {
ChatSession, ChatSession,
ChatMessage, ChatMessage,
NoteSource, NoteSource
LLMStreamMessage
}; };

View File

@ -4,18 +4,15 @@
*/ */
import log from "../../log.js"; import log from "../../log.js";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import type { Message, ChatCompletionOptions } from "../ai_interface.js"; import type { Message } from "../ai_interface.js";
import aiServiceManager from "../ai_service_manager.js"; import aiServiceManager from "../ai_service_manager.js";
import { ChatPipeline } from "../pipeline/chat_pipeline.js"; import { ChatPipeline } from "../pipeline/chat_pipeline.js";
import type { ChatPipelineInput } from "../pipeline/interfaces.js"; import type { ChatPipelineInput } from "../pipeline/interfaces.js";
import options from "../../options.js"; import options from "../../options.js";
import { ToolHandler } from "./handlers/tool_handler.js"; import { ToolHandler } from "./handlers/tool_handler.js";
import type { LLMStreamMessage } from "../interfaces/chat_ws_messages.js";
import chatStorageService from '../chat_storage_service.js'; import chatStorageService from '../chat_storage_service.js';
import { import { getSelectedModelConfig } from '../config/configuration_helpers.js';
isAIEnabled, import { WebSocketMessage } from "@triliumnext/commons";
getSelectedModelConfig,
} from '../config/configuration_helpers.js';
/** /**
* Simplified service to handle chat API interactions * Simplified service to handle chat API interactions
@ -79,7 +76,7 @@ class RestChatService {
throw new Error("Database is not initialized"); throw new Error("Database is not initialized");
} }
// Get or create AI service - will throw meaningful error if not possible // Get or create AI service - will throw meaningful error if not possible
await aiServiceManager.getOrCreateAnyService(); await aiServiceManager.getOrCreateAnyService();
// Load or create chat directly from storage // Load or create chat directly from storage
@ -204,7 +201,7 @@ class RestChatService {
accumulatedContentRef: { value: string }, accumulatedContentRef: { value: string },
chat: { id: string; messages: Message[]; title: string } chat: { id: string; messages: Message[]; title: string }
) { ) {
const message: LLMStreamMessage = { const message: WebSocketMessage = {
type: 'llm-stream', type: 'llm-stream',
chatNoteId: chatNoteId, chatNoteId: chatNoteId,
done: done done: done
@ -237,7 +234,7 @@ class RestChatService {
// Send WebSocket message // Send WebSocket message
wsService.sendMessageToAllClients(message); wsService.sendMessageToAllClients(message);
// When streaming is complete, save the accumulated content to the chat note // When streaming is complete, save the accumulated content to the chat note
if (done) { if (done) {
try { try {
@ -248,7 +245,7 @@ class RestChatService {
role: 'assistant', role: 'assistant',
content: accumulatedContentRef.value content: accumulatedContentRef.value
}); });
// Save the updated chat back to storage // Save the updated chat back to storage
await chatStorageService.updateChat(chat.id, chat.messages, chat.title); await chatStorageService.updateChat(chat.id, chat.messages, chat.title);
log.info(`Saved streaming assistant response: ${accumulatedContentRef.value.length} characters`); log.info(`Saved streaming assistant response: ${accumulatedContentRef.value.length} characters`);
@ -257,7 +254,7 @@ class RestChatService {
// Log error but don't break the response flow // Log error but don't break the response flow
log.error(`Error saving streaming response: ${error}`); log.error(`Error saving streaming response: ${error}`);
} }
// Note: For WebSocket-only streaming, we don't end the HTTP response here // Note: For WebSocket-only streaming, we don't end the HTTP response here
// since it was already handled by the calling endpoint // since it was already handled by the calling endpoint
} }

View File

@ -1,24 +0,0 @@
/**
* Interfaces for WebSocket LLM streaming messages
*/
/**
* Interface for WebSocket LLM streaming messages
*/
export interface LLMStreamMessage {
type: 'llm-stream' | 'tool_execution_start' | 'tool_result' | 'tool_execution_error' | 'tool_completion_processing';
chatNoteId: string;
content?: string;
thinking?: string;
toolExecution?: {
action?: string;
tool?: string;
toolCallId?: string;
result?: string | Record<string, any>;
error?: string;
args?: Record<string, unknown>;
};
done?: boolean;
error?: string;
raw?: unknown;
}

View File

@ -1,6 +1,6 @@
"use strict"; "use strict";
import type { TaskData } from "./task_context_interface.js"; import type { TaskData } from "@triliumnext/commons";
import ws from "./ws.js"; import ws from "./ws.js";
// taskId => TaskContext // taskId => TaskContext
@ -61,7 +61,7 @@ class TaskContext {
taskId: this.taskId, taskId: this.taskId,
taskType: this.taskType, taskType: this.taskType,
data: this.data, data: this.data,
message: message message
}); });
} }
@ -71,7 +71,7 @@ class TaskContext {
taskId: this.taskId, taskId: this.taskId,
taskType: this.taskType, taskType: this.taskType,
data: this.data, data: this.data,
result: result result
}); });
} }
} }

View File

@ -1,7 +0,0 @@
export interface TaskData {
safeImport?: boolean;
textImportedAsText?: boolean;
codeImportedAsCode?: boolean;
shrinkImages?: boolean;
replaceUnderscoresWithSpaces?: boolean;
}

View File

@ -1,5 +1,5 @@
import { WebSocketServer as WebSocketServer, WebSocket } from "ws"; import { WebSocketServer as WebSocketServer, WebSocket } from "ws";
import { isDev, isElectron, randomString } from "./utils.js"; import { isElectron, randomString } from "./utils.js";
import log from "./log.js"; import log from "./log.js";
import sql from "./sql.js"; import sql from "./sql.js";
import cls from "./cls.js"; import cls from "./cls.js";
@ -15,52 +15,6 @@ import { WebSocketMessage, type EntityChange } from "@triliumnext/commons";
let webSocketServer!: WebSocketServer; let webSocketServer!: WebSocketServer;
let lastSyncedPush: number | null = null; let lastSyncedPush: number | null = null;
interface Message {
type: string;
data?: {
lastSyncedPush?: number | null;
entityChanges?: any[];
shrinkImages?: boolean;
} | null;
lastSyncedPush?: number | null;
progressCount?: number;
taskId?: string;
taskType?: string | null;
message?: string;
reason?: string;
result?: string | Record<string, string | undefined>;
script?: string;
params?: any[];
noteId?: string;
messages?: string[];
startNoteId?: string;
currentNoteId?: string;
entityType?: string;
entityId?: string;
originEntityName?: "notes";
originEntityId?: string | null;
lastModifiedMs?: number;
filePath?: string;
// LLM streaming specific fields
chatNoteId?: string;
content?: string;
thinking?: string;
toolExecution?: {
action?: string;
tool?: string;
toolCallId?: string;
result?: string | Record<string, any>;
error?: string;
args?: Record<string, unknown>;
};
done?: boolean;
error?: string;
raw?: unknown;
}
type SessionParser = (req: IncomingMessage, params: {}, cb: () => void) => void; type SessionParser = (req: IncomingMessage, params: {}, cb: () => void) => void;
function init(httpServer: HttpServer, sessionParser: SessionParser) { function init(httpServer: HttpServer, sessionParser: SessionParser) {
webSocketServer = new WebSocketServer({ webSocketServer = new WebSocketServer({
@ -106,7 +60,7 @@ Stack: ${message.stack}`);
}); });
} }
function sendMessage(client: WebSocket, message: Message) { function sendMessage(client: WebSocket, message: WebSocketMessage) {
const jsonStr = JSON.stringify(message); const jsonStr = JSON.stringify(message);
if (client.readyState === WebSocket.OPEN) { if (client.readyState === WebSocket.OPEN) {
@ -114,7 +68,7 @@ function sendMessage(client: WebSocket, message: Message) {
} }
} }
function sendMessageToAllClients(message: Message) { function sendMessageToAllClients(message: WebSocketMessage) {
const jsonStr = JSON.stringify(message); const jsonStr = JSON.stringify(message);
if (webSocketServer) { if (webSocketServer) {

View File

@ -270,3 +270,96 @@ export interface EntityChangeRecord {
entityChange: EntityChange; entityChange: EntityChange;
entity?: EntityRow; entity?: EntityRow;
} }
type TaskStatus<TypeT, DataT> = {
type: "taskProgressCount",
taskId: string;
taskType: TypeT;
data: DataT,
progressCount: number
} | {
type: "taskError",
taskId: string;
taskType: TypeT;
data: DataT;
message: string;
} | {
type: "taskSucceeded",
taskId: string;
taskType: TypeT;
data: DataT;
result?: string | Record<string, string | undefined>
}
type TaskDefinitions =
TaskStatus<"protectNotes", { protect: boolean; }>
| TaskStatus<"importNotes", null>
| TaskStatus<"importAttachments", null>
| TaskStatus<"deleteNotes", null>
| TaskStatus<"undeleteNotes", null>
| TaskStatus<"export", null>
;
export interface OpenedFileUpdateStatus {
entityType: string;
entityId: string;
lastModifiedMs?: number;
filePath: string;
}
export type WebSocketMessage = TaskDefinitions | {
type: "ping"
} | {
type: "frontend-update",
data: {
lastSyncedPush: number,
entityChanges: EntityChange[]
}
} | {
type: "openNote",
noteId: string
} | OpenedFileUpdateStatus & {
type: "openedFileUpdated"
} | {
type: "protectedSessionLogin"
} | {
type: "protectedSessionLogout"
} | {
type: "toast",
message: string;
} | {
type: "api-log-messages",
noteId: string,
messages: string[]
} | {
type: "execute-script";
script: string;
params: unknown[];
startNoteId?: string;
currentNoteId: string;
originEntityName: string;
originEntityId?: string | null;
} | {
type: "reload-frontend";
reason: string;
} | {
type: "sync-pull-in-progress" | "sync-push-in-progress" | "sync-finished" | "sync-failed";
lastSyncedPush: number;
} | {
type: "consistency-checks-failed"
} | {
type: "llm-stream",
chatNoteId: string;
done?: boolean;
error?: string;
thinking?: string;
content?: string;
toolExecution?: {
action?: string;
tool?: string;
toolCallId?: string;
result?: string | Record<string, any>;
error?: string;
args?: Record<string, unknown>;
}
}