From dbe9068b0b9dd394d51451c13e130a3bf363f603 Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Sun, 28 Sep 2025 14:57:58 +0100 Subject: [PATCH] Add MessageService and AIThoughtMessage component --- Components/ChatArea.svelte | 27 ++++++---------- Components/ChatAreaThought.svelte | 53 +++++++++++++++++++++++++++++++ Messages/AIThoughtMessage.ts | 7 ++++ Messages/IMessage.ts | 3 ++ Services/MessageService.ts | 29 +++++++++++++++++ Services/ServiceRegistration.ts | 2 ++ Services/Services.ts | 1 + 7 files changed, 104 insertions(+), 18 deletions(-) create mode 100644 Components/ChatAreaThought.svelte create mode 100644 Messages/AIThoughtMessage.ts create mode 100644 Messages/IMessage.ts create mode 100644 Services/MessageService.ts diff --git a/Components/ChatArea.svelte b/Components/ChatArea.svelte index 7286042..a04ae10 100644 --- a/Components/ChatArea.svelte +++ b/Components/ChatArea.svelte @@ -2,11 +2,13 @@ import { Resolve } from "Services/DependencyService"; import { Services } from "Services/Services"; import type { StreamingMarkdownService } from "Services/StreamingMarkdownService"; + import ChatAreaThought from "./ChatAreaThought.svelte"; export let messages: Array<{id: string, content: string, isUser: boolean, isStreaming: boolean}> = []; - let chatContainer: HTMLDivElement; let streamingMarkdownService: StreamingMarkdownService = Resolve(Services.StreamingMarkdownService); + + let chatContainer: HTMLDivElement; let messageElements = new Map(); let lastProcessedContent = new Map(); @@ -30,10 +32,8 @@ if (!element) return; if (message.isStreaming) { - // Use streaming update streamingMarkdownService.streamChunk(message.id, message.content); } else { - // Finalize the message streamingMarkdownService.finalizeStream(message.id, message.content); } } @@ -49,7 +49,6 @@ return { destroy() { - // Cleanup when element is removed messageElements.delete(messageId); } }; @@ -74,12 +73,6 @@ return ''; // Streaming messages will be handled by the streaming service } - // Clean up when component is destroyed - function cleanup() { - messageElements.clear(); - lastProcessedContent.clear(); - } - // Make sure to clean up when messages are removed $: { const currentMessageIds = new Set(messages.map(m => m.id)); @@ -102,16 +95,14 @@

{message.content}

{:else}
+ {#if message.isStreaming} - -
- ● ● ● +
+ ● ● ● + {:else} - - {@html getStaticHTML(message)} + + {@html getStaticHTML(message)} {/if}
{/if} diff --git a/Components/ChatAreaThought.svelte b/Components/ChatAreaThought.svelte new file mode 100644 index 0000000..8a2d015 --- /dev/null +++ b/Components/ChatAreaThought.svelte @@ -0,0 +1,53 @@ + + + {#if isVisible && currentThought} +
+
+ {currentThought} +
+
+ {/if} + + \ No newline at end of file diff --git a/Messages/AIThoughtMessage.ts b/Messages/AIThoughtMessage.ts new file mode 100644 index 0000000..2ff42d8 --- /dev/null +++ b/Messages/AIThoughtMessage.ts @@ -0,0 +1,7 @@ +import type { IMessage } from "./IMessage"; + +export class AIThoughtMessage implements IMessage { + constructor(public thought: string | null) {} + invoke(): void { + } +} \ No newline at end of file diff --git a/Messages/IMessage.ts b/Messages/IMessage.ts new file mode 100644 index 0000000..a90fe4f --- /dev/null +++ b/Messages/IMessage.ts @@ -0,0 +1,3 @@ +export interface IMessage { + invoke(): void; + } \ No newline at end of file diff --git a/Services/MessageService.ts b/Services/MessageService.ts new file mode 100644 index 0000000..3a3a510 --- /dev/null +++ b/Services/MessageService.ts @@ -0,0 +1,29 @@ +import type { IMessage } from "Messages/IMessage"; + +type MessageConstructor = new (...args: any[]) => T; + +export class MessageService { + private handlers = new Map, Set<(message: any) => void>>(); + + register(messageType: MessageConstructor, handler: (message: T) => void): void { + if (!this.handlers.has(messageType)) { + this.handlers.set(messageType, new Set()); + } + + let messageHandler: Set<(message: any) => void> | undefined = this.handlers.get(messageType); + + if (messageHandler != null) { + messageHandler.add(handler); + } + } + + unregister(messageType: MessageConstructor, handler: (message: T) => void): void { + this.handlers.get(messageType)?.delete(handler); + } + + send(message: T): void { + const messageType = message.constructor as MessageConstructor; + const handlers = this.handlers.get(messageType); + handlers?.forEach(handler => handler(message)); + } +} \ No newline at end of file diff --git a/Services/ServiceRegistration.ts b/Services/ServiceRegistration.ts index b39be3e..733c5cc 100644 --- a/Services/ServiceRegistration.ts +++ b/Services/ServiceRegistration.ts @@ -12,8 +12,10 @@ import { GeminiActionDefinitions } from "Actioner/Gemini/GeminiActionDefinitions import type { IActionDefinitions } from "Actioner/IActionDefinitions"; import { Gemini } from "AIClasses/Gemini/Gemini"; import { StreamingMarkdownService } from "./StreamingMarkdownService"; +import { MessageService } from "./MessageService"; export function RegisterDependencies(plugin: DmsAssistantPlugin) { + RegisterSingleton(Services.MessageService, new MessageService()); RegisterSingleton(Services.DmsAssistantPlugin, plugin); RegisterSingleton(Services.OdbCache, new OdbCache()); RegisterSingleton(Services.ModalService, new ModalService()) diff --git a/Services/Services.ts b/Services/Services.ts index 6e6d052..dcfd690 100644 --- a/Services/Services.ts +++ b/Services/Services.ts @@ -1,4 +1,5 @@ export class Services { + static MessageService = Symbol("MessageService"); static DmsAssistantPlugin = Symbol("DmsAssistantPlugin"); static OdbCache = Symbol("OdbCache"); static ModalService = Symbol("ModalService");