Change thought component to use svelte binding.

Add styling to thought.
This commit is contained in:
Andrew Beal 2025-10-07 22:27:51 +01:00
parent 7cdd4600e4
commit f5a3a513ec
3 changed files with 79 additions and 57 deletions

View file

@ -10,6 +10,7 @@
import type { ConversationContent } from "Conversations/ConversationContent";
export let messages: ConversationContent[] = [];
export let currentThought: string | null = null;
export let isStreaming: boolean = false;
export let isSubmitting: boolean = false;
export let chatContainer: HTMLDivElement;
@ -183,8 +184,8 @@
{/if}
{/each}
<ChatAreaThought thought={currentThought}/>
{#if isSubmitting}
<ChatAreaThought/>
<StreamingIndicator/>
{/if}

View file

@ -1,53 +1,73 @@
<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>
import { fade } from "svelte/transition";
export let thought: string | null = null;
$: isVisible = thought !== null && thought.trim().length > 0;
</script>
{#if isVisible && thought}
<div class="ai-thought-container" in:fade={{ duration: 200 }} out:fade={{ duration: 200 }}>
<div class="ai-thought-bubble">
<span>{thought}</span>
</div>
{/if}
<style>
.ai-thought-container {
margin-top: var(--size-2-1);
margin-bottom: var(--size-2-1);
</div>
{/if}
<style>
.ai-thought-container {
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
.ai-thought-bubble {
--border-width: 1px;
position: relative;
display: inline-flex;
align-items: center;
border-radius: 10px;
max-width: 100%;
}
.ai-thought-bubble span {
position: relative;
z-index: 1;
background: var(--color-base-05);
border-radius: 10px;
padding: 0.75rem 1rem;
font-size: 0.875rem;
color: var(--text-muted);
font-style: italic;
word-wrap: break-word;
width: 100%;
}
.ai-thought-bubble::after {
position: absolute;
content: "";
top: calc(-1 * var(--border-width));
left: calc(-1 * var(--border-width));
z-index: 0;
width: calc(100% + var(--border-width) * 2);
height: calc(100% + var(--border-width) * 2);
background: linear-gradient(
60deg,
hsl(224, 85%, 66%),
hsl(269, 85%, 66%),
hsl(314, 85%, 66%),
hsl(359, 85%, 66%),
hsl(44, 85%, 66%),
hsl(89, 85%, 66%),
hsl(134, 85%, 66%),
hsl(179, 85%, 66%)
);
background-size: 300% 300%;
background-position: 0 50%;
border-radius: 10px;
animation: moveGradient 3s alternate infinite;
}
@keyframes moveGradient {
50% {
background-position: 100% 50%;
}
.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>
}
</style>

View file

@ -30,6 +30,8 @@
let conversation = new Conversation();
let currentThought: string | null = null;
async function handleSubmit() {
if (!await semaphore.wait()) {
return;
@ -54,9 +56,7 @@
while (functionCall) {
if ('user_message' in functionCall.arguments) {
conversation.contents = [...conversation.contents, new ConversationContent(
Role.Assistant, functionCall.arguments.user_message)];
await conversationService.saveConversation(conversation);
currentThought = functionCall.arguments.user_message
}
conversation.contents = [...conversation.contents, new ConversationContent(
@ -71,8 +71,9 @@
functionCall = await streamRequestResponse();
}
} finally {
semaphore.release();
currentThought = null;
isSubmitting = false;
semaphore.release();
tick().then(() => {
textareaElement?.focus();
});
@ -186,7 +187,7 @@
<main class="container">
<div id="chat-container">
<ChatArea messages={conversation.contents} bind:isStreaming bind:isSubmitting bind:chatContainer={chatContainer}/>
<ChatArea messages={conversation.contents} bind:currentThought bind:isStreaming bind:isSubmitting bind:chatContainer={chatContainer}/>
</div>
<div id="input-container">
@ -214,7 +215,7 @@
<style>
.container {
display: grid;
grid-template-rows: 1fr auto;
grid-template-rows: 1fr auto var(--size-2-1);
grid-template-columns: 1fr;
height: calc(100% - var(--size-4-16));
border-radius: var(--radius-m);