diff --git a/AIClasses/Claude/Claude.ts b/AIClasses/Claude/Claude.ts index 1cd07d4..4d746df 100644 --- a/AIClasses/Claude/Claude.ts +++ b/AIClasses/Claude/Claude.ts @@ -8,7 +8,6 @@ import type { IAIFunctionDefinition } from "AIClasses/FunctionDefinitions/IAIFun import type { ConversationContent } from "Conversations/ConversationContent"; import { Role } from "Enums/Role"; import type { RawMessageStreamEvent, ContentBlockParam, Tool } from '@anthropic-ai/sdk/resources/messages'; -import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/Schemas/AIFunctionTypes"; import { Exception } from "Helpers/Exception"; export class Claude extends BaseAIClass { diff --git a/AIClasses/OpenAI/OpenAI.ts b/AIClasses/OpenAI/OpenAI.ts index 0c01fde..d929f3d 100644 --- a/AIClasses/OpenAI/OpenAI.ts +++ b/AIClasses/OpenAI/OpenAI.ts @@ -6,7 +6,6 @@ import { AIProvider, AIProviderURL, toProviderModel } from "Enums/ApiProvider"; import { AIFunctionCall } from "AIClasses/AIFunctionCall"; import { fromString as aiFunctionFromString } from "Enums/AIFunction"; import type { IAIFunctionDefinition } from "AIClasses/FunctionDefinitions/IAIFunctionDefinition"; -import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/Schemas/AIFunctionTypes"; import type { ResponseEvent, ResponseOutputTextDelta, ResponseOutputItemDone, ResponseErrorEvent, ResponseFailedEvent, OpenAIFunctionTool, ResponsesAPIInput } from "./OpenAITypes"; import { Exception } from "Helpers/Exception"; import { ApiErrorType } from "Types/ApiError"; diff --git a/Components/ChatArea.svelte b/Components/ChatArea.svelte index 9e411b0..ce92917 100644 --- a/Components/ChatArea.svelte +++ b/Components/ChatArea.svelte @@ -31,10 +31,8 @@ chatContainer.scroll({ top: 0, behavior: "instant" }); } - export function updateChatAreaLayout(behavior: ScrollBehavior | undefined) { + export function updateChatAreaLayout(behavior: ScrollBehavior | undefined, shouldSettle: boolean = false) { tick().then(() => { - settled = false; - if (messageElements.length <= 0 || !chatAreaPaddingElement) { if (chatAreaPaddingElement) { chatAreaPaddingElement.style.padding = "0px"; @@ -46,25 +44,39 @@ const paddingBottom = parseFloat(getComputedStyle(chatContainer).paddingBottom) || 0; const messageElement = messageElements.sort((a, b) => a.index - b.index)[messageElements.length - 1]; - const messageSpace = messageElement.element.offsetHeight; + let messageSpace = messageElement.element.offsetHeight; - const padding = chatContainer.offsetHeight - paddingTop - paddingBottom - messageSpace; + if (!shouldSettle) { + const gap = parseFloat(getComputedStyle(chatContainer).gap) || 0; + + if (thoughtIndicatorElement) { + messageSpace += thoughtIndicatorElement.offsetHeight + gap + gap; + } + if (streamingIndicatorElement) { + messageSpace += streamingIndicatorElement.offsetHeight + gap + gap; + } + } + + let padding = chatContainer.offsetHeight - paddingTop - paddingBottom - messageSpace; + if (!shouldSettle) { + padding = Math.max(padding, chatContainer.offsetHeight * 0.25); + } chatAreaPaddingElement.style.padding = `${Math.max(0, padding / 2)}px`; tick().then(() => { if (autoScroll && behavior) { chatContainer.scroll({ top: chatContainer.scrollHeight, behavior: behavior }) } - tick().then(() => settled = true); }); }); } - let settled: boolean = false; let autoScroll: boolean = true; let lastScrollTop: number = 0; let chatAreaPaddingElement: HTMLElement | undefined; + let thoughtIndicatorElement: HTMLElement | undefined; + let streamingIndicatorElement: HTMLElement | undefined; let streamingMarkdownService: StreamingMarkdownService = Resolve(Services.StreamingMarkdownService); @@ -138,6 +150,20 @@ messageElements.push({ index: index, element: element }); } + function observeResize(element: HTMLElement) { + const observer = new ResizeObserver(() => { + updateChatAreaLayout("smooth", false); + }); + + observer.observe(element); + + return { + destroy() { + observer.disconnect(); + } + }; + } + // decide if we should be auto scrolling function handleScroll() { if (!chatContainer) { @@ -196,7 +222,7 @@ } -
+
{#each messages as message, index} {#if !message.isFunctionCallResponse && message.content.trim() !== ""} {#if message.role === Role.User} @@ -222,11 +248,9 @@ {/if} {/each} - {#if settled} - - {#if isSubmitting} - - {/if} + + {#if isSubmitting} + {/if} {#if cancelling} diff --git a/Components/ChatWindow.svelte b/Components/ChatWindow.svelte index b7501b0..1d58739 100644 --- a/Components/ChatWindow.svelte +++ b/Components/ChatWindow.svelte @@ -89,7 +89,6 @@ function handleStop() { chatService.stop(); currentThought = null; - chatArea.updateChatAreaLayout("smooth"); } async function handleSubmit(userRequest: string, formattedRequest: string) { @@ -121,7 +120,7 @@ cancelling = false; isSubmitting = false; abortService.reset(); - chatArea.updateChatAreaLayout("smooth"); + chatArea.updateChatAreaLayout("smooth", true); await chatService.updateTokenDisplay(conversation); }, onCancel: () => { @@ -158,7 +157,7 @@ chatService.onNameChanged?.(loadedConversation.title); chatService.updateTokenDisplay(loadedConversation); conversationStore.clearLoadFlag(); - chatArea.updateChatAreaLayout("instant"); + chatArea.updateChatAreaLayout("instant", true); } }); } diff --git a/Components/StreamingIndicator.svelte b/Components/StreamingIndicator.svelte index 05988aa..4c0f0ab 100644 --- a/Components/StreamingIndicator.svelte +++ b/Components/StreamingIndicator.svelte @@ -1,8 +1,9 @@ -
+
diff --git a/Components/ThoughtIndicator.svelte b/Components/ThoughtIndicator.svelte index fd2f0b0..9fbf94d 100644 --- a/Components/ThoughtIndicator.svelte +++ b/Components/ThoughtIndicator.svelte @@ -1,11 +1,12 @@ {#if isVisible} -
+
{thought}