mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Add MessageService and AIThoughtMessage component
This commit is contained in:
parent
ce41512c42
commit
dbe9068b0b
7 changed files with 104 additions and 18 deletions
|
|
@ -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<string, HTMLElement>();
|
||||
let lastProcessedContent = new Map<string, string>();
|
||||
|
||||
|
|
@ -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 @@
|
|||
<p>{message.content}</p>
|
||||
{:else}
|
||||
<div class="markdown-content" class:streaming={message.isStreaming}>
|
||||
<!-- Streaming message: use action for initialization -->
|
||||
{#if message.isStreaming}
|
||||
<!-- Streaming message: use action for initialization -->
|
||||
<div
|
||||
use:streamingAction={message.id}
|
||||
class="streaming-content"
|
||||
></div>
|
||||
<span class="streaming-indicator">● ● ●</span>
|
||||
<div use:streamingAction={message.id} class="streaming-content"></div>
|
||||
<span class="streaming-indicator">● ● ●</span>
|
||||
<ChatAreaThought/>
|
||||
{:else}
|
||||
<!-- Static message: use traditional rendering -->
|
||||
{@html getStaticHTML(message)}
|
||||
<!-- Static message: use traditional rendering -->
|
||||
{@html getStaticHTML(message)}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
53
Components/ChatAreaThought.svelte
Normal file
53
Components/ChatAreaThought.svelte
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<script lang="ts">
|
||||
import { AIThoughtMessage } from "Messages/AIThoughtMessage";
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import type { MessageService } from "Services/MessageService";
|
||||
import { Services } from "Services/Services";
|
||||
import { onDestroy } from "svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
let messageService: MessageService = Resolve(Services.MessageService);
|
||||
let currentThought: string | null = null;
|
||||
let isVisible: boolean = false;
|
||||
|
||||
// Handler for AI thought messages
|
||||
function handleAIThought(message: AIThoughtMessage): void {
|
||||
currentThought = message.thought;
|
||||
isVisible = currentThought !== null && currentThought.trim().length > 0;
|
||||
}
|
||||
|
||||
messageService.register(AIThoughtMessage, handleAIThought);
|
||||
|
||||
onDestroy(() => {
|
||||
messageService.unregister(AIThoughtMessage, handleAIThought);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if isVisible && currentThought}
|
||||
<div class="ai-thought-container" in:fade={{ duration: 200 }} out:fade={{ duration: 200 }}>
|
||||
<div class="ai-thought-bubble">
|
||||
<span>{currentThought}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.ai-thought-container {
|
||||
margin-top: var(--size-2-1);
|
||||
margin-bottom: var(--size-2-1);
|
||||
}
|
||||
|
||||
.ai-thought-bubble {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: var(--metadata-background);
|
||||
border: var(--border-width) solid var(--metadata-border-color);
|
||||
border-radius: var(--metadata-border-radius);
|
||||
padding: var(--metadata-padding);
|
||||
font-size: var(--metadata-label-font-size);
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
max-width: 100%;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
7
Messages/AIThoughtMessage.ts
Normal file
7
Messages/AIThoughtMessage.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import type { IMessage } from "./IMessage";
|
||||
|
||||
export class AIThoughtMessage implements IMessage {
|
||||
constructor(public thought: string | null) {}
|
||||
invoke(): void {
|
||||
}
|
||||
}
|
||||
3
Messages/IMessage.ts
Normal file
3
Messages/IMessage.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export interface IMessage {
|
||||
invoke(): void;
|
||||
}
|
||||
29
Services/MessageService.ts
Normal file
29
Services/MessageService.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import type { IMessage } from "Messages/IMessage";
|
||||
|
||||
type MessageConstructor<T extends IMessage> = new (...args: any[]) => T;
|
||||
|
||||
export class MessageService {
|
||||
private handlers = new Map<MessageConstructor<any>, Set<(message: any) => void>>();
|
||||
|
||||
register<T extends IMessage>(messageType: MessageConstructor<T>, 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<T extends IMessage>(messageType: MessageConstructor<T>, handler: (message: T) => void): void {
|
||||
this.handlers.get(messageType)?.delete(handler);
|
||||
}
|
||||
|
||||
send<T extends IMessage>(message: T): void {
|
||||
const messageType = message.constructor as MessageConstructor<T>;
|
||||
const handlers = this.handlers.get(messageType);
|
||||
handlers?.forEach(handler => handler(message));
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export class Services {
|
||||
static MessageService = Symbol("MessageService");
|
||||
static DmsAssistantPlugin = Symbol("DmsAssistantPlugin");
|
||||
static OdbCache = Symbol("OdbCache");
|
||||
static ModalService = Symbol("ModalService");
|
||||
|
|
|
|||
Loading…
Reference in a new issue