mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-08-12 20:02:28 +08:00
decouple sessions even further
i think this is a better idea?
This commit is contained in:
parent
06924aad59
commit
cdc84f1cef
@ -45,10 +45,10 @@ interface SessionOptions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/sessions:
|
* /api/llm/chat:
|
||||||
* post:
|
* post:
|
||||||
* summary: Create a new LLM chat session
|
* summary: Create a new LLM chat
|
||||||
* operationId: llm-create-session
|
* operationId: llm-create-chat
|
||||||
* requestBody:
|
* requestBody:
|
||||||
* required: true
|
* required: true
|
||||||
* content:
|
* content:
|
||||||
@ -58,7 +58,7 @@ interface SessionOptions {
|
|||||||
* properties:
|
* properties:
|
||||||
* title:
|
* title:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Title for the chat session
|
* description: Title for the chat
|
||||||
* systemPrompt:
|
* systemPrompt:
|
||||||
* type: string
|
* type: string
|
||||||
* description: System message to set the behavior of the assistant
|
* description: System message to set the behavior of the assistant
|
||||||
@ -76,16 +76,16 @@ interface SessionOptions {
|
|||||||
* description: LLM provider to use (e.g., 'openai', 'anthropic', 'ollama')
|
* description: LLM provider to use (e.g., 'openai', 'anthropic', 'ollama')
|
||||||
* contextNoteId:
|
* contextNoteId:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Note ID to use as context for the session
|
* description: Note ID to use as context for the chat
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: Successfully created session
|
* description: Successfully created chat
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* type: object
|
* type: object
|
||||||
* properties:
|
* properties:
|
||||||
* sessionId:
|
* chatNoteId:
|
||||||
* type: string
|
* type: string
|
||||||
* title:
|
* title:
|
||||||
* type: string
|
* type: string
|
||||||
@ -96,25 +96,25 @@ interface SessionOptions {
|
|||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function createSession(req: Request, res: Response) {
|
async function createChat(req: Request, res: Response) {
|
||||||
return restChatService.createSession(req, res);
|
return restChatService.createSession(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/sessions/{sessionId}:
|
* /api/llm/chat/{chatNoteId}:
|
||||||
* get:
|
* get:
|
||||||
* summary: Retrieve a specific chat session by ID
|
* summary: Retrieve a specific chat by ID
|
||||||
* operationId: llm-get-session
|
* operationId: llm-get-chat
|
||||||
* parameters:
|
* parameters:
|
||||||
* - name: sessionId
|
* - name: chatNoteId
|
||||||
* in: path
|
* in: path
|
||||||
* required: true
|
* required: true
|
||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: Chat session details
|
* description: Chat details
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
@ -144,23 +144,23 @@ async function createSession(req: Request, res: Response) {
|
|||||||
* type: string
|
* type: string
|
||||||
* format: date-time
|
* format: date-time
|
||||||
* '404':
|
* '404':
|
||||||
* description: Session not found
|
* description: Chat not found
|
||||||
* security:
|
* security:
|
||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function getSession(req: Request, res: Response) {
|
async function getChat(req: Request, res: Response) {
|
||||||
return restChatService.getSession(req, res);
|
return restChatService.getSession(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/sessions/{sessionId}:
|
* /api/llm/chat/{chatNoteId}:
|
||||||
* patch:
|
* patch:
|
||||||
* summary: Update a chat session's settings
|
* summary: Update a chat's settings
|
||||||
* operationId: llm-update-session
|
* operationId: llm-update-chat
|
||||||
* parameters:
|
* parameters:
|
||||||
* - name: sessionId
|
* - name: chatNoteId
|
||||||
* in: path
|
* in: path
|
||||||
* required: true
|
* required: true
|
||||||
* schema:
|
* schema:
|
||||||
@ -174,7 +174,7 @@ async function getSession(req: Request, res: Response) {
|
|||||||
* properties:
|
* properties:
|
||||||
* title:
|
* title:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Updated title for the session
|
* description: Updated title for the chat
|
||||||
* systemPrompt:
|
* systemPrompt:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Updated system prompt
|
* description: Updated system prompt
|
||||||
@ -195,7 +195,7 @@ async function getSession(req: Request, res: Response) {
|
|||||||
* description: Updated note ID for context
|
* description: Updated note ID for context
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: Session successfully updated
|
* description: Chat successfully updated
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
@ -209,46 +209,46 @@ async function getSession(req: Request, res: Response) {
|
|||||||
* type: string
|
* type: string
|
||||||
* format: date-time
|
* format: date-time
|
||||||
* '404':
|
* '404':
|
||||||
* description: Session not found
|
* description: Chat not found
|
||||||
* security:
|
* security:
|
||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function updateSession(req: Request, res: Response) {
|
async function updateChat(req: Request, res: Response) {
|
||||||
// Get the session using ChatService
|
// Get the chat using ChatService
|
||||||
const sessionId = req.params.sessionId;
|
const chatNoteId = req.params.chatNoteId;
|
||||||
const updates = req.body;
|
const updates = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the session
|
// Get the chat
|
||||||
const session = await chatService.getOrCreateSession(sessionId);
|
const session = await chatService.getOrCreateSession(chatNoteId);
|
||||||
|
|
||||||
// Update title if provided
|
// Update title if provided
|
||||||
if (updates.title) {
|
if (updates.title) {
|
||||||
await chatStorageService.updateChat(sessionId, session.messages, updates.title);
|
await chatStorageService.updateChat(chatNoteId, session.messages, updates.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the updated session
|
// Return the updated chat
|
||||||
return {
|
return {
|
||||||
id: sessionId,
|
id: chatNoteId,
|
||||||
title: updates.title || session.title,
|
title: updates.title || session.title,
|
||||||
updatedAt: new Date()
|
updatedAt: new Date()
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error updating session: ${error}`);
|
log.error(`Error updating chat: ${error}`);
|
||||||
throw new Error(`Failed to update session: ${error}`);
|
throw new Error(`Failed to update chat: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/sessions:
|
* /api/llm/chat:
|
||||||
* get:
|
* get:
|
||||||
* summary: List all chat sessions
|
* summary: List all chats
|
||||||
* operationId: llm-list-sessions
|
* operationId: llm-list-chats
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: List of chat sessions
|
* description: List of chats
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
@ -272,14 +272,14 @@ async function updateSession(req: Request, res: Response) {
|
|||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function listSessions(req: Request, res: Response) {
|
async function listChats(req: Request, res: Response) {
|
||||||
// Get all sessions using ChatService
|
// Get all chats using ChatService
|
||||||
try {
|
try {
|
||||||
const sessions = await chatService.getAllSessions();
|
const sessions = await chatService.getAllSessions();
|
||||||
|
|
||||||
// Format the response
|
// Format the response
|
||||||
return {
|
return {
|
||||||
sessions: sessions.map(session => ({
|
chats: sessions.map(session => ({
|
||||||
id: session.id,
|
id: session.id,
|
||||||
title: session.title,
|
title: session.title,
|
||||||
createdAt: new Date(), // Since we don't have this in chat sessions
|
createdAt: new Date(), // Since we don't have this in chat sessions
|
||||||
@ -288,44 +288,44 @@ async function listSessions(req: Request, res: Response) {
|
|||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error listing sessions: ${error}`);
|
log.error(`Error listing chats: ${error}`);
|
||||||
throw new Error(`Failed to list sessions: ${error}`);
|
throw new Error(`Failed to list chats: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/sessions/{sessionId}:
|
* /api/llm/chat/{chatNoteId}:
|
||||||
* delete:
|
* delete:
|
||||||
* summary: Delete a chat session
|
* summary: Delete a chat
|
||||||
* operationId: llm-delete-session
|
* operationId: llm-delete-chat
|
||||||
* parameters:
|
* parameters:
|
||||||
* - name: sessionId
|
* - name: chatNoteId
|
||||||
* in: path
|
* in: path
|
||||||
* required: true
|
* required: true
|
||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: Session successfully deleted
|
* description: Chat successfully deleted
|
||||||
* '404':
|
* '404':
|
||||||
* description: Session not found
|
* description: Chat not found
|
||||||
* security:
|
* security:
|
||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function deleteSession(req: Request, res: Response) {
|
async function deleteChat(req: Request, res: Response) {
|
||||||
return restChatService.deleteSession(req, res);
|
return restChatService.deleteSession(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/sessions/{sessionId}/messages:
|
* /api/llm/chat/{chatNoteId}/messages:
|
||||||
* post:
|
* post:
|
||||||
* summary: Send a message to an LLM and get a response
|
* summary: Send a message to an LLM and get a response
|
||||||
* operationId: llm-send-message
|
* operationId: llm-send-message
|
||||||
* parameters:
|
* parameters:
|
||||||
* - name: sessionId
|
* - name: chatNoteId
|
||||||
* in: path
|
* in: path
|
||||||
* required: true
|
* required: true
|
||||||
* schema:
|
* schema:
|
||||||
@ -357,7 +357,7 @@ async function deleteSession(req: Request, res: Response) {
|
|||||||
* description: Whether to include relevant notes as context
|
* description: Whether to include relevant notes as context
|
||||||
* useNoteContext:
|
* useNoteContext:
|
||||||
* type: boolean
|
* type: boolean
|
||||||
* description: Whether to use the session's context note
|
* description: Whether to use the chat's context note
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: LLM response
|
* description: LLM response
|
||||||
@ -379,10 +379,10 @@ async function deleteSession(req: Request, res: Response) {
|
|||||||
* type: string
|
* type: string
|
||||||
* similarity:
|
* similarity:
|
||||||
* type: number
|
* type: number
|
||||||
* sessionId:
|
* chatNoteId:
|
||||||
* type: string
|
* type: string
|
||||||
* '404':
|
* '404':
|
||||||
* description: Session not found
|
* description: Chat not found
|
||||||
* '500':
|
* '500':
|
||||||
* description: Error processing request
|
* description: Error processing request
|
||||||
* security:
|
* security:
|
||||||
@ -393,6 +393,175 @@ async function sendMessage(req: Request, res: Response) {
|
|||||||
return restChatService.handleSendMessage(req, res);
|
return restChatService.handleSendMessage(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/llm/chat/{chatNoteId}/messages/stream:
|
||||||
|
* post:
|
||||||
|
* summary: Start a streaming response via WebSockets
|
||||||
|
* operationId: llm-stream-message
|
||||||
|
* parameters:
|
||||||
|
* - name: chatNoteId
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* content:
|
||||||
|
* type: string
|
||||||
|
* description: The user message to send to the LLM
|
||||||
|
* useAdvancedContext:
|
||||||
|
* type: boolean
|
||||||
|
* description: Whether to use advanced context extraction
|
||||||
|
* showThinking:
|
||||||
|
* type: boolean
|
||||||
|
* description: Whether to show thinking process in the response
|
||||||
|
* responses:
|
||||||
|
* '200':
|
||||||
|
* description: Streaming started successfully
|
||||||
|
* '404':
|
||||||
|
* description: Chat not found
|
||||||
|
* '500':
|
||||||
|
* description: Error processing request
|
||||||
|
* security:
|
||||||
|
* - session: []
|
||||||
|
* tags: ["llm"]
|
||||||
|
*/
|
||||||
|
async function streamMessage(req: Request, res: Response) {
|
||||||
|
log.info("=== Starting streamMessage ===");
|
||||||
|
try {
|
||||||
|
const chatNoteId = req.params.chatNoteId;
|
||||||
|
const { content, useAdvancedContext, showThinking } = req.body;
|
||||||
|
|
||||||
|
if (!content || typeof content !== 'string' || content.trim().length === 0) {
|
||||||
|
throw new Error('Content cannot be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if session exists in memory
|
||||||
|
let session = restChatService.getSessions().get(chatNoteId);
|
||||||
|
|
||||||
|
// If session doesn't exist in memory, try to create it from the Chat Note
|
||||||
|
if (!session) {
|
||||||
|
log.info(`Session not found in memory for Chat Note ${chatNoteId}, attempting to create from Chat Note`);
|
||||||
|
|
||||||
|
const restoredSession = await restChatService.createSessionFromChatNote(chatNoteId);
|
||||||
|
|
||||||
|
if (!restoredSession) {
|
||||||
|
// If we can't find the Chat Note, then it's truly not found
|
||||||
|
log.error(`Chat Note ${chatNoteId} not found, cannot create session`);
|
||||||
|
throw new Error('Chat Note not found, cannot create session for streaming');
|
||||||
|
}
|
||||||
|
|
||||||
|
session = restoredSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last active timestamp
|
||||||
|
session.lastActive = new Date();
|
||||||
|
|
||||||
|
// Add user message to the session
|
||||||
|
session.messages.push({
|
||||||
|
role: 'user',
|
||||||
|
content,
|
||||||
|
timestamp: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create request parameters for the pipeline
|
||||||
|
const requestParams = {
|
||||||
|
chatNoteId,
|
||||||
|
content,
|
||||||
|
useAdvancedContext: useAdvancedContext === true,
|
||||||
|
showThinking: showThinking === true,
|
||||||
|
stream: true // Always stream for this endpoint
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a fake request/response pair to pass to the handler
|
||||||
|
const fakeReq = {
|
||||||
|
...req,
|
||||||
|
method: 'GET', // Set to GET to indicate streaming
|
||||||
|
query: {
|
||||||
|
stream: 'true', // Set stream param - don't use format: 'stream' to avoid confusion
|
||||||
|
useAdvancedContext: String(useAdvancedContext === true),
|
||||||
|
showThinking: String(showThinking === true)
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
chatNoteId
|
||||||
|
},
|
||||||
|
// Make sure the original content is available to the handler
|
||||||
|
body: {
|
||||||
|
content,
|
||||||
|
useAdvancedContext: useAdvancedContext === true,
|
||||||
|
showThinking: showThinking === true
|
||||||
|
}
|
||||||
|
} as unknown as Request;
|
||||||
|
|
||||||
|
// Log to verify correct parameters
|
||||||
|
log.info(`WebSocket stream settings - useAdvancedContext=${useAdvancedContext === true}, in query=${fakeReq.query.useAdvancedContext}, in body=${fakeReq.body.useAdvancedContext}`);
|
||||||
|
// Extra safety to ensure the parameters are passed correctly
|
||||||
|
if (useAdvancedContext === true) {
|
||||||
|
log.info(`Enhanced context IS enabled for this request`);
|
||||||
|
} else {
|
||||||
|
log.info(`Enhanced context is NOT enabled for this request`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the request in the background
|
||||||
|
Promise.resolve().then(async () => {
|
||||||
|
try {
|
||||||
|
await restChatService.handleSendMessage(fakeReq, res);
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`Background message processing error: ${error}`);
|
||||||
|
|
||||||
|
// Import the WebSocket service
|
||||||
|
const wsService = (await import('../../services/ws.js')).default;
|
||||||
|
|
||||||
|
// Define LLMStreamMessage interface
|
||||||
|
interface LLMStreamMessage {
|
||||||
|
type: 'llm-stream';
|
||||||
|
sessionId: string; // Keep this as sessionId for WebSocket compatibility
|
||||||
|
content?: string;
|
||||||
|
thinking?: string;
|
||||||
|
toolExecution?: any;
|
||||||
|
done?: boolean;
|
||||||
|
error?: string;
|
||||||
|
raw?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send error to client via WebSocket
|
||||||
|
wsService.sendMessageToAllClients({
|
||||||
|
type: 'llm-stream',
|
||||||
|
sessionId: chatNoteId, // Use sessionId property, but pass the chatNoteId
|
||||||
|
error: `Error processing message: ${error}`,
|
||||||
|
done: true
|
||||||
|
} as LLMStreamMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Import the WebSocket service
|
||||||
|
const wsService = (await import('../../services/ws.js')).default;
|
||||||
|
|
||||||
|
// Let the client know streaming has started via WebSocket (helps client confirm connection is working)
|
||||||
|
wsService.sendMessageToAllClients({
|
||||||
|
type: 'llm-stream',
|
||||||
|
sessionId: chatNoteId,
|
||||||
|
thinking: 'Initializing streaming LLM response...'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Let the client know streaming has started via HTTP response
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Streaming started',
|
||||||
|
sessionId: chatNoteId // Keep using sessionId for API response compatibility
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
log.error(`Error starting message stream: ${error.message}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/indexes/stats:
|
* /api/llm/indexes/stats:
|
||||||
@ -788,182 +957,13 @@ async function indexNote(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/llm/sessions/{sessionId}/messages/stream:
|
|
||||||
* post:
|
|
||||||
* summary: Start a streaming response session via WebSockets
|
|
||||||
* operationId: llm-stream-message
|
|
||||||
* parameters:
|
|
||||||
* - name: sessionId
|
|
||||||
* in: path
|
|
||||||
* required: true
|
|
||||||
* schema:
|
|
||||||
* type: string
|
|
||||||
* requestBody:
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* content:
|
|
||||||
* type: string
|
|
||||||
* description: The user message to send to the LLM
|
|
||||||
* useAdvancedContext:
|
|
||||||
* type: boolean
|
|
||||||
* description: Whether to use advanced context extraction
|
|
||||||
* showThinking:
|
|
||||||
* type: boolean
|
|
||||||
* description: Whether to show thinking process in the response
|
|
||||||
* responses:
|
|
||||||
* '200':
|
|
||||||
* description: Streaming started successfully
|
|
||||||
* '404':
|
|
||||||
* description: Session not found
|
|
||||||
* '500':
|
|
||||||
* description: Error processing request
|
|
||||||
* security:
|
|
||||||
* - session: []
|
|
||||||
* tags: ["llm"]
|
|
||||||
*/
|
|
||||||
async function streamMessage(req: Request, res: Response) {
|
|
||||||
log.info("=== Starting streamMessage ===");
|
|
||||||
try {
|
|
||||||
const sessionId = req.params.sessionId;
|
|
||||||
const { content, useAdvancedContext, showThinking } = req.body;
|
|
||||||
|
|
||||||
if (!content || typeof content !== 'string' || content.trim().length === 0) {
|
|
||||||
throw new Error('Content cannot be empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if session exists in memory
|
|
||||||
let session = restChatService.getSessions().get(sessionId);
|
|
||||||
|
|
||||||
// If session doesn't exist in memory, try to recreate it from the Chat Note
|
|
||||||
if (!session) {
|
|
||||||
log.info(`Session ${sessionId} not found in memory, attempting to restore from Chat Note`);
|
|
||||||
|
|
||||||
const restoredSession = await restChatService.restoreSessionFromChatNote(sessionId);
|
|
||||||
|
|
||||||
if (!restoredSession) {
|
|
||||||
// If we can't find the Chat Note either, then it's truly not found
|
|
||||||
log.error(`Chat Note ${sessionId} not found, cannot restore session`);
|
|
||||||
throw new Error('Session not found and no corresponding Chat Note exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
session = restoredSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update last active timestamp
|
|
||||||
session.lastActive = new Date();
|
|
||||||
|
|
||||||
// Add user message to the session
|
|
||||||
session.messages.push({
|
|
||||||
role: 'user',
|
|
||||||
content,
|
|
||||||
timestamp: new Date()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create request parameters for the pipeline
|
|
||||||
const requestParams = {
|
|
||||||
sessionId,
|
|
||||||
content,
|
|
||||||
useAdvancedContext: useAdvancedContext === true,
|
|
||||||
showThinking: showThinking === true,
|
|
||||||
stream: true // Always stream for this endpoint
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a fake request/response pair to pass to the handler
|
|
||||||
const fakeReq = {
|
|
||||||
...req,
|
|
||||||
method: 'GET', // Set to GET to indicate streaming
|
|
||||||
query: {
|
|
||||||
stream: 'true', // Set stream param - don't use format: 'stream' to avoid confusion
|
|
||||||
useAdvancedContext: String(useAdvancedContext === true),
|
|
||||||
showThinking: String(showThinking === true)
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
sessionId
|
|
||||||
},
|
|
||||||
// Make sure the original content is available to the handler
|
|
||||||
body: {
|
|
||||||
content,
|
|
||||||
useAdvancedContext: useAdvancedContext === true,
|
|
||||||
showThinking: showThinking === true
|
|
||||||
}
|
|
||||||
} as unknown as Request;
|
|
||||||
|
|
||||||
// Log to verify correct parameters
|
|
||||||
log.info(`WebSocket stream settings - useAdvancedContext=${useAdvancedContext === true}, in query=${fakeReq.query.useAdvancedContext}, in body=${fakeReq.body.useAdvancedContext}`);
|
|
||||||
// Extra safety to ensure the parameters are passed correctly
|
|
||||||
if (useAdvancedContext === true) {
|
|
||||||
log.info(`Enhanced context IS enabled for this request`);
|
|
||||||
} else {
|
|
||||||
log.info(`Enhanced context is NOT enabled for this request`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the request in the background
|
|
||||||
Promise.resolve().then(async () => {
|
|
||||||
try {
|
|
||||||
await restChatService.handleSendMessage(fakeReq, res);
|
|
||||||
} catch (error) {
|
|
||||||
log.error(`Background message processing error: ${error}`);
|
|
||||||
|
|
||||||
// Import the WebSocket service
|
|
||||||
const wsService = (await import('../../services/ws.js')).default;
|
|
||||||
|
|
||||||
// Define LLMStreamMessage interface
|
|
||||||
interface LLMStreamMessage {
|
|
||||||
type: 'llm-stream';
|
|
||||||
sessionId: string;
|
|
||||||
content?: string;
|
|
||||||
thinking?: string;
|
|
||||||
toolExecution?: any;
|
|
||||||
done?: boolean;
|
|
||||||
error?: string;
|
|
||||||
raw?: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send error to client via WebSocket
|
|
||||||
wsService.sendMessageToAllClients({
|
|
||||||
type: 'llm-stream',
|
|
||||||
sessionId,
|
|
||||||
error: `Error processing message: ${error}`,
|
|
||||||
done: true
|
|
||||||
} as LLMStreamMessage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Import the WebSocket service
|
|
||||||
const wsService = (await import('../../services/ws.js')).default;
|
|
||||||
|
|
||||||
// Let the client know streaming has started via WebSocket (helps client confirm connection is working)
|
|
||||||
wsService.sendMessageToAllClients({
|
|
||||||
type: 'llm-stream',
|
|
||||||
sessionId,
|
|
||||||
thinking: 'Initializing streaming LLM response...'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Let the client know streaming has started via HTTP response
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: 'Streaming started',
|
|
||||||
sessionId
|
|
||||||
};
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`Error starting message stream: ${error.message}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// Chat session management
|
// Chat management
|
||||||
createSession,
|
createChat,
|
||||||
getSession,
|
getChat,
|
||||||
updateSession,
|
updateChat,
|
||||||
listSessions,
|
listChats,
|
||||||
deleteSession,
|
deleteChat,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
streamMessage,
|
streamMessage,
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ class RestChatService {
|
|||||||
log.info("=== Starting handleSendMessage ===");
|
log.info("=== Starting handleSendMessage ===");
|
||||||
try {
|
try {
|
||||||
// Extract parameters differently based on the request method
|
// Extract parameters differently based on the request method
|
||||||
let content, useAdvancedContext, showThinking, sessionId;
|
let content, useAdvancedContext, showThinking, chatNoteId;
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
// For POST requests, get content from the request body
|
// For POST requests, get content from the request body
|
||||||
@ -94,7 +94,7 @@ class RestChatService {
|
|||||||
showThinking = requestBody.showThinking || false;
|
showThinking = requestBody.showThinking || false;
|
||||||
|
|
||||||
// Add logging for POST requests
|
// Add logging for POST requests
|
||||||
log.info(`LLM POST message: sessionId=${req.params.sessionId}, useAdvancedContext=${useAdvancedContext}, showThinking=${showThinking}, contentLength=${content ? content.length : 0}`);
|
log.info(`LLM POST message: chatNoteId=${req.params.chatNoteId}, useAdvancedContext=${useAdvancedContext}, showThinking=${showThinking}, contentLength=${content ? content.length : 0}`);
|
||||||
} else if (req.method === 'GET') {
|
} else if (req.method === 'GET') {
|
||||||
// For GET (streaming) requests, get parameters from query params and body
|
// For GET (streaming) requests, get parameters from query params and body
|
||||||
// For streaming requests, we need the content from the body
|
// For streaming requests, we need the content from the body
|
||||||
@ -103,13 +103,13 @@ class RestChatService {
|
|||||||
content = req.body && req.body.content ? req.body.content : '';
|
content = req.body && req.body.content ? req.body.content : '';
|
||||||
|
|
||||||
// Add detailed logging for GET requests
|
// Add detailed logging for GET requests
|
||||||
log.info(`LLM GET stream: sessionId=${req.params.sessionId}, useAdvancedContext=${useAdvancedContext}, showThinking=${showThinking}`);
|
log.info(`LLM GET stream: chatNoteId=${req.params.chatNoteId}, useAdvancedContext=${useAdvancedContext}, showThinking=${showThinking}`);
|
||||||
log.info(`Parameters from query: useAdvancedContext=${req.query.useAdvancedContext}, showThinking=${req.query.showThinking}`);
|
log.info(`Parameters from query: useAdvancedContext=${req.query.useAdvancedContext}, showThinking=${req.query.showThinking}`);
|
||||||
log.info(`Parameters from body: useAdvancedContext=${req.body?.useAdvancedContext}, showThinking=${req.body?.showThinking}, content=${content ? `${content.substring(0, 20)}...` : 'none'}`);
|
log.info(`Parameters from body: useAdvancedContext=${req.body?.useAdvancedContext}, showThinking=${req.body?.showThinking}, content=${content ? `${content.substring(0, 20)}...` : 'none'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get sessionId from URL params since it's part of the route
|
// Get chatNoteId from URL params since it's part of the route
|
||||||
sessionId = req.params.sessionId;
|
chatNoteId = req.params.chatNoteId || req.params.sessionId; // Support both names for backward compatibility
|
||||||
|
|
||||||
// For GET requests, ensure we have the stream parameter
|
// For GET requests, ensure we have the stream parameter
|
||||||
if (req.method === 'GET' && req.query.stream !== 'true') {
|
if (req.method === 'GET' && req.query.stream !== 'true') {
|
||||||
@ -121,46 +121,48 @@ class RestChatService {
|
|||||||
throw new Error('Content cannot be empty');
|
throw new Error('Content cannot be empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if session exists, create one if not
|
// Get or create session from Chat Note
|
||||||
let session = SessionsStore.getSession(sessionId);
|
let session = await this.getOrCreateSessionFromChatNote(chatNoteId, req.method === 'POST');
|
||||||
|
|
||||||
|
// If no session found and we're not allowed to create one (GET request)
|
||||||
|
if (!session && req.method === 'GET') {
|
||||||
|
throw new Error('Chat Note not found, cannot create session for streaming');
|
||||||
|
}
|
||||||
|
|
||||||
|
// For POST requests, if no Chat Note exists, create a new one
|
||||||
|
if (!session && req.method === 'POST') {
|
||||||
|
log.info(`No Chat Note found for ${chatNoteId}, creating a new Chat Note and session`);
|
||||||
|
|
||||||
|
// Create a new Chat Note via the storage service
|
||||||
|
const chatStorageService = (await import('../../llm/chat_storage_service.js')).default;
|
||||||
|
const newChat = await chatStorageService.createChat('New Chat');
|
||||||
|
|
||||||
|
// Use the new Chat Note's ID for the session
|
||||||
|
session = SessionsStore.createSession({
|
||||||
|
title: newChat.title
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the session ID to match the Chat Note ID
|
||||||
|
session.id = newChat.id;
|
||||||
|
|
||||||
|
log.info(`Created new Chat Note and session with ID: ${session.id}`);
|
||||||
|
|
||||||
|
// Update the parameter to use the new ID
|
||||||
|
chatNoteId = session.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, session should never be null
|
||||||
|
// TypeScript doesn't know this, so we'll add a check
|
||||||
if (!session) {
|
if (!session) {
|
||||||
if (req.method === 'GET') {
|
// This should never happen due to our logic above
|
||||||
// For GET requests, we should try to restore from Chat Note
|
throw new Error('Failed to create or retrieve session');
|
||||||
log.info(`Session ${sessionId} not found in memory for GET request, attempting to restore from Chat Note`);
|
|
||||||
|
|
||||||
const restoredSession = await this.restoreSessionFromChatNote(sessionId);
|
|
||||||
|
|
||||||
if (!restoredSession) {
|
|
||||||
// If we still can't find the Chat Note, throw error
|
|
||||||
throw new Error('Session not found and no corresponding Chat Note exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
session = restoredSession;
|
|
||||||
} else {
|
|
||||||
// For POST requests, we can create a new session, or try to restore from Chat Note
|
|
||||||
log.info(`Session ${sessionId} not found for POST request, checking if Chat Note exists`);
|
|
||||||
|
|
||||||
const restoredSession = await this.restoreSessionFromChatNote(sessionId);
|
|
||||||
|
|
||||||
if (!restoredSession) {
|
|
||||||
// If we can't find a Chat Note, create a new session
|
|
||||||
log.info(`No Chat Note found for ${sessionId}, creating a new session`);
|
|
||||||
session = SessionsStore.createSession({
|
|
||||||
title: 'Auto-created Session'
|
|
||||||
});
|
|
||||||
log.info(`Created new session with ID: ${session.id}`);
|
|
||||||
} else {
|
|
||||||
session = restoredSession;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update session last active timestamp
|
// Update session last active timestamp
|
||||||
SessionsStore.touchSession(session.id);
|
SessionsStore.touchSession(session.id);
|
||||||
|
|
||||||
// For POST requests, store the user message
|
// For POST requests, store the user message
|
||||||
if (req.method === 'POST' && content) {
|
if (req.method === 'POST' && content && session) {
|
||||||
// Add message to session
|
// Add message to session
|
||||||
session.messages.push({
|
session.messages.push({
|
||||||
role: 'user',
|
role: 'user',
|
||||||
@ -261,15 +263,15 @@ class RestChatService {
|
|||||||
const pipelineOptions = {
|
const pipelineOptions = {
|
||||||
// Force useAdvancedContext to be a boolean, no matter what
|
// Force useAdvancedContext to be a boolean, no matter what
|
||||||
useAdvancedContext: useAdvancedContext === true,
|
useAdvancedContext: useAdvancedContext === true,
|
||||||
systemPrompt: session.messages.find(m => m.role === 'system')?.content,
|
systemPrompt: session?.messages.find(m => m.role === 'system')?.content,
|
||||||
temperature: session.metadata.temperature,
|
temperature: session?.metadata.temperature,
|
||||||
maxTokens: session.metadata.maxTokens,
|
maxTokens: session?.metadata.maxTokens,
|
||||||
model: session.metadata.model,
|
model: session?.metadata.model,
|
||||||
// Set stream based on request type, but ensure it's explicitly a boolean value
|
// Set stream based on request type, but ensure it's explicitly a boolean value
|
||||||
// GET requests or format=stream parameter indicates streaming should be used
|
// GET requests or format=stream parameter indicates streaming should be used
|
||||||
stream: !!(req.method === 'GET' || req.query.format === 'stream' || req.query.stream === 'true'),
|
stream: !!(req.method === 'GET' || req.query.format === 'stream' || req.query.stream === 'true'),
|
||||||
// Include sessionId for tracking tool executions
|
// Include chatNoteId for tracking tool executions
|
||||||
sessionId: sessionId
|
sessionId: chatNoteId // Use sessionId property for backward compatibility
|
||||||
};
|
};
|
||||||
|
|
||||||
// Log the options to verify what's being sent to the pipeline
|
// Log the options to verify what's being sent to the pipeline
|
||||||
@ -300,7 +302,7 @@ class RestChatService {
|
|||||||
// Use WebSocket service to send messages
|
// Use WebSocket service to send messages
|
||||||
this.handleStreamCallback(
|
this.handleStreamCallback(
|
||||||
data, done, rawChunk,
|
data, done, rawChunk,
|
||||||
wsService.default, sessionId,
|
wsService.default, chatNoteId,
|
||||||
messageContent, session, res
|
messageContent, session, res
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -310,7 +312,7 @@ class RestChatService {
|
|||||||
try {
|
try {
|
||||||
wsService.default.sendMessageToAllClients({
|
wsService.default.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
sessionId: chatNoteId, // Use sessionId property for backward compatibility
|
||||||
error: `Stream error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
error: `Stream error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||||
done: true
|
done: true
|
||||||
});
|
});
|
||||||
@ -379,7 +381,7 @@ class RestChatService {
|
|||||||
done: boolean,
|
done: boolean,
|
||||||
rawChunk: any,
|
rawChunk: any,
|
||||||
wsService: any,
|
wsService: any,
|
||||||
sessionId: string,
|
chatNoteId: string,
|
||||||
messageContent: string,
|
messageContent: string,
|
||||||
session: any,
|
session: any,
|
||||||
res: Response
|
res: Response
|
||||||
@ -392,7 +394,7 @@ class RestChatService {
|
|||||||
// Create a message object with all necessary fields
|
// Create a message object with all necessary fields
|
||||||
const message: LLMStreamMessage = {
|
const message: LLMStreamMessage = {
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId
|
sessionId: chatNoteId // Use sessionId property for backward compatibility
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add content if available - either the new chunk or full content on completion
|
// Add content if available - either the new chunk or full content on completion
|
||||||
@ -445,7 +447,7 @@ class RestChatService {
|
|||||||
// Log what was sent (first message and completion)
|
// Log what was sent (first message and completion)
|
||||||
if (message.thinking || done) {
|
if (message.thinking || done) {
|
||||||
log.info(
|
log.info(
|
||||||
`[WS-SERVER] Sending LLM stream message: sessionId=${sessionId}, content=${!!message.content}, contentLength=${message.content?.length || 0}, thinking=${!!message.thinking}, toolExecution=${!!message.toolExecution}, done=${done}`
|
`[WS-SERVER] Sending LLM stream message: chatNoteId=${chatNoteId}, content=${!!message.content}, contentLength=${message.content?.length || 0}, thinking=${!!message.thinking}, toolExecution=${!!message.toolExecution}, done=${done}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -577,25 +579,25 @@ class RestChatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore a session from a Chat Note
|
* Create an in-memory session from a Chat Note
|
||||||
* This is used when a session doesn't exist in memory but there's a corresponding Chat Note
|
* This treats the Chat Note as the source of truth, using its ID as the session ID
|
||||||
*/
|
*/
|
||||||
async restoreSessionFromChatNote(sessionId: string): Promise<ChatSession | null> {
|
async createSessionFromChatNote(noteId: string): Promise<ChatSession | null> {
|
||||||
try {
|
try {
|
||||||
log.info(`Attempting to restore session ${sessionId} from Chat Note`);
|
log.info(`Creating in-memory session for Chat Note ID ${noteId}`);
|
||||||
|
|
||||||
// Import chat storage service
|
// Import chat storage service
|
||||||
const chatStorageService = (await import('../../llm/chat_storage_service.js')).default;
|
const chatStorageService = (await import('../../llm/chat_storage_service.js')).default;
|
||||||
|
|
||||||
// Try to get the Chat Note data
|
// Try to get the Chat Note data
|
||||||
const chatNote = await chatStorageService.getChat(sessionId);
|
const chatNote = await chatStorageService.getChat(noteId);
|
||||||
|
|
||||||
if (!chatNote) {
|
if (!chatNote) {
|
||||||
log.error(`Chat Note ${sessionId} not found, cannot restore session`);
|
log.error(`Chat Note ${noteId} not found, cannot create session`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(`Found Chat Note ${sessionId}, recreating session from it`);
|
log.info(`Found Chat Note ${noteId}, creating in-memory session`);
|
||||||
|
|
||||||
// Convert Message[] to ChatMessage[] by ensuring the role is compatible
|
// Convert Message[] to ChatMessage[] by ensuring the role is compatible
|
||||||
const chatMessages: ChatMessage[] = chatNote.messages.map(msg => ({
|
const chatMessages: ChatMessage[] = chatNote.messages.map(msg => ({
|
||||||
@ -606,7 +608,7 @@ class RestChatService {
|
|||||||
|
|
||||||
// Create a new session with the same ID as the Chat Note
|
// Create a new session with the same ID as the Chat Note
|
||||||
const session: ChatSession = {
|
const session: ChatSession = {
|
||||||
id: chatNote.id,
|
id: chatNote.id, // Use Chat Note ID as the session ID
|
||||||
title: chatNote.title,
|
title: chatNote.title,
|
||||||
messages: chatMessages,
|
messages: chatMessages,
|
||||||
createdAt: chatNote.createdAt || new Date(),
|
createdAt: chatNote.createdAt || new Date(),
|
||||||
@ -615,15 +617,41 @@ class RestChatService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add the session to the in-memory store
|
// Add the session to the in-memory store
|
||||||
SessionsStore.getAllSessions().set(sessionId, session);
|
SessionsStore.getAllSessions().set(noteId, session);
|
||||||
|
|
||||||
log.info(`Successfully restored session ${sessionId} from Chat Note`);
|
log.info(`Successfully created in-memory session for Chat Note ${noteId}`);
|
||||||
return session;
|
return session;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Failed to restore session from Chat Note: ${error}`);
|
log.error(`Failed to create session from Chat Note: ${error}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an existing session or create a new one from a Chat Note
|
||||||
|
* This treats the Chat Note as the source of truth, using its ID as the session ID
|
||||||
|
*/
|
||||||
|
async getOrCreateSessionFromChatNote(noteId: string, createIfNotFound: boolean = true): Promise<ChatSession | null> {
|
||||||
|
// First check if we already have this session in memory
|
||||||
|
let session = SessionsStore.getSession(noteId);
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
log.info(`Found existing in-memory session for Chat Note ${noteId}`);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not in memory, try to create from Chat Note
|
||||||
|
log.info(`Session not found in memory for Chat Note ${noteId}, attempting to create it`);
|
||||||
|
|
||||||
|
// Only try to create if allowed
|
||||||
|
if (!createIfNotFound) {
|
||||||
|
log.info(`Not creating new session for ${noteId} as createIfNotFound=false`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create from Chat Note
|
||||||
|
return await this.createSessionFromChatNote(noteId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create singleton instance
|
// Create singleton instance
|
||||||
|
Loading…
x
Reference in New Issue
Block a user