mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-09-29 00:11:32 +08:00
closer to decoupling sessions...
douple sessions, again closer... uhhh closer.... closer...
This commit is contained in:
parent
2b14f73ad4
commit
5e1fc5625d
@ -7,10 +7,11 @@ import type { SessionResponse } from "./types.js";
|
|||||||
/**
|
/**
|
||||||
* Create a new chat session
|
* Create a new chat session
|
||||||
*/
|
*/
|
||||||
export async function createChatSession(): Promise<{chatNoteId: string | null, noteId: string | null}> {
|
export async function createChatSession(currentNoteId?: string): Promise<{chatNoteId: string | null, noteId: string | null}> {
|
||||||
try {
|
try {
|
||||||
const resp = await server.post<SessionResponse>('llm/chat', {
|
const resp = await server.post<SessionResponse>('llm/chat', {
|
||||||
title: 'Note Chat'
|
title: 'Note Chat',
|
||||||
|
currentNoteId: currentNoteId // Pass the current note ID if available
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resp && resp.id) {
|
if (resp && resp.id) {
|
||||||
@ -36,6 +37,13 @@ export async function createChatSession(): Promise<{chatNoteId: string | null, n
|
|||||||
*/
|
*/
|
||||||
export async function checkSessionExists(chatNoteId: string): Promise<boolean> {
|
export async function checkSessionExists(chatNoteId: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
|
// Validate that we have a proper note ID format, not a session ID
|
||||||
|
// Note IDs in Trilium are typically longer or in a different format
|
||||||
|
if (chatNoteId && chatNoteId.length === 16 && /^[A-Za-z0-9]+$/.test(chatNoteId)) {
|
||||||
|
console.warn(`Invalid note ID format detected: ${chatNoteId} appears to be a legacy session ID`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const sessionCheck = await server.getWithSilentNotFound<any>(`llm/chat/${chatNoteId}`);
|
const sessionCheck = await server.getWithSilentNotFound<any>(`llm/chat/${chatNoteId}`);
|
||||||
return !!(sessionCheck && sessionCheck.id);
|
return !!(sessionCheck && sessionCheck.id);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -56,6 +64,13 @@ export async function setupStreamingResponse(
|
|||||||
onComplete: () => void,
|
onComplete: () => void,
|
||||||
onError: (error: Error) => void
|
onError: (error: Error) => void
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
// Validate that we have a proper note ID format, not a session ID
|
||||||
|
if (chatNoteId && chatNoteId.length === 16 && /^[A-Za-z0-9]+$/.test(chatNoteId)) {
|
||||||
|
console.error(`Invalid note ID format: ${chatNoteId} appears to be a legacy session ID`);
|
||||||
|
onError(new Error("Invalid note ID format - using a legacy session ID"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let assistantResponse = '';
|
let assistantResponse = '';
|
||||||
let postToolResponse = ''; // Separate accumulator for post-tool execution content
|
let postToolResponse = ''; // Separate accumulator for post-tool execution content
|
||||||
@ -74,6 +89,32 @@ export async function setupStreamingResponse(
|
|||||||
const responseId = `llm-stream-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
const responseId = `llm-stream-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
||||||
console.log(`[${responseId}] Setting up WebSocket streaming for chat note ${chatNoteId}`);
|
console.log(`[${responseId}] Setting up WebSocket streaming for chat note ${chatNoteId}`);
|
||||||
|
|
||||||
|
// Send the initial request to initiate streaming
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const streamResponse = await server.post<any>(`llm/chat/${chatNoteId}/messages/stream`, {
|
||||||
|
content: messageParams.content,
|
||||||
|
includeContext: messageParams.useAdvancedContext,
|
||||||
|
options: {
|
||||||
|
temperature: 0.7,
|
||||||
|
maxTokens: 2000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!streamResponse || !streamResponse.success) {
|
||||||
|
console.error(`[${responseId}] Failed to initiate streaming`);
|
||||||
|
reject(new Error('Failed to initiate streaming'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[${responseId}] Streaming initiated successfully`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[${responseId}] Error initiating streaming:`, error);
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// Function to safely perform cleanup
|
// Function to safely perform cleanup
|
||||||
const performCleanup = () => {
|
const performCleanup = () => {
|
||||||
if (cleanupTimeoutId) {
|
if (cleanupTimeoutId) {
|
||||||
@ -116,8 +157,7 @@ export async function setupStreamingResponse(
|
|||||||
const message = customEvent.detail;
|
const message = customEvent.detail;
|
||||||
|
|
||||||
// Only process messages for our chat note
|
// Only process messages for our chat note
|
||||||
// Note: The WebSocket messages still use sessionId property for backward compatibility
|
if (!message || message.chatNoteId !== chatNoteId) {
|
||||||
if (!message || message.sessionId !== chatNoteId) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,31 +442,6 @@ export async function setupStreamingResponse(
|
|||||||
reject(new Error('WebSocket connection not established'));
|
reject(new Error('WebSocket connection not established'));
|
||||||
}
|
}
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
// Send the streaming request to start the process
|
|
||||||
console.log(`[${responseId}] Sending HTTP POST request to initiate streaming: /llm/chat/${chatNoteId}/messages/stream`);
|
|
||||||
server.post(`llm/chat/${chatNoteId}/messages/stream`, {
|
|
||||||
...messageParams,
|
|
||||||
stream: true // Explicitly indicate this is a streaming request
|
|
||||||
}).catch(err => {
|
|
||||||
console.error(`[${responseId}] HTTP error sending streaming request for chat note ${chatNoteId}:`, err);
|
|
||||||
|
|
||||||
// Clean up timeouts
|
|
||||||
if (initialTimeoutId !== null) {
|
|
||||||
window.clearTimeout(initialTimeoutId);
|
|
||||||
initialTimeoutId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeoutId !== null) {
|
|
||||||
window.clearTimeout(timeoutId);
|
|
||||||
timeoutId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up event listener
|
|
||||||
cleanupEventListener(eventListener);
|
|
||||||
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,6 +464,12 @@ function cleanupEventListener(listener: ((event: Event) => void) | null): void {
|
|||||||
*/
|
*/
|
||||||
export async function getDirectResponse(chatNoteId: string, messageParams: any): Promise<any> {
|
export async function getDirectResponse(chatNoteId: string, messageParams: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
|
// Validate that we have a proper note ID format, not a session ID
|
||||||
|
if (chatNoteId && chatNoteId.length === 16 && /^[A-Za-z0-9]+$/.test(chatNoteId)) {
|
||||||
|
console.error(`Invalid note ID format: ${chatNoteId} appears to be a legacy session ID`);
|
||||||
|
throw new Error("Invalid note ID format - using a legacy session ID");
|
||||||
|
}
|
||||||
|
|
||||||
const postResponse = await server.post<any>(`llm/chat/${chatNoteId}/messages`, {
|
const postResponse = await server.post<any>(`llm/chat/${chatNoteId}/messages`, {
|
||||||
message: messageParams.content,
|
message: messageParams.content,
|
||||||
includeContext: messageParams.useAdvancedContext,
|
includeContext: messageParams.useAdvancedContext,
|
||||||
|
@ -321,35 +321,13 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load Chat Note ID if available
|
// Load Chat Note ID if available
|
||||||
if (savedData.chatNoteId) {
|
if (savedData.noteId) {
|
||||||
console.log(`Setting Chat Note ID from saved data: ${savedData.chatNoteId}`);
|
console.log(`Using noteId as Chat Note ID: ${savedData.noteId}`);
|
||||||
this.chatNoteId = savedData.chatNoteId;
|
this.chatNoteId = savedData.noteId;
|
||||||
|
this.noteId = savedData.noteId;
|
||||||
// Set the noteId as well - this could be different from the chatNoteId
|
|
||||||
// If we have a separate noteId stored, use it, otherwise default to the chatNoteId
|
|
||||||
if (savedData.noteId) {
|
|
||||||
this.noteId = savedData.noteId;
|
|
||||||
console.log(`Using stored Chat Note ID: ${this.noteId}`);
|
|
||||||
} else {
|
|
||||||
// For compatibility with older data, use the chatNoteId as the noteId
|
|
||||||
this.noteId = savedData.chatNoteId;
|
|
||||||
console.log(`No Chat Note ID found, using Chat Note ID: ${this.chatNoteId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// No need to check if session exists on server since the Chat Note
|
|
||||||
// is now the source of truth - if we have the Note, we have the session
|
|
||||||
} else {
|
} else {
|
||||||
// For backward compatibility, try to get sessionId
|
console.log(`No noteId found in saved data, cannot load chat session`);
|
||||||
if ((savedData as any).sessionId) {
|
return false;
|
||||||
console.log(`Using legacy sessionId as Chat Note ID: ${(savedData as any).sessionId}`);
|
|
||||||
this.chatNoteId = (savedData as any).sessionId;
|
|
||||||
this.noteId = savedData.noteId || (savedData as any).sessionId;
|
|
||||||
} else {
|
|
||||||
// No saved Chat Note ID, create a new one
|
|
||||||
this.chatNoteId = null;
|
|
||||||
this.noteId = null;
|
|
||||||
await this.createChatSession();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -491,50 +469,30 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new chat session
|
||||||
|
*/
|
||||||
private async createChatSession() {
|
private async createChatSession() {
|
||||||
try {
|
try {
|
||||||
// Create a new Chat Note to represent this chat session
|
// Create a new chat session, passing the current note ID if it exists
|
||||||
// The function now returns both chatNoteId and noteId
|
const { chatNoteId, noteId } = await createChatSession(
|
||||||
const result = await createChatSession();
|
this.currentNoteId ? this.currentNoteId : undefined
|
||||||
|
);
|
||||||
|
|
||||||
if (!result.chatNoteId) {
|
if (chatNoteId) {
|
||||||
toastService.showError('Failed to create chat session');
|
// If we got back an ID from the API, use it
|
||||||
return;
|
this.chatNoteId = chatNoteId;
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Created new chat session with ID: ${result.chatNoteId}`);
|
// For new sessions, the noteId should equal the chatNoteId
|
||||||
this.chatNoteId = result.chatNoteId;
|
// This ensures we're using the note ID consistently
|
||||||
|
this.noteId = noteId || chatNoteId;
|
||||||
|
|
||||||
// If the API returned a noteId directly, use it
|
console.log(`Created new chat session with noteId: ${this.noteId}`);
|
||||||
if (result.noteId) {
|
|
||||||
this.noteId = result.noteId;
|
|
||||||
console.log(`Using noteId from API response: ${this.noteId}`);
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, try to get session details to find the noteId
|
throw new Error("Failed to create chat session - no ID returned");
|
||||||
try {
|
|
||||||
const sessionDetails = await server.get<any>(`llm/chat/${this.chatNoteId}`);
|
|
||||||
if (sessionDetails && sessionDetails.noteId) {
|
|
||||||
this.noteId = sessionDetails.noteId;
|
|
||||||
console.log(`Using noteId from session details: ${this.noteId}`);
|
|
||||||
} else {
|
|
||||||
// As a last resort, use the current note ID
|
|
||||||
console.warn(`No noteId found in session details, using parent note ID: ${this.currentNoteId}`);
|
|
||||||
this.noteId = this.currentNoteId;
|
|
||||||
}
|
|
||||||
} catch (detailsError) {
|
|
||||||
console.error('Could not fetch session details:', detailsError);
|
|
||||||
// Use current note ID as a fallback
|
|
||||||
this.noteId = this.currentNoteId;
|
|
||||||
console.warn(`Using current note ID as fallback: ${this.noteId}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the noteId is valid
|
// Save the note ID as the session identifier
|
||||||
if (this.noteId !== this.currentNoteId) {
|
|
||||||
console.log(`Note ID verification - session's noteId: ${this.noteId}, current note: ${this.currentNoteId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the session ID and data
|
|
||||||
await this.saveCurrentData();
|
await this.saveCurrentData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating chat session:', error);
|
console.error('Error creating chat session:', error);
|
||||||
@ -818,7 +776,7 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
similarity?: number;
|
similarity?: number;
|
||||||
content?: string;
|
content?: string;
|
||||||
}>;
|
}>;
|
||||||
}>(`llm/sessions/${this.chatNoteId}`)
|
}>(`llm/chat/${this.chatNoteId}`)
|
||||||
.then((sessionData) => {
|
.then((sessionData) => {
|
||||||
console.log("Got updated session data:", sessionData);
|
console.log("Got updated session data:", sessionData);
|
||||||
|
|
||||||
|
@ -15,40 +15,14 @@ interface ChatMessage {
|
|||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatSession {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
messages: ChatMessage[];
|
|
||||||
createdAt: Date;
|
|
||||||
lastActive: Date;
|
|
||||||
noteContext?: string; // Optional noteId that provides context
|
|
||||||
metadata: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NoteSource {
|
|
||||||
noteId: string;
|
|
||||||
title: string;
|
|
||||||
content?: string;
|
|
||||||
similarity?: number;
|
|
||||||
branchId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SessionOptions {
|
|
||||||
title?: string;
|
|
||||||
systemPrompt?: string;
|
|
||||||
temperature?: number;
|
|
||||||
maxTokens?: number;
|
|
||||||
model?: string;
|
|
||||||
provider?: string;
|
|
||||||
contextNoteId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/chat:
|
* /api/llm/sessions:
|
||||||
* post:
|
* post:
|
||||||
* summary: Create a new LLM chat
|
* summary: Create a new LLM chat session
|
||||||
* operationId: llm-create-chat
|
* operationId: llm-create-session
|
||||||
* requestBody:
|
* requestBody:
|
||||||
* required: true
|
* required: true
|
||||||
* content:
|
* content:
|
||||||
@ -58,7 +32,7 @@ interface SessionOptions {
|
|||||||
* properties:
|
* properties:
|
||||||
* title:
|
* title:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Title for the chat
|
* description: Title for the chat session
|
||||||
* 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 +50,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 chat
|
* description: Note ID to use as context for the session
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: Successfully created chat
|
* description: Successfully created session
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* type: object
|
* type: object
|
||||||
* properties:
|
* properties:
|
||||||
* chatNoteId:
|
* sessionId:
|
||||||
* type: string
|
* type: string
|
||||||
* title:
|
* title:
|
||||||
* type: string
|
* type: string
|
||||||
@ -96,25 +70,25 @@ interface SessionOptions {
|
|||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function createChat(req: Request, res: Response) {
|
async function createSession(req: Request, res: Response) {
|
||||||
return restChatService.createSession(req, res);
|
return restChatService.createSession(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/chat/{chatNoteId}:
|
* /api/llm/sessions/{sessionId}:
|
||||||
* get:
|
* get:
|
||||||
* summary: Retrieve a specific chat by ID
|
* summary: Retrieve a specific chat session
|
||||||
* operationId: llm-get-chat
|
* operationId: llm-get-session
|
||||||
* parameters:
|
* parameters:
|
||||||
* - name: chatNoteId
|
* - name: sessionId
|
||||||
* in: path
|
* in: path
|
||||||
* required: true
|
* required: true
|
||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: Chat details
|
* description: Chat session details
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
@ -144,12 +118,12 @@ async function createChat(req: Request, res: Response) {
|
|||||||
* type: string
|
* type: string
|
||||||
* format: date-time
|
* format: date-time
|
||||||
* '404':
|
* '404':
|
||||||
* description: Chat not found
|
* description: Session not found
|
||||||
* security:
|
* security:
|
||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function getChat(req: Request, res: Response) {
|
async function getSession(req: Request, res: Response) {
|
||||||
return restChatService.getSession(req, res);
|
return restChatService.getSession(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,6 +139,7 @@ async function getChat(req: Request, res: Response) {
|
|||||||
* required: true
|
* required: true
|
||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
|
* description: The ID of the chat note (formerly sessionId)
|
||||||
* requestBody:
|
* requestBody:
|
||||||
* required: true
|
* required: true
|
||||||
* content:
|
* content:
|
||||||
@ -174,7 +149,7 @@ async function getChat(req: Request, res: Response) {
|
|||||||
* properties:
|
* properties:
|
||||||
* title:
|
* title:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Updated title for the chat
|
* description: Updated title for the session
|
||||||
* systemPrompt:
|
* systemPrompt:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Updated system prompt
|
* description: Updated system prompt
|
||||||
@ -195,7 +170,7 @@ async function getChat(req: Request, res: Response) {
|
|||||||
* description: Updated note ID for context
|
* description: Updated note ID for context
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: Chat successfully updated
|
* description: Session successfully updated
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
@ -209,12 +184,12 @@ async function getChat(req: Request, res: Response) {
|
|||||||
* type: string
|
* type: string
|
||||||
* format: date-time
|
* format: date-time
|
||||||
* '404':
|
* '404':
|
||||||
* description: Chat not found
|
* description: Session not found
|
||||||
* security:
|
* security:
|
||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function updateChat(req: Request, res: Response) {
|
async function updateSession(req: Request, res: Response) {
|
||||||
// Get the chat using ChatService
|
// Get the chat using ChatService
|
||||||
const chatNoteId = req.params.chatNoteId;
|
const chatNoteId = req.params.chatNoteId;
|
||||||
const updates = req.body;
|
const updates = req.body;
|
||||||
@ -242,13 +217,13 @@ async function updateChat(req: Request, res: Response) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/chat:
|
* /api/llm/sessions:
|
||||||
* get:
|
* get:
|
||||||
* summary: List all chats
|
* summary: List all chat sessions
|
||||||
* operationId: llm-list-chats
|
* operationId: llm-list-sessions
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: List of chats
|
* description: List of chat sessions
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
@ -272,14 +247,14 @@ async function updateChat(req: Request, res: Response) {
|
|||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function listChats(req: Request, res: Response) {
|
async function listSessions(req: Request, res: Response) {
|
||||||
// Get all chats using ChatService
|
// Get all sessions using ChatService
|
||||||
try {
|
try {
|
||||||
const sessions = await chatService.getAllSessions();
|
const sessions = await chatService.getAllSessions();
|
||||||
|
|
||||||
// Format the response
|
// Format the response
|
||||||
return {
|
return {
|
||||||
chats: sessions.map(session => ({
|
sessions: 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,33 +263,33 @@ async function listChats(req: Request, res: Response) {
|
|||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error listing chats: ${error}`);
|
log.error(`Error listing sessions: ${error}`);
|
||||||
throw new Error(`Failed to list chats: ${error}`);
|
throw new Error(`Failed to list sessions: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/llm/chat/{chatNoteId}:
|
* /api/llm/sessions/{sessionId}:
|
||||||
* delete:
|
* delete:
|
||||||
* summary: Delete a chat
|
* summary: Delete a chat session
|
||||||
* operationId: llm-delete-chat
|
* operationId: llm-delete-session
|
||||||
* parameters:
|
* parameters:
|
||||||
* - name: chatNoteId
|
* - name: sessionId
|
||||||
* in: path
|
* in: path
|
||||||
* required: true
|
* required: true
|
||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: Chat successfully deleted
|
* description: Session successfully deleted
|
||||||
* '404':
|
* '404':
|
||||||
* description: Chat not found
|
* description: Session not found
|
||||||
* security:
|
* security:
|
||||||
* - session: []
|
* - session: []
|
||||||
* tags: ["llm"]
|
* tags: ["llm"]
|
||||||
*/
|
*/
|
||||||
async function deleteChat(req: Request, res: Response) {
|
async function deleteSession(req: Request, res: Response) {
|
||||||
return restChatService.deleteSession(req, res);
|
return restChatService.deleteSession(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,6 +305,7 @@ async function deleteChat(req: Request, res: Response) {
|
|||||||
* required: true
|
* required: true
|
||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
|
* description: The ID of the chat note (formerly sessionId)
|
||||||
* requestBody:
|
* requestBody:
|
||||||
* required: true
|
* required: true
|
||||||
* content:
|
* content:
|
||||||
@ -357,7 +333,7 @@ async function deleteChat(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 chat's context note
|
* description: Whether to use the session's context note
|
||||||
* responses:
|
* responses:
|
||||||
* '200':
|
* '200':
|
||||||
* description: LLM response
|
* description: LLM response
|
||||||
@ -379,10 +355,10 @@ async function deleteChat(req: Request, res: Response) {
|
|||||||
* type: string
|
* type: string
|
||||||
* similarity:
|
* similarity:
|
||||||
* type: number
|
* type: number
|
||||||
* chatNoteId:
|
* sessionId:
|
||||||
* type: string
|
* type: string
|
||||||
* '404':
|
* '404':
|
||||||
* description: Chat not found
|
* description: Session not found
|
||||||
* '500':
|
* '500':
|
||||||
* description: Error processing request
|
* description: Error processing request
|
||||||
* security:
|
* security:
|
||||||
@ -393,175 +369,6 @@ 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:
|
||||||
@ -957,13 +764,171 @@ async function indexNote(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/llm/chat/{chatNoteId}/messages/stream:
|
||||||
|
* post:
|
||||||
|
* summary: Stream a message to an LLM via WebSocket
|
||||||
|
* operationId: llm-stream-message
|
||||||
|
* parameters:
|
||||||
|
* - name: chatNoteId
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: The ID of the chat note to stream messages to (formerly sessionId)
|
||||||
|
* 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 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
|
||||||
|
const session = restChatService.getSessions().get(chatNoteId);
|
||||||
|
if (!session) {
|
||||||
|
throw new Error('Chat not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: 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: 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';
|
||||||
|
chatNoteId: string;
|
||||||
|
content?: string;
|
||||||
|
thinking?: string;
|
||||||
|
toolExecution?: any;
|
||||||
|
done?: boolean;
|
||||||
|
error?: string;
|
||||||
|
raw?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send error to client via WebSocket
|
||||||
|
wsService.sendMessageToAllClients({
|
||||||
|
type: 'llm-stream',
|
||||||
|
chatNoteId: 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',
|
||||||
|
chatNoteId: chatNoteId,
|
||||||
|
thinking: 'Initializing streaming LLM response...'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Let the client know streaming has started via HTTP response
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Streaming started',
|
||||||
|
chatNoteId: chatNoteId
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
log.error(`Error starting message stream: ${error.message}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// Chat management
|
// Chat session management
|
||||||
createChat,
|
createSession,
|
||||||
getChat,
|
getSession,
|
||||||
updateChat,
|
updateSession,
|
||||||
listChats,
|
listSessions,
|
||||||
deleteChat,
|
deleteSession,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
streamMessage,
|
streamMessage,
|
||||||
|
|
||||||
|
@ -392,15 +392,14 @@ function register(app: express.Application) {
|
|||||||
etapiSpecRoute.register(router);
|
etapiSpecRoute.register(router);
|
||||||
etapiBackupRoute.register(router);
|
etapiBackupRoute.register(router);
|
||||||
|
|
||||||
// LLM chat session management endpoints
|
// LLM Chat API
|
||||||
apiRoute(PST, "/api/llm/sessions", llmRoute.createSession);
|
apiRoute(PST, "/api/llm/chat", llmRoute.createSession);
|
||||||
apiRoute(GET, "/api/llm/sessions", llmRoute.listSessions);
|
apiRoute(GET, "/api/llm/chat", llmRoute.listSessions);
|
||||||
apiRoute(GET, "/api/llm/sessions/:sessionId", llmRoute.getSession);
|
apiRoute(GET, "/api/llm/chat/:sessionId", llmRoute.getSession);
|
||||||
apiRoute(PATCH, "/api/llm/sessions/:sessionId", llmRoute.updateSession);
|
apiRoute(PATCH, "/api/llm/chat/:sessionId", llmRoute.updateSession);
|
||||||
apiRoute(DEL, "/api/llm/sessions/:sessionId", llmRoute.deleteSession);
|
apiRoute(DEL, "/api/llm/chat/:chatNoteId", llmRoute.deleteSession);
|
||||||
apiRoute(PST, "/api/llm/sessions/:sessionId/messages", llmRoute.sendMessage);
|
apiRoute(PST, "/api/llm/chat/:chatNoteId/messages", llmRoute.sendMessage);
|
||||||
apiRoute(GET, "/api/llm/sessions/:sessionId/messages", llmRoute.sendMessage);
|
apiRoute(PST, "/api/llm/chat/:chatNoteId/messages/stream", llmRoute.streamMessage);
|
||||||
apiRoute(PST, "/api/llm/sessions/:sessionId/messages/stream", llmRoute.streamMessage);
|
|
||||||
|
|
||||||
// LLM index management endpoints - reorganized for REST principles
|
// LLM index management endpoints - reorganized for REST principles
|
||||||
apiRoute(GET, "/api/llm/indexes/stats", llmRoute.getIndexStats);
|
apiRoute(GET, "/api/llm/indexes/stats", llmRoute.getIndexStats);
|
||||||
|
@ -38,13 +38,13 @@ export class StreamHandler {
|
|||||||
const wsService = (await import('../../../ws.js')).default;
|
const wsService = (await import('../../../ws.js')).default;
|
||||||
|
|
||||||
let messageContent = '';
|
let messageContent = '';
|
||||||
const sessionId = session.id;
|
const chatNoteId = session.id;
|
||||||
|
|
||||||
// Immediately send an initial message to confirm WebSocket connection is working
|
// Immediately send an initial message to confirm WebSocket connection is working
|
||||||
// This helps prevent timeouts on the client side
|
// This helps prevent timeouts on the client side
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
thinking: 'Preparing response...'
|
thinking: 'Preparing response...'
|
||||||
} as LLMStreamMessage);
|
} as LLMStreamMessage);
|
||||||
|
|
||||||
@ -64,19 +64,19 @@ export class StreamHandler {
|
|||||||
// Send thinking state notification via WebSocket
|
// Send thinking state notification via WebSocket
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
thinking: 'Analyzing tools needed for this request...'
|
thinking: 'Analyzing tools needed for this request...'
|
||||||
} as LLMStreamMessage);
|
} as LLMStreamMessage);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Execute the tools
|
// Execute the tools
|
||||||
const toolResults = await ToolHandler.executeToolCalls(response, sessionId);
|
const toolResults = await ToolHandler.executeToolCalls(response, chatNoteId);
|
||||||
|
|
||||||
// For each tool execution, send progress update via WebSocket
|
// For each tool execution, send progress update via WebSocket
|
||||||
for (const toolResult of toolResults) {
|
for (const toolResult of toolResults) {
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
toolExecution: {
|
toolExecution: {
|
||||||
action: 'complete',
|
action: 'complete',
|
||||||
tool: toolResult.name,
|
tool: toolResult.name,
|
||||||
@ -108,7 +108,7 @@ export class StreamHandler {
|
|||||||
await this.processStreamedResponse(
|
await this.processStreamedResponse(
|
||||||
followUpResponse,
|
followUpResponse,
|
||||||
wsService,
|
wsService,
|
||||||
sessionId,
|
chatNoteId,
|
||||||
session,
|
session,
|
||||||
toolMessages,
|
toolMessages,
|
||||||
followUpOptions,
|
followUpOptions,
|
||||||
@ -120,7 +120,7 @@ export class StreamHandler {
|
|||||||
// Send error via WebSocket with done flag
|
// Send error via WebSocket with done flag
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
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);
|
} as LLMStreamMessage);
|
||||||
@ -137,7 +137,7 @@ export class StreamHandler {
|
|||||||
await this.processStreamedResponse(
|
await this.processStreamedResponse(
|
||||||
response,
|
response,
|
||||||
wsService,
|
wsService,
|
||||||
sessionId,
|
chatNoteId,
|
||||||
session
|
session
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -149,7 +149,7 @@ export class StreamHandler {
|
|||||||
// Send via WebSocket - include both content and done flag in same message
|
// Send via WebSocket - include both content and done flag in same message
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
content: messageContent,
|
content: messageContent,
|
||||||
done: true
|
done: true
|
||||||
} as LLMStreamMessage);
|
} as LLMStreamMessage);
|
||||||
@ -172,14 +172,14 @@ export class StreamHandler {
|
|||||||
// Send error via WebSocket
|
// Send error via WebSocket
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
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);
|
} as LLMStreamMessage);
|
||||||
|
|
||||||
// Signal completion
|
// Signal completion
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
done: true
|
done: true
|
||||||
} as LLMStreamMessage);
|
} as LLMStreamMessage);
|
||||||
}
|
}
|
||||||
@ -191,7 +191,7 @@ export class StreamHandler {
|
|||||||
private static async processStreamedResponse(
|
private static async processStreamedResponse(
|
||||||
response: any,
|
response: any,
|
||||||
wsService: any,
|
wsService: any,
|
||||||
sessionId: string,
|
chatNoteId: string,
|
||||||
session: ChatSession,
|
session: ChatSession,
|
||||||
toolMessages?: any[],
|
toolMessages?: any[],
|
||||||
followUpOptions?: any,
|
followUpOptions?: any,
|
||||||
@ -213,7 +213,7 @@ export class StreamHandler {
|
|||||||
// Send each individual chunk via WebSocket as it arrives
|
// Send each individual chunk via WebSocket as it arrives
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
content: chunk.text,
|
content: chunk.text,
|
||||||
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
|
||||||
@ -230,7 +230,7 @@ export class StreamHandler {
|
|||||||
if (chunk.raw?.thinking) {
|
if (chunk.raw?.thinking) {
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
thinking: chunk.raw.thinking
|
thinking: chunk.raw.thinking
|
||||||
} as LLMStreamMessage);
|
} as LLMStreamMessage);
|
||||||
}
|
}
|
||||||
@ -239,7 +239,7 @@ export class StreamHandler {
|
|||||||
if (chunk.raw?.toolExecution) {
|
if (chunk.raw?.toolExecution) {
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
toolExecution: chunk.raw.toolExecution
|
toolExecution: chunk.raw.toolExecution
|
||||||
} as LLMStreamMessage);
|
} as LLMStreamMessage);
|
||||||
}
|
}
|
||||||
@ -251,7 +251,7 @@ export class StreamHandler {
|
|||||||
// Send tool execution notification
|
// Send tool execution notification
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'tool_execution_start',
|
type: 'tool_execution_start',
|
||||||
sessionId
|
chatNoteId
|
||||||
} as LLMStreamMessage);
|
} as LLMStreamMessage);
|
||||||
|
|
||||||
// Process each tool call
|
// Process each tool call
|
||||||
@ -270,7 +270,7 @@ export class StreamHandler {
|
|||||||
// Format into a standardized tool execution message
|
// Format into a standardized tool execution message
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'tool_result',
|
type: 'tool_result',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
toolExecution: {
|
toolExecution: {
|
||||||
action: 'executing',
|
action: 'executing',
|
||||||
tool: toolCall.function?.name || 'unknown',
|
tool: toolCall.function?.name || 'unknown',
|
||||||
@ -300,7 +300,7 @@ export class StreamHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Execute the next round of tools
|
// Execute the next round of tools
|
||||||
const nextToolResults = await ToolHandler.executeToolCalls(response, sessionId);
|
const nextToolResults = await ToolHandler.executeToolCalls(response, chatNoteId);
|
||||||
|
|
||||||
// Create a new messages array with the latest tool results
|
// Create a new messages array with the latest tool results
|
||||||
const nextToolMessages = [...toolMessages, assistantMessage, ...nextToolResults];
|
const nextToolMessages = [...toolMessages, assistantMessage, ...nextToolResults];
|
||||||
@ -320,7 +320,7 @@ export class StreamHandler {
|
|||||||
await this.processStreamedResponse(
|
await this.processStreamedResponse(
|
||||||
nextResponse,
|
nextResponse,
|
||||||
wsService,
|
wsService,
|
||||||
sessionId,
|
chatNoteId,
|
||||||
session,
|
session,
|
||||||
nextToolMessages,
|
nextToolMessages,
|
||||||
nextFollowUpOptions,
|
nextFollowUpOptions,
|
||||||
@ -335,7 +335,7 @@ export class StreamHandler {
|
|||||||
// Send final message with done flag only (no content)
|
// Send final message with done flag only (no content)
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
chatNoteId,
|
||||||
done: true
|
done: true
|
||||||
} as LLMStreamMessage);
|
} as LLMStreamMessage);
|
||||||
}
|
}
|
||||||
@ -357,7 +357,7 @@ export class StreamHandler {
|
|||||||
// Report the error to the client
|
// Report the error to the client
|
||||||
wsService.sendMessageToAllClients({
|
wsService.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId,
|
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);
|
} as LLMStreamMessage);
|
||||||
|
@ -12,9 +12,9 @@ export class ToolHandler {
|
|||||||
/**
|
/**
|
||||||
* Execute tool calls from the LLM response
|
* Execute tool calls from the LLM response
|
||||||
* @param response The LLM response containing tool calls
|
* @param response The LLM response containing tool calls
|
||||||
* @param sessionId Optional session ID for tracking
|
* @param chatNoteId Optional chat note ID for tracking
|
||||||
*/
|
*/
|
||||||
static async executeToolCalls(response: any, sessionId?: string): Promise<Message[]> {
|
static async executeToolCalls(response: any, chatNoteId?: string): Promise<Message[]> {
|
||||||
log.info(`========== TOOL EXECUTION FLOW ==========`);
|
log.info(`========== TOOL EXECUTION FLOW ==========`);
|
||||||
if (!response.tool_calls || response.tool_calls.length === 0) {
|
if (!response.tool_calls || response.tool_calls.length === 0) {
|
||||||
log.info(`No tool calls to execute, returning early`);
|
log.info(`No tool calls to execute, returning early`);
|
||||||
@ -101,9 +101,9 @@ export class ToolHandler {
|
|||||||
: JSON.stringify(result).substring(0, 100) + '...';
|
: JSON.stringify(result).substring(0, 100) + '...';
|
||||||
log.info(`Tool result: ${resultPreview}`);
|
log.info(`Tool result: ${resultPreview}`);
|
||||||
|
|
||||||
// Record tool execution in session if session ID is provided
|
// Record tool execution in session if chatNoteId is provided
|
||||||
if (sessionId) {
|
if (chatNoteId) {
|
||||||
SessionsStore.recordToolExecution(sessionId, toolCall, typeof result === 'string' ? result : JSON.stringify(result));
|
SessionsStore.recordToolExecution(chatNoteId, toolCall, typeof result === 'string' ? result : JSON.stringify(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format result as a proper message
|
// Format result as a proper message
|
||||||
@ -116,9 +116,9 @@ export class ToolHandler {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
log.error(`Error executing tool ${toolCall.function.name}: ${error.message}`);
|
log.error(`Error executing tool ${toolCall.function.name}: ${error.message}`);
|
||||||
|
|
||||||
// Record error in session if session ID is provided
|
// Record error in session if chatNoteId is provided
|
||||||
if (sessionId) {
|
if (chatNoteId) {
|
||||||
SessionsStore.recordToolExecution(sessionId, toolCall, '', error.message);
|
SessionsStore.recordToolExecution(chatNoteId, toolCall, '', error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return error as tool result
|
// Return error as tool result
|
||||||
|
@ -108,8 +108,8 @@ class RestChatService {
|
|||||||
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 chatNoteId from URL params since it's part of the route
|
// Get chatNoteId from URL params
|
||||||
chatNoteId = req.params.chatNoteId || req.params.sessionId; // Support both names for backward compatibility
|
chatNoteId = req.params.chatNoteId;
|
||||||
|
|
||||||
// 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') {
|
||||||
@ -134,16 +134,17 @@ class RestChatService {
|
|||||||
log.info(`No Chat Note found for ${chatNoteId}, creating a new Chat Note and session`);
|
log.info(`No Chat Note found for ${chatNoteId}, creating a new Chat Note and session`);
|
||||||
|
|
||||||
// Create a new Chat Note via the storage service
|
// Create a new Chat Note via the storage service
|
||||||
const chatStorageService = (await import('../../llm/chat_storage_service.js')).default;
|
//const chatStorageService = (await import('../../llm/chat_storage_service.js')).default;
|
||||||
const newChat = await chatStorageService.createChat('New Chat');
|
//const newChat = await chatStorageService.createChat('New Chat');
|
||||||
|
|
||||||
// Use the new Chat Note's ID for the session
|
// Use the new Chat Note's ID for the session
|
||||||
session = SessionsStore.createSession({
|
session = SessionsStore.createSession({
|
||||||
title: newChat.title
|
//title: newChat.title,
|
||||||
|
chatNoteId: chatNoteId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the session ID to match the Chat Note ID
|
// Update the session ID to match the Chat Note ID
|
||||||
session.id = newChat.id;
|
session.id = chatNoteId;
|
||||||
|
|
||||||
log.info(`Created new Chat Note and session with ID: ${session.id}`);
|
log.info(`Created new Chat Note and session with ID: ${session.id}`);
|
||||||
|
|
||||||
@ -271,7 +272,7 @@ class RestChatService {
|
|||||||
// 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 chatNoteId for tracking tool executions
|
// Include chatNoteId for tracking tool executions
|
||||||
sessionId: chatNoteId // Use sessionId property for backward compatibility
|
chatNoteId: chatNoteId
|
||||||
};
|
};
|
||||||
|
|
||||||
// Log the options to verify what's being sent to the pipeline
|
// Log the options to verify what's being sent to the pipeline
|
||||||
@ -312,7 +313,7 @@ class RestChatService {
|
|||||||
try {
|
try {
|
||||||
wsService.default.sendMessageToAllClients({
|
wsService.default.sendMessageToAllClients({
|
||||||
type: 'llm-stream',
|
type: 'llm-stream',
|
||||||
sessionId: chatNoteId, // Use sessionId property for backward compatibility
|
chatNoteId: chatNoteId,
|
||||||
error: `Stream error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
error: `Stream error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||||
done: true
|
done: true
|
||||||
});
|
});
|
||||||
@ -394,7 +395,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: chatNoteId // Use sessionId property for backward compatibility
|
chatNoteId: chatNoteId
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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
|
||||||
@ -479,8 +480,27 @@ class RestChatService {
|
|||||||
const options: any = req.body || {};
|
const options: any = req.body || {};
|
||||||
const title = options.title || 'Chat Session';
|
const title = options.title || 'Chat Session';
|
||||||
|
|
||||||
|
// Use the currentNoteId as the chatNoteId if provided
|
||||||
|
let chatNoteId = options.chatNoteId;
|
||||||
|
|
||||||
|
// If currentNoteId is provided but chatNoteId is not, use currentNoteId
|
||||||
|
if (!chatNoteId && options.currentNoteId) {
|
||||||
|
chatNoteId = options.currentNoteId;
|
||||||
|
log.info(`Using provided currentNoteId ${chatNoteId} as chatNoteId`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we still don't have a chatNoteId, create a new Chat Note
|
||||||
|
if (!chatNoteId) {
|
||||||
|
// Create a new Chat Note via the storage service
|
||||||
|
const chatStorageService = (await import('../../llm/chat_storage_service.js')).default;
|
||||||
|
const newChat = await chatStorageService.createChat(title);
|
||||||
|
chatNoteId = newChat.id;
|
||||||
|
log.info(`Created new Chat Note with ID: ${chatNoteId}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new session through our session store
|
// Create a new session through our session store
|
||||||
const session = SessionsStore.createSession({
|
const session = SessionsStore.createSession({
|
||||||
|
chatNoteId,
|
||||||
title,
|
title,
|
||||||
systemPrompt: options.systemPrompt,
|
systemPrompt: options.systemPrompt,
|
||||||
contextNoteId: options.contextNoteId,
|
contextNoteId: options.contextNoteId,
|
||||||
@ -493,7 +513,8 @@ class RestChatService {
|
|||||||
return {
|
return {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
title: session.title,
|
title: session.title,
|
||||||
createdAt: session.createdAt
|
createdAt: session.createdAt,
|
||||||
|
noteId: chatNoteId // Return the note ID explicitly
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
log.error(`Error creating LLM session: ${error.message || 'Unknown error'}`);
|
log.error(`Error creating LLM session: ${error.message || 'Unknown error'}`);
|
||||||
|
@ -59,6 +59,7 @@ class SessionsStore {
|
|||||||
* Create a new session
|
* Create a new session
|
||||||
*/
|
*/
|
||||||
createSession(options: {
|
createSession(options: {
|
||||||
|
chatNoteId: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
contextNoteId?: string;
|
contextNoteId?: string;
|
||||||
@ -70,7 +71,7 @@ class SessionsStore {
|
|||||||
this.initializeCleanupTimer();
|
this.initializeCleanupTimer();
|
||||||
|
|
||||||
const title = options.title || 'Chat Session';
|
const title = options.title || 'Chat Session';
|
||||||
const sessionId = randomString(16);
|
const sessionId = options.chatNoteId;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
// Initial system message if provided
|
// Initial system message if provided
|
||||||
@ -103,7 +104,7 @@ class SessionsStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sessions.set(sessionId, session);
|
sessions.set(sessionId, session);
|
||||||
log.info(`Created new session with ID: ${sessionId}`);
|
log.info(`Created in-memory session for Chat Note ID: ${sessionId}`);
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
@ -131,10 +132,10 @@ class SessionsStore {
|
|||||||
/**
|
/**
|
||||||
* Record a tool execution in the session metadata
|
* Record a tool execution in the session metadata
|
||||||
*/
|
*/
|
||||||
recordToolExecution(sessionId: string, tool: any, result: string, error?: string): void {
|
recordToolExecution(chatNoteId: string, tool: any, result: string, error?: string): void {
|
||||||
if (!sessionId) return;
|
if (!chatNoteId) return;
|
||||||
|
|
||||||
const session = sessions.get(sessionId);
|
const session = sessions.get(chatNoteId);
|
||||||
if (!session) return;
|
if (!session) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -156,7 +157,7 @@ class SessionsStore {
|
|||||||
toolExecutions.push(execution);
|
toolExecutions.push(execution);
|
||||||
session.metadata.toolExecutions = toolExecutions;
|
session.metadata.toolExecutions = toolExecutions;
|
||||||
|
|
||||||
log.info(`Recorded tool execution for ${execution.name} in session ${sessionId}`);
|
log.info(`Recorded tool execution for ${execution.name} in session ${chatNoteId}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error(`Failed to record tool execution: ${err}`);
|
log.error(`Failed to record tool execution: ${err}`);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
export interface LLMStreamMessage {
|
export interface LLMStreamMessage {
|
||||||
type: 'llm-stream' | 'tool_execution_start' | 'tool_result' | 'tool_execution_error' | 'tool_completion_processing';
|
type: 'llm-stream' | 'tool_execution_start' | 'tool_result' | 'tool_execution_error' | 'tool_completion_processing';
|
||||||
sessionId: string;
|
chatNoteId: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
thinking?: string;
|
thinking?: string;
|
||||||
toolExecution?: {
|
toolExecution?: {
|
||||||
|
@ -58,7 +58,7 @@ interface Message {
|
|||||||
filePath?: string;
|
filePath?: string;
|
||||||
|
|
||||||
// LLM streaming specific fields
|
// LLM streaming specific fields
|
||||||
sessionId?: string;
|
chatNoteId?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
thinking?: string;
|
thinking?: string;
|
||||||
toolExecution?: {
|
toolExecution?: {
|
||||||
@ -133,7 +133,7 @@ function sendMessageToAllClients(message: Message) {
|
|||||||
if (webSocketServer) {
|
if (webSocketServer) {
|
||||||
// Special logging for LLM streaming messages
|
// Special logging for LLM streaming messages
|
||||||
if (message.type === "llm-stream") {
|
if (message.type === "llm-stream") {
|
||||||
log.info(`[WS-SERVER] Sending LLM stream message: sessionId=${message.sessionId}, content=${!!message.content}, thinking=${!!message.thinking}, toolExecution=${!!message.toolExecution}, done=${!!message.done}`);
|
log.info(`[WS-SERVER] Sending LLM stream message: chatNoteId=${message.chatNoteId}, content=${!!message.content}, thinking=${!!message.thinking}, toolExecution=${!!message.toolExecution}, done=${!!message.done}`);
|
||||||
} else if (message.type !== "sync-failed" && message.type !== "api-log-messages") {
|
} else if (message.type !== "sync-failed" && message.type !== "api-log-messages") {
|
||||||
log.info(`Sending message to all clients: ${jsonStr}`);
|
log.info(`Sending message to all clients: ${jsonStr}`);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user