diff --git a/.gitignore b/.gitignore index 1aee6e7dd7..f6904bc1da 100644 --- a/.gitignore +++ b/.gitignore @@ -387,6 +387,7 @@ log **/vectorization-api-event-profile.json **/vectorization-worker-event-profile.json deploy/quick-start/.env +deploy/quick-start/tools **/management-api-event-profile.json **/testsettings.json diff --git a/src/dotnet/Common/Models/Orchestration/LongRunningOperation.cs b/src/dotnet/Common/Models/Orchestration/LongRunningOperation.cs new file mode 100644 index 0000000000..7ac1380e5e --- /dev/null +++ b/src/dotnet/Common/Models/Orchestration/LongRunningOperation.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace FoundationaLLM.Common.Models.Orchestration +{ + /// + /// Represents the current state of a long-running operation. + /// + public class LongRunningOperation + { + /// + /// The identifier of the long-running operation. + /// + [JsonPropertyName("operation_id")] + public required string OperationId { get; set; } + + /// + /// The status of the long-running operation. + /// + [JsonPropertyName("status")] + public required OperationStatus Status { get; set; } + + /// + /// The message describing the current state of the operation. + /// + [JsonPropertyName("status_message")] + public string? StatusMessage { get; set; } + + /// + /// The time stamp of the last update to the operation. + /// + [JsonPropertyName("last_updated")] + public DateTime? LastUpdated { get; set; } + } +} diff --git a/src/dotnet/Common/Models/Orchestration/OperationState.cs b/src/dotnet/Common/Models/Orchestration/OperationState.cs deleted file mode 100644 index 2f07f6b8be..0000000000 --- a/src/dotnet/Common/Models/Orchestration/OperationState.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace FoundationaLLM.Common.Models.Orchestration -{ - /// - /// Represents the current state of a long running operation. - /// - public class OperationState - { - /// - /// The identifier of the long running operation. - /// - public required string OperationId { get; set; } - - /// - /// The status of the long running operation. - /// - public required OperationStatus Status { get; set; } - - /// - /// The message describing the current state of the operation. - /// - public string? Message { get; set; } - } -} diff --git a/src/dotnet/Core/Interfaces/ICoreService.cs b/src/dotnet/Core/Interfaces/ICoreService.cs index 54fe8523e6..27cc886576 100644 --- a/src/dotnet/Core/Interfaces/ICoreService.cs +++ b/src/dotnet/Core/Interfaces/ICoreService.cs @@ -83,4 +83,28 @@ public interface ICoreService /// The id of the completion prompt to retrieve. /// Task GetCompletionPrompt(string instanceId, string sessionId, string completionPromptId); + + /// + /// Begins a completion operation. + /// + /// The FoundationaLLM instance id. + /// The completion request containing the user prompt and message history. + /// Returns an object containing the OperationId and Status. + Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest); + + /// + /// Gets the status of a completion operation. + /// + /// The FoundationaLLM instance id. + /// The OperationId for which to retrieve the status. + /// Returns an object containing the OperationId and Status. + Task GetCompletionOperationStatus(string instanceId, string operationId); + + /// + /// Gets a completion operation from the downstream API. + /// + /// The FoundationaLLM instance id. + /// The ID of the operation to retrieve. + /// Returns a object. + Task GetCompletionOperationResult(string instanceId, string operationId); } diff --git a/src/dotnet/Core/Services/CoreService.cs b/src/dotnet/Core/Services/CoreService.cs index 4269735ca7..89cb8bea9f 100644 --- a/src/dotnet/Core/Services/CoreService.cs +++ b/src/dotnet/Core/Services/CoreService.cs @@ -159,6 +159,18 @@ public async Task GetCompletionAsync(string instanceId, CompletionRe } } + /// + public async Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest) => + throw new NotImplementedException(); + + /// + public Task GetCompletionOperationStatus(string instanceId, string operationId) => + throw new NotImplementedException(); + + /// + public async Task GetCompletionOperationResult(string instanceId, string operationId) => + throw new NotImplementedException(); + /// public async Task GenerateChatSessionNameAsync(string instanceId, string? sessionId, string? text) { diff --git a/src/dotnet/CoreAPI/Controllers/CompletionsController.cs b/src/dotnet/CoreAPI/Controllers/CompletionsController.cs index 59adadd71b..dea9f6e876 100644 --- a/src/dotnet/CoreAPI/Controllers/CompletionsController.cs +++ b/src/dotnet/CoreAPI/Controllers/CompletionsController.cs @@ -19,7 +19,7 @@ namespace FoundationaLLM.Core.API.Controllers /// [Authorize(Policy = "DefaultPolicy")] [ApiController] - [Route("instances/{instanceId}/[controller]")] + [Route("instances/{instanceId}")] public class CompletionsController : ControllerBase { private readonly ICoreService _coreService; @@ -60,17 +60,50 @@ public CompletionsController(ICoreService coreService, /// /// The instance ID of the current request. /// The user prompt for which to generate a completion. - [HttpPost(Name = "GetCompletion")] + [HttpPost("completions", Name = "GetCompletion")] public async Task GetCompletion(string instanceId, [FromBody] CompletionRequest completionRequest) => !string.IsNullOrWhiteSpace(completionRequest.SessionId) ? Ok(await _coreService.GetChatCompletionAsync(instanceId, completionRequest)) : Ok(await _coreService.GetCompletionAsync(instanceId, completionRequest)); + /// + /// Begins a completion operation. + /// + /// The FoundationaLLM instance id. + /// The completion request containing the user prompt and message history. + /// Returns an object containing the OperationId and Status. + [HttpPost("async-completions")] + public async Task> StartCompletionOperation(string instanceId, CompletionRequest completionRequest) + { + var state = await _coreService.StartCompletionOperation(instanceId, completionRequest); + return Accepted(state); + } + + /// + /// Gets the status of a completion operation. + /// + /// The FoundationaLLM instance id. + /// The OperationId for which to retrieve the status. + /// Returns an object containing the OperationId and Status. + [HttpGet("async-completions/{operationId}/status")] + public async Task GetCompletionOperationStatus(string instanceId, string operationId) => + await _coreService.GetCompletionOperationStatus(instanceId, operationId); + + /// + /// Gets a completion operation from the downstream APIs. + /// + /// The FoundationaLLM instance id. + /// The ID of the operation to retrieve. + /// Returns a completion response + [HttpGet("async-completions/{operationId}/result")] + public async Task GetCompletionOperationResult(string instanceId, string operationId) => + await _coreService.GetCompletionOperationResult(instanceId, operationId); + /// /// Retrieves a list of global and private agents. /// /// The instance ID of the current request. /// A list of available agents. - [HttpGet("agents", Name = "GetAgents")] + [HttpGet("completions/agents", Name = "GetAgents")] public async Task>> GetAgents(string instanceId) => await _agentResourceProvider.GetResourcesWithRBAC(instanceId, _callContext.CurrentUserIdentity!); } diff --git a/src/dotnet/Gatekeeper/Interfaces/IGatekeeperService.cs b/src/dotnet/Gatekeeper/Interfaces/IGatekeeperService.cs index 7b856c7d21..7a0a0714a4 100644 --- a/src/dotnet/Gatekeeper/Interfaces/IGatekeeperService.cs +++ b/src/dotnet/Gatekeeper/Interfaces/IGatekeeperService.cs @@ -20,16 +20,16 @@ public interface IGatekeeperService /// /// The FoundationaLLM instance id. /// The completion request containing the user prompt and message history. - /// Returns an object containing the OperationId and Status. - Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest); + /// Returns an object containing the OperationId and Status. + Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest); /// /// Gets the status of a completion operation. /// /// The FoundationaLLM instance id. /// The OperationId to retrieve the status for. - /// Returns an object containing the OperationId and Status. - Task GetCompletionOperationStatus(string instanceId, string operationId); + /// Returns an object containing the OperationId and Status. + Task GetCompletionOperationStatus(string instanceId, string operationId); /// /// Gets a completion operation from the Gatekeeper service. @@ -37,5 +37,5 @@ public interface IGatekeeperService /// The FoundationaLLM instance id. /// The ID of the operation to retrieve. /// Returns a object. - Task GetCompletionOperation(string instanceId, string operationId); + Task GetCompletionOperationResult(string instanceId, string operationId); } diff --git a/src/dotnet/Gatekeeper/Services/GatekeeperService.cs b/src/dotnet/Gatekeeper/Services/GatekeeperService.cs index 7db07df40a..185f6ca13d 100644 --- a/src/dotnet/Gatekeeper/Services/GatekeeperService.cs +++ b/src/dotnet/Gatekeeper/Services/GatekeeperService.cs @@ -92,15 +92,15 @@ public async Task GetCompletion(string instanceId, Completio } /// - public async Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest) => + public async Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest) => // TODO: Need to call State API to start the operation. throw new NotImplementedException(); /// - public Task GetCompletionOperationStatus(string instanceId, string operationId) => throw new NotImplementedException(); + public Task GetCompletionOperationStatus(string instanceId, string operationId) => throw new NotImplementedException(); /// - public async Task GetCompletionOperation(string instanceId, string operationId) => + public async Task GetCompletionOperationResult(string instanceId, string operationId) => // TODO: Need to call State API to get the operation. throw new NotImplementedException(); diff --git a/src/dotnet/GatekeeperAPI/Controllers/CompletionsController.cs b/src/dotnet/GatekeeperAPI/Controllers/CompletionsController.cs index a40e3fc003..d1890e5dc3 100644 --- a/src/dotnet/GatekeeperAPI/Controllers/CompletionsController.cs +++ b/src/dotnet/GatekeeperAPI/Controllers/CompletionsController.cs @@ -35,9 +35,9 @@ public async Task GetCompletion(string instanceId, Completio /// /// The FoundationaLLM instance id. /// The completion request containing the user prompt and message history. - /// Returns an object containing the OperationId and Status. + /// Returns an object containing the OperationId and Status. [HttpPost("async-completions")] - public async Task> StartCompletionOperation(string instanceId, CompletionRequest completionRequest) + public async Task> StartCompletionOperation(string instanceId, CompletionRequest completionRequest) { var state = await _gatekeeperService.StartCompletionOperation(instanceId, completionRequest); return Accepted(state); @@ -48,9 +48,9 @@ public async Task> StartCompletionOperation(string /// /// The FoundationaLLM instance id. /// The OperationId for which to retrieve the status. - /// Returns an object containing the OperationId and Status. + /// Returns an object containing the OperationId and Status. [HttpGet("async-completions/{operationId}/status")] - public async Task GetCompletionOperationStatus(string instanceId, string operationId) => + public async Task GetCompletionOperationStatus(string instanceId, string operationId) => await _gatekeeperService.GetCompletionOperationStatus(instanceId, operationId); /// @@ -60,8 +60,8 @@ public async Task GetCompletionOperationStatus(string instanceId /// The ID of the operation to retrieve. /// Returns a completion response [HttpGet("async-completions/{operationId}/result")] - public async Task GetCompletionOperation(string instanceId, string operationId) => - await _gatekeeperService.GetCompletionOperation(instanceId, operationId); + public async Task GetCompletionOperationResult(string instanceId, string operationId) => + await _gatekeeperService.GetCompletionOperationResult(instanceId, operationId); } } diff --git a/src/dotnet/Orchestration/Interfaces/IOrchestrationService.cs b/src/dotnet/Orchestration/Interfaces/IOrchestrationService.cs index eff83399cf..c0daa1a613 100644 --- a/src/dotnet/Orchestration/Interfaces/IOrchestrationService.cs +++ b/src/dotnet/Orchestration/Interfaces/IOrchestrationService.cs @@ -28,16 +28,16 @@ public interface IOrchestrationService /// /// The FoundationaLLM instance id. /// The completion request containing the user prompt and message history. - /// Returns an object containing the OperationId and Status. - Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest); + /// Returns an object containing the OperationId and Status. + Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest); /// /// Gets the status of a completion operation. /// /// The FoundationaLLM instance id. - /// The OperationId to retrieve the status for. - /// Returns an object containing the OperationId and Status. - Task GetCompletionOperationStatus(string instanceId, string operationId); + /// The OperationId for which to retrieve the status. + /// Returns an object containing the OperationId and Status. + Task GetCompletionOperationStatus(string instanceId, string operationId); /// /// Gets a completion operation from the Orchestration service. @@ -45,5 +45,5 @@ public interface IOrchestrationService /// The FoundationaLLM instance id. /// The ID of the operation to retrieve. /// Returns a object. - Task GetCompletionOperation(string instanceId, string operationId); + Task GetCompletionOperationResult(string instanceId, string operationId); } diff --git a/src/dotnet/Orchestration/Services/OrchestrationService.cs b/src/dotnet/Orchestration/Services/OrchestrationService.cs index 9f495a6a19..0cf1edc03b 100644 --- a/src/dotnet/Orchestration/Services/OrchestrationService.cs +++ b/src/dotnet/Orchestration/Services/OrchestrationService.cs @@ -33,7 +33,7 @@ public class OrchestrationService : IOrchestrationService /// /// Constructor for the Orchestration Service. /// - /// A list of of resource providers hashed by resource provider name. + /// A list of resource providers hashed by resource provider name. /// The managing the internal and external LLM orchestration services. /// The call context of the request being handled. /// The used to retrieve app settings from configuration. @@ -101,15 +101,15 @@ public async Task GetCompletion(string instanceId, Completio } /// - public async Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest) => + public async Task StartCompletionOperation(string instanceId, CompletionRequest completionRequest) => // TODO: Need to call State API to start the operation. throw new NotImplementedException(); /// - public Task GetCompletionOperationStatus(string instanceId, string operationId) => throw new NotImplementedException(); + public Task GetCompletionOperationStatus(string instanceId, string operationId) => throw new NotImplementedException(); /// - public async Task GetCompletionOperation(string instanceId, string operationId) => + public async Task GetCompletionOperationResult(string instanceId, string operationId) => // TODO: Need to call State API to get the operation. throw new NotImplementedException(); diff --git a/src/dotnet/OrchestrationAPI/Controllers/CompletionsController.cs b/src/dotnet/OrchestrationAPI/Controllers/CompletionsController.cs index eca2b20c70..cb8dda451c 100644 --- a/src/dotnet/OrchestrationAPI/Controllers/CompletionsController.cs +++ b/src/dotnet/OrchestrationAPI/Controllers/CompletionsController.cs @@ -38,12 +38,12 @@ public async Task GetCompletion(string instanceId, [FromBody /// /// The FoundationaLLM instance id. /// The completion request containing the user prompt and message history. - /// Returns an object containing the OperationId and Status. + /// Returns an object containing the OperationId and Status. [HttpPost("async-completions")] - public async Task> StartCompletionOperation(string instanceId, CompletionRequest completionRequest) + public async Task> StartCompletionOperation(string instanceId, CompletionRequest completionRequest) { - var state = await _orchestrationService.StartCompletionOperation(instanceId, completionRequest); - return Accepted(state); + var longRunningOperation = await _orchestrationService.StartCompletionOperation(instanceId, completionRequest); + return Accepted(longRunningOperation); } /// @@ -51,9 +51,9 @@ public async Task> StartCompletionOperation(string /// /// The FoundationaLLM instance id. /// The OperationId for which to retrieve the status. - /// Returns an object containing the OperationId and Status. + /// Returns an object containing the OperationId and Status. [HttpGet("async-completions/{operationId}/status")] - public async Task GetCompletionOperationStatus(string instanceId, string operationId) => + public async Task GetCompletionOperationStatus(string instanceId, string operationId) => await _orchestrationService.GetCompletionOperationStatus(instanceId, operationId); /// @@ -63,8 +63,8 @@ public async Task GetCompletionOperationStatus(string instanceId /// The ID of the operation to retrieve. /// Returns a completion response [HttpGet("async-completions/{operationId}/result")] - public async Task GetCompletionOperation(string instanceId, string operationId) => - await _orchestrationService.GetCompletionOperation(instanceId, operationId); + public async Task GetCompletionOperationResult(string instanceId, string operationId) => + await _orchestrationService.GetCompletionOperationResult(instanceId, operationId); } } diff --git a/src/ui/UserPortal/components/ChatThread.vue b/src/ui/UserPortal/components/ChatThread.vue index 0f95c34339..aab8f67ef2 100644 --- a/src/ui/UserPortal/components/ChatThread.vue +++ b/src/ui/UserPortal/components/ChatThread.vue @@ -46,6 +46,7 @@ diff --git a/src/ui/UserPortal/js/api.ts b/src/ui/UserPortal/js/api.ts index e59f06efda..d308c9f0be 100644 --- a/src/ui/UserPortal/js/api.ts +++ b/src/ui/UserPortal/js/api.ts @@ -72,6 +72,71 @@ export default { } }, + /** + * Starts a long-running process by making a POST request to the specified URL with the given request body. + * @param url - The URL to send the POST request to. + * @param requestBody - The request body to send with the POST request. + * @returns A Promise that resolves to the operation ID if the process is successfully started. + * @throws An error if the process fails to start. + */ + async startLongRunningProcess(url: string, requestBody: any) { + const options = { + method: 'POST', + body: JSON.stringify(requestBody), + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${await this.getBearerToken()}` + } + }; + + try { + const response = await $fetch(`${this.apiUrl}${url}`, options); + if (response.status === 202) { + return response.operationId; + } else { + throw new Error('Failed to start process'); + } + } catch (error) { + throw new Error(this.formatError(error)); + } + }, + + /** + * Checks the status of a process operation. + * @param operationId - The ID of the operation to check. + * @returns A Promise that resolves to the response from the server. + * @throws If an error occurs during the API call. + */ + async checkProcessStatus(operationId: string) { + try { + const response = await $fetch(`/instances/${this.instanceId}/async-completions/${operationId}/status`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${await this.getBearerToken()}` + } + }); + + return response; + } catch (error) { + throw new Error(this.formatError(error)); + } + }, + + /** + * Polls for the completion of an operation. + * @param operationId - The ID of the operation to poll for completion. + * @returns A promise that resolves to the result of the operation when it is completed. + */ + async pollForCompletion(operationId: string) { + while (true) { + const status = await this.checkProcessStatus(operationId); + if (status.isCompleted) { + return status.result; + } + await new Promise(resolve => setTimeout(resolve, 2000)); // Poll every 2 seconds + } + }, + /** * Retrieves the chat sessions from the API. * @returns {Promise>} A promise that resolves to an array of sessions. @@ -180,10 +245,17 @@ export default { settings: null, attachments: attachments }; - return (await this.fetch(`/instances/${this.instanceId}/completions`, { - method: 'POST', - body: orchestrationRequest, - })) as string; + + if (agent.long_running) { + const operationId = await this.startLongRunningProcess(`/instances/${this.instanceId}/async-completions`, + orchestrationRequest); + return this.pollForCompletion(operationId); + } else { + return (await this.fetch(`/instances/${this.instanceId}/completions`, { + method: 'POST', + body: orchestrationRequest, + })) as string; + } }, /** diff --git a/src/ui/UserPortal/js/eventBus.ts b/src/ui/UserPortal/js/eventBus.ts new file mode 100644 index 0000000000..4b06ecd047 --- /dev/null +++ b/src/ui/UserPortal/js/eventBus.ts @@ -0,0 +1,5 @@ +import mitt from 'mitt'; + +const emitter = mitt(); + +export default emitter; diff --git a/src/ui/UserPortal/js/types/index.ts b/src/ui/UserPortal/js/types/index.ts index 43fa55cc7a..45e5d30855 100644 --- a/src/ui/UserPortal/js/types/index.ts +++ b/src/ui/UserPortal/js/types/index.ts @@ -63,6 +63,7 @@ export interface Agent { name: string; object_id: string; description: string; + long_running: boolean; orchestration_settings?: OrchestrationSettings; } diff --git a/src/ui/UserPortal/package-lock.json b/src/ui/UserPortal/package-lock.json index 40f09efdaf..40c582c25a 100644 --- a/src/ui/UserPortal/package-lock.json +++ b/src/ui/UserPortal/package-lock.json @@ -15,6 +15,7 @@ "javascript-time-ago": "^2.5.9", "marked": "^13.0.2", "marked-highlight": "^2.1.3", + "mitt": "^3.0.1", "pinia": "^2.1.7", "primeicons": "^6.0.1", "primevue": "^3.35.0", @@ -10258,8 +10259,7 @@ "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" }, "node_modules/mkdirp": { "version": "1.0.4", diff --git a/src/ui/UserPortal/package.json b/src/ui/UserPortal/package.json index 2b6943bcb8..c97334cf0d 100644 --- a/src/ui/UserPortal/package.json +++ b/src/ui/UserPortal/package.json @@ -36,6 +36,7 @@ "javascript-time-ago": "^2.5.9", "marked": "^13.0.2", "marked-highlight": "^2.1.3", + "mitt": "^3.0.1", "pinia": "^2.1.7", "primeicons": "^6.0.1", "primevue": "^3.35.0", diff --git a/src/ui/UserPortal/stores/appStore.ts b/src/ui/UserPortal/stores/appStore.ts index 37b6e32811..f6a32d2662 100644 --- a/src/ui/UserPortal/stores/appStore.ts +++ b/src/ui/UserPortal/stores/appStore.ts @@ -3,6 +3,7 @@ import { useAppConfigStore } from './appConfigStore'; import { useAuthStore } from './authStore'; import type { Session, Message, Agent, ResourceProviderGetResult, Attachment } from '@/js/types'; import api from '@/js/api'; +import eventBus from '@/js/eventBus'; export const useAppStore = defineStore('app', { state: () => ({ @@ -14,6 +15,7 @@ export const useAppStore = defineStore('app', { selectedAgents: new Map(), lastSelectedAgent: null as ResourceProviderGetResult | null, attachments: [] as Attachment[], + longRunningOperations: new Map(), // sessionId -> operationId }), getters: {}, @@ -150,6 +152,12 @@ export const useAppStore = defineStore('app', { return this.selectedAgents.set(session.id, agent); }, + /** + * Sends a message to the Core API. + * + * @param text - The text of the message to send. + * @returns A Promise that resolves when the message is sent. + */ async sendMessage(text: string) { if (!text) return; @@ -184,13 +192,28 @@ export const useAppStore = defineStore('app', { }; this.currentMessages.push(tempAssistantMessage); - await api.sendMessage( - this.currentSession!.id, - text, - this.getSessionAgent(this.currentSession!).resource, - this.attachments.map(attachment => String(attachment.id)), // Convert attachments to an array of strings - ); - await this.getMessages(); + const agent = this.getSessionAgent(this.currentSession!).resource; + if (agent.long_running) { + // Handle long-running operations + const operationId = await api.startLongRunningProcess('/completions', { + session_id: this.currentSession!.id, + user_prompt: text, + agent_name: agent.name, + settings: null, + attachments: this.attachments.map(attachment => String(attachment.id)) + }); + + this.longRunningOperations.set(this.currentSession!.id, operationId); + this.pollForCompletion(this.currentSession!.id, operationId); + } else { + await api.sendMessage( + this.currentSession!.id, + text, + agent, + this.attachments.map(attachment => String(attachment.id)), + ); + await this.getMessages(); + } // Update the session name based on the message sent. if (this.currentMessages.length === 2) { @@ -202,6 +225,25 @@ export const useAppStore = defineStore('app', { // the generate session name already renames the session in the backend this.currentSession!.name = newSessionName; } + }, + + /** + * Polls for the completion of a long-running operation. + * + * @param sessionId - The session ID associated with the operation. + * @param operationId - The ID of the operation to check for completion. + */ + async pollForCompletion(sessionId: string, operationId: string) { + while (true) { + const status = await api.checkProcessStatus(operationId); + if (status.isCompleted) { + this.longRunningOperations.delete(sessionId); + eventBus.emit('operation-completed', { sessionId, operationId }); + await this.getMessages(); + break; + } + await new Promise(resolve => setTimeout(resolve, 2000)); // Poll every 2 seconds + } }, async rateMessage(messageToRate: Message, isLiked: Message['rating']) {