mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
refactor: improve chat layout updates and remove unused imports
- Remove unused StoredFunctionCall/Response imports from AI classes - Replace settled flag with shouldSettle parameter for better control - Add ResizeObserver to handle dynamic content size changes - Bind indicator elements for accurate height calculations - Update layout calculation to account for indicators when not settled
This commit is contained in:
parent
565bfdfda7
commit
187b831155
6 changed files with 43 additions and 20 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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<StreamingMarkdownService>(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 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="chat-area" bind:this={chatContainer} on:scroll={handleScroll}>
|
||||
<div class="chat-area" bind:this={chatContainer} on:scroll={handleScroll} use:observeResize>
|
||||
{#each messages as message, index}
|
||||
{#if !message.isFunctionCallResponse && message.content.trim() !== ""}
|
||||
{#if message.role === Role.User}
|
||||
|
|
@ -222,11 +248,9 @@
|
|||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if settled}
|
||||
<ThoughtIndicator thought={currentThought}/>
|
||||
{#if isSubmitting}
|
||||
<StreamingIndicator editModeActive={editModeActive}/>
|
||||
{/if}
|
||||
<ThoughtIndicator thought={currentThought} bind:thoughtIndicatorElement={thoughtIndicatorElement}/>
|
||||
{#if isSubmitting}
|
||||
<StreamingIndicator editModeActive={editModeActive} bind:streamingIndicatorElement={streamingIndicatorElement}/>
|
||||
{/if}
|
||||
|
||||
{#if cancelling}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
<script lang="ts">
|
||||
export let editModeActive: boolean = false;
|
||||
export let streamingIndicatorElement: HTMLElement | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div class="loader" class:edit-mode={editModeActive}>
|
||||
<div class="loader" class:edit-mode={editModeActive} bind:this={streamingIndicatorElement}>
|
||||
<div class="circle">
|
||||
<div class="dot"></div>
|
||||
<div class="outline"></div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { fade } from "svelte/transition";
|
||||
export let thought: string | null = null;
|
||||
export let thoughtIndicatorElement: HTMLElement | undefined = undefined;
|
||||
$: isVisible = thought !== null && thought.trim().length > 0;
|
||||
</script>
|
||||
|
||||
{#if isVisible}
|
||||
<div class="ai-thought-container" in:fade={{ duration: 200 }} out:fade={{ duration: 200 }}>
|
||||
<div class="ai-thought-container" in:fade={{ duration: 200 }} out:fade={{ duration: 200 }} bind:this={thoughtIndicatorElement}>
|
||||
<div class="ai-thought-bubble">
|
||||
<span>{thought}</span>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue