add loading icon + pending state when assistant message is pending (#48)
Browse files* add loading icon + pending state when assistant message is pending
* remove dead code
Co-authored-by: Eliott C. <coyotte508@gmail.com>
* add loading to messages if a token takes a while to come
* add dom utils
---------
Co-authored-by: Eliott C. <coyotte508@gmail.com>
- src/lib/components/chat/ChatMessage.svelte +41 -10
 - src/lib/components/chat/ChatMessages.svelte +7 -2
 - src/lib/components/chat/ChatWindow.svelte +7 -10
 - src/lib/components/icons/IconLoading.svelte +31 -0
 - src/lib/utils/dom.ts +7 -0
 - src/routes/+page.svelte +1 -1
 - src/routes/conversation/[id]/+page.svelte +5 -1
 
    	
        src/lib/components/chat/ChatMessage.svelte
    CHANGED
    
    | 
         @@ -1,15 +1,22 @@ 
     | 
|
| 1 | 
         
             
            <script lang="ts">
         
     | 
| 2 | 
         
             
            	import { marked } from 'marked';
         
     | 
| 3 | 
         
             
            	import type { Message } from '$lib/types/Message';
         
     | 
| 
         | 
|
| 
         | 
|
| 4 | 
         | 
| 5 | 
         
             
            	import CodeBlock from '../CodeBlock.svelte';
         
     | 
| 
         | 
|
| 6 | 
         | 
| 7 | 
         
             
            	function sanitizeMd(md: string) {
         
     | 
| 8 | 
         
             
            		return md.replaceAll('<', '<');
         
     | 
| 9 | 
         
             
            	}
         
     | 
| 10 | 
         | 
| 11 | 
         
             
            	export let message: Message;
         
     | 
| 12 | 
         
            -
            	let  
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 13 | 
         | 
| 14 | 
         
             
            	const options: marked.MarkedOptions = {
         
     | 
| 15 | 
         
             
            		...marked.getDefaults(),
         
     | 
| 
         @@ -17,6 +24,23 @@ 
     | 
|
| 17 | 
         
             
            	};
         
     | 
| 18 | 
         | 
| 19 | 
         
             
            	$: tokens = marked.lexer(sanitizeMd(message.content));
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 20 | 
         
             
            </script>
         
     | 
| 21 | 
         | 
| 22 | 
         
             
            {#if message.from === 'assistant'}
         
     | 
| 
         @@ -27,16 +51,23 @@ 
     | 
|
| 27 | 
         
             
            			class="mt-5 w-3 h-3 flex-none rounded-full shadow-lg"
         
     | 
| 28 | 
         
             
            		/>
         
     | 
| 29 | 
         
             
            		<div
         
     | 
| 30 | 
         
            -
            			class=" 
     | 
| 31 | 
         
            -
            			bind:this={el}
         
     | 
| 32 | 
         
             
            		>
         
     | 
| 33 | 
         
            -
            			{# 
     | 
| 34 | 
         
            -
            				 
     | 
| 35 | 
         
            -
             
     | 
| 36 | 
         
            -
             
     | 
| 37 | 
         
            -
             
     | 
| 38 | 
         
            -
            				{ 
     | 
| 39 | 
         
            -
            			 
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 40 | 
         
             
            		</div>
         
     | 
| 41 | 
         
             
            	</div>
         
     | 
| 42 | 
         
             
            {/if}
         
     | 
| 
         | 
|
| 1 | 
         
             
            <script lang="ts">
         
     | 
| 2 | 
         
             
            	import { marked } from 'marked';
         
     | 
| 3 | 
         
             
            	import type { Message } from '$lib/types/Message';
         
     | 
| 4 | 
         
            +
            	import { afterUpdate } from 'svelte';
         
     | 
| 5 | 
         
            +
            	import { deepestChild } from '$lib/utils/dom';
         
     | 
| 6 | 
         | 
| 7 | 
         
             
            	import CodeBlock from '../CodeBlock.svelte';
         
     | 
| 8 | 
         
            +
            	import IconLoading from '../icons/IconLoading.svelte';
         
     | 
| 9 | 
         | 
| 10 | 
         
             
            	function sanitizeMd(md: string) {
         
     | 
| 11 | 
         
             
            		return md.replaceAll('<', '<');
         
     | 
| 12 | 
         
             
            	}
         
     | 
| 13 | 
         | 
| 14 | 
         
             
            	export let message: Message;
         
     | 
| 15 | 
         
            +
            	export let loading: boolean = false;
         
     | 
| 16 | 
         
            +
             
     | 
| 17 | 
         
            +
            	let contentEl: HTMLElement;
         
     | 
| 18 | 
         
            +
            	let loadingEl: any;
         
     | 
| 19 | 
         
            +
            	let pendingTimeout: NodeJS.Timeout;
         
     | 
| 20 | 
         | 
| 21 | 
         
             
            	const options: marked.MarkedOptions = {
         
     | 
| 22 | 
         
             
            		...marked.getDefaults(),
         
     | 
| 
         | 
|
| 24 | 
         
             
            	};
         
     | 
| 25 | 
         | 
| 26 | 
         
             
            	$: tokens = marked.lexer(sanitizeMd(message.content));
         
     | 
| 27 | 
         
            +
             
     | 
| 28 | 
         
            +
            	afterUpdate(() => {
         
     | 
| 29 | 
         
            +
            		loadingEl?.$destroy();
         
     | 
| 30 | 
         
            +
            		clearTimeout(pendingTimeout);
         
     | 
| 31 | 
         
            +
             
     | 
| 32 | 
         
            +
            		// Add loading animation to the last message if update takes more than 600ms
         
     | 
| 33 | 
         
            +
            		if (loading) {
         
     | 
| 34 | 
         
            +
            			pendingTimeout = setTimeout(() => {
         
     | 
| 35 | 
         
            +
            				if (contentEl) {
         
     | 
| 36 | 
         
            +
            					loadingEl = new IconLoading({
         
     | 
| 37 | 
         
            +
            						target: deepestChild(contentEl),
         
     | 
| 38 | 
         
            +
            						props: { classNames: 'loading inline ml-2' }
         
     | 
| 39 | 
         
            +
            					});
         
     | 
| 40 | 
         
            +
            				}
         
     | 
| 41 | 
         
            +
            			}, 600);
         
     | 
| 42 | 
         
            +
            		}
         
     | 
| 43 | 
         
            +
            	});
         
     | 
| 44 | 
         
             
            </script>
         
     | 
| 45 | 
         | 
| 46 | 
         
             
            {#if message.from === 'assistant'}
         
     | 
| 
         | 
|
| 51 | 
         
             
            			class="mt-5 w-3 h-3 flex-none rounded-full shadow-lg"
         
     | 
| 52 | 
         
             
            		/>
         
     | 
| 53 | 
         
             
            		<div
         
     | 
| 54 | 
         
            +
            			class="relative rounded-2xl px-5 py-3.5 border border-gray-100 bg-gradient-to-br from-gray-50 dark:from-gray-800/40 dark:border-gray-800 text-gray-600 dark:text-gray-300 min-h-[calc(2rem+theme(spacing[3.5])*2)] min-w-[100px]"
         
     | 
| 
         | 
|
| 55 | 
         
             
            		>
         
     | 
| 56 | 
         
            +
            			{#if !message.content}
         
     | 
| 57 | 
         
            +
            				<IconLoading classNames="absolute inset-0 m-auto" />
         
     | 
| 58 | 
         
            +
            			{/if}
         
     | 
| 59 | 
         
            +
            			<div
         
     | 
| 60 | 
         
            +
            				class="prose dark:prose-invert :prose-pre:bg-gray-100 dark:prose-pre:bg-gray-950"
         
     | 
| 61 | 
         
            +
            				bind:this={contentEl}
         
     | 
| 62 | 
         
            +
            			>
         
     | 
| 63 | 
         
            +
            				{#each tokens as token}
         
     | 
| 64 | 
         
            +
            					{#if token.type === 'code'}
         
     | 
| 65 | 
         
            +
            						<CodeBlock lang={token.lang} code={token.text} />
         
     | 
| 66 | 
         
            +
            					{:else}
         
     | 
| 67 | 
         
            +
            						{@html marked.parser([token], options)}
         
     | 
| 68 | 
         
            +
            					{/if}
         
     | 
| 69 | 
         
            +
            				{/each}
         
     | 
| 70 | 
         
            +
            			</div>
         
     | 
| 71 | 
         
             
            		</div>
         
     | 
| 72 | 
         
             
            	</div>
         
     | 
| 73 | 
         
             
            {/if}
         
     | 
    	
        src/lib/components/chat/ChatMessages.svelte
    CHANGED
    
    | 
         @@ -6,17 +6,22 @@ 
     | 
|
| 6 | 
         
             
            	import ChatMessage from './ChatMessage.svelte';
         
     | 
| 7 | 
         | 
| 8 | 
         
             
            	export let messages: Message[];
         
     | 
| 
         | 
|
| 
         | 
|
| 9 | 
         | 
| 10 | 
         
             
            	let chatContainer: HTMLElement;
         
     | 
| 11 | 
         
             
            </script>
         
     | 
| 12 | 
         | 
| 13 | 
         
             
            <div class="overflow-y-auto h-full" use:snapScrollToBottom={messages} bind:this={chatContainer}>
         
     | 
| 14 | 
         
             
            	<div class="max-w-3xl xl:max-w-4xl mx-auto px-5 pt-6 flex flex-col gap-8 h-full">
         
     | 
| 15 | 
         
            -
            		{#each messages as message}
         
     | 
| 16 | 
         
            -
            			<ChatMessage {message} />
         
     | 
| 17 | 
         
             
            		{:else}
         
     | 
| 18 | 
         
             
            			<ChatIntroduction on:message />
         
     | 
| 19 | 
         
             
            		{/each}
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 20 | 
         
             
            		<div class="h-32 flex-none" />
         
     | 
| 21 | 
         
             
            	</div>
         
     | 
| 22 | 
         
             
            	<ScrollToBottomBtn class="bottom-10 right-12" scrollNode={chatContainer} />
         
     | 
| 
         | 
|
| 6 | 
         
             
            	import ChatMessage from './ChatMessage.svelte';
         
     | 
| 7 | 
         | 
| 8 | 
         
             
            	export let messages: Message[];
         
     | 
| 9 | 
         
            +
            	export let loading: boolean;
         
     | 
| 10 | 
         
            +
            	export let pending: boolean;
         
     | 
| 11 | 
         | 
| 12 | 
         
             
            	let chatContainer: HTMLElement;
         
     | 
| 13 | 
         
             
            </script>
         
     | 
| 14 | 
         | 
| 15 | 
         
             
            <div class="overflow-y-auto h-full" use:snapScrollToBottom={messages} bind:this={chatContainer}>
         
     | 
| 16 | 
         
             
            	<div class="max-w-3xl xl:max-w-4xl mx-auto px-5 pt-6 flex flex-col gap-8 h-full">
         
     | 
| 17 | 
         
            +
            		{#each messages as message, i}
         
     | 
| 18 | 
         
            +
            			<ChatMessage loading={loading && i === messages.length - 1} {message} />
         
     | 
| 19 | 
         
             
            		{:else}
         
     | 
| 20 | 
         
             
            			<ChatIntroduction on:message />
         
     | 
| 21 | 
         
             
            		{/each}
         
     | 
| 22 | 
         
            +
            		{#if pending}
         
     | 
| 23 | 
         
            +
            			<ChatMessage message={{ from: 'assistant', content: '' }} />
         
     | 
| 24 | 
         
            +
            		{/if}
         
     | 
| 25 | 
         
             
            		<div class="h-32 flex-none" />
         
     | 
| 26 | 
         
             
            	</div>
         
     | 
| 27 | 
         
             
            	<ScrollToBottomBtn class="bottom-10 right-12" scrollNode={chatContainer} />
         
     | 
    	
        src/lib/components/chat/ChatWindow.svelte
    CHANGED
    
    | 
         @@ -8,7 +8,9 @@ 
     | 
|
| 8 | 
         
             
            	import ChatInput from './ChatInput.svelte';
         
     | 
| 9 | 
         | 
| 10 | 
         
             
            	export let messages: Message[] = [];
         
     | 
| 11 | 
         
            -
            	export let disabled: boolean;
         
     | 
| 
         | 
|
| 
         | 
|
| 12 | 
         | 
| 13 | 
         
             
            	let message: string;
         
     | 
| 14 | 
         | 
| 
         @@ -21,28 +23,23 @@ 
     | 
|
| 21 | 
         
             
            		<button>New Chat</button>
         
     | 
| 22 | 
         
             
            		<button>+</button>
         
     | 
| 23 | 
         
             
            	</nav>
         
     | 
| 24 | 
         
            -
            	<ChatMessages {messages} on:message />
         
     | 
| 25 | 
         
             
            	<div
         
     | 
| 26 | 
         
             
            		class="flex max-md:border-t dark:border-gray-800 items-center max-md:dark:bg-gray-900 max-md:bg-white bg-gradient-to-t from-white dark:from-gray-900 to-transparent justify-center absolute inset-x-0 max-w-3xl xl:max-w-4xl mx-auto px-5 bottom-0 py-4 md:py-8 w-full"
         
     | 
| 27 | 
         
             
            	>
         
     | 
| 28 | 
         
             
            		<form
         
     | 
| 29 | 
         
             
            			on:submit|preventDefault={() => {
         
     | 
| 
         | 
|
| 30 | 
         
             
            				dispatch('message', message);
         
     | 
| 31 | 
         
             
            				message = '';
         
     | 
| 32 | 
         
             
            			}}
         
     | 
| 33 | 
         
             
            			class="w-full relative flex items-center rounded-xl flex-1 max-w-4xl border bg-gray-100 focus-within:border-gray-300 dark:bg-gray-700 dark:border-gray-600 dark:focus-within:border-gray-500 transition-all"
         
     | 
| 34 | 
         
             
            		>
         
     | 
| 35 | 
         
             
            			<div class="w-full flex flex-1 border-none bg-transparent">
         
     | 
| 36 | 
         
            -
            				<ChatInput
         
     | 
| 37 | 
         
            -
            					placeholder="Ask anything"
         
     | 
| 38 | 
         
            -
            					bind:value={message}
         
     | 
| 39 | 
         
            -
            					{disabled}
         
     | 
| 40 | 
         
            -
            					autofocus
         
     | 
| 41 | 
         
            -
            					maxRows={10}
         
     | 
| 42 | 
         
            -
            				/>
         
     | 
| 43 | 
         
             
            				<button
         
     | 
| 44 | 
         
             
            					class="p-1 px-[0.7rem] group self-end my-1 h-[2.4rem] rounded-lg hover:bg-gray-100 enabled:dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent disabled:opacity-60 dark:disabled:opacity-40 flex-shrink-0 transition-all mx-1"
         
     | 
| 45 | 
         
            -
            					disabled={!message || disabled}
         
     | 
| 46 | 
         
             
            					type="submit"
         
     | 
| 47 | 
         
             
            				>
         
     | 
| 48 | 
         
             
            					<CarbonSendAltFilled
         
     | 
| 
         | 
|
| 8 | 
         
             
            	import ChatInput from './ChatInput.svelte';
         
     | 
| 9 | 
         | 
| 10 | 
         
             
            	export let messages: Message[] = [];
         
     | 
| 11 | 
         
            +
            	export let disabled: boolean = false;
         
     | 
| 12 | 
         
            +
            	export let loading: boolean = false;
         
     | 
| 13 | 
         
            +
            	export let pending: boolean = false;
         
     | 
| 14 | 
         | 
| 15 | 
         
             
            	let message: string;
         
     | 
| 16 | 
         | 
| 
         | 
|
| 23 | 
         
             
            		<button>New Chat</button>
         
     | 
| 24 | 
         
             
            		<button>+</button>
         
     | 
| 25 | 
         
             
            	</nav>
         
     | 
| 26 | 
         
            +
            	<ChatMessages {loading} {pending} {messages} on:message />
         
     | 
| 27 | 
         
             
            	<div
         
     | 
| 28 | 
         
             
            		class="flex max-md:border-t dark:border-gray-800 items-center max-md:dark:bg-gray-900 max-md:bg-white bg-gradient-to-t from-white dark:from-gray-900 to-transparent justify-center absolute inset-x-0 max-w-3xl xl:max-w-4xl mx-auto px-5 bottom-0 py-4 md:py-8 w-full"
         
     | 
| 29 | 
         
             
            	>
         
     | 
| 30 | 
         
             
            		<form
         
     | 
| 31 | 
         
             
            			on:submit|preventDefault={() => {
         
     | 
| 32 | 
         
            +
            				if (loading) return;
         
     | 
| 33 | 
         
             
            				dispatch('message', message);
         
     | 
| 34 | 
         
             
            				message = '';
         
     | 
| 35 | 
         
             
            			}}
         
     | 
| 36 | 
         
             
            			class="w-full relative flex items-center rounded-xl flex-1 max-w-4xl border bg-gray-100 focus-within:border-gray-300 dark:bg-gray-700 dark:border-gray-600 dark:focus-within:border-gray-500 transition-all"
         
     | 
| 37 | 
         
             
            		>
         
     | 
| 38 | 
         
             
            			<div class="w-full flex flex-1 border-none bg-transparent">
         
     | 
| 39 | 
         
            +
            				<ChatInput placeholder="Ask anything" bind:value={message} autofocus maxRows={10} />
         
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 40 | 
         
             
            				<button
         
     | 
| 41 | 
         
             
            					class="p-1 px-[0.7rem] group self-end my-1 h-[2.4rem] rounded-lg hover:bg-gray-100 enabled:dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent disabled:opacity-60 dark:disabled:opacity-40 flex-shrink-0 transition-all mx-1"
         
     | 
| 42 | 
         
            +
            					disabled={!message || loading || disabled}
         
     | 
| 43 | 
         
             
            					type="submit"
         
     | 
| 44 | 
         
             
            				>
         
     | 
| 45 | 
         
             
            					<CarbonSendAltFilled
         
     | 
    	
        src/lib/components/icons/IconLoading.svelte
    ADDED
    
    | 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            <script lang="ts">
         
     | 
| 2 | 
         
            +
            	export let classNames: string = '';
         
     | 
| 3 | 
         
            +
            </script>
         
     | 
| 4 | 
         
            +
             
     | 
| 5 | 
         
            +
            <svg
         
     | 
| 6 | 
         
            +
            	xmlns="http://www.w3.org/2000/svg"
         
     | 
| 7 | 
         
            +
            	width="40px"
         
     | 
| 8 | 
         
            +
            	height="25px"
         
     | 
| 9 | 
         
            +
            	viewBox="0 0 60 40"
         
     | 
| 10 | 
         
            +
            	preserveAspectRatio="xMidYMid"
         
     | 
| 11 | 
         
            +
            	class={classNames}
         
     | 
| 12 | 
         
            +
            >
         
     | 
| 13 | 
         
            +
            	{#each Array(3) as _, index}
         
     | 
| 14 | 
         
            +
            		<g transform={`translate(${20 * index + 10} 20)`}>
         
     | 
| 15 | 
         
            +
            			{index}
         
     | 
| 16 | 
         
            +
            			<circle cx="0" cy="0" r="6" fill="currentColor">
         
     | 
| 17 | 
         
            +
            				<animateTransform
         
     | 
| 18 | 
         
            +
            					attributeName="transform"
         
     | 
| 19 | 
         
            +
            					type="scale"
         
     | 
| 20 | 
         
            +
            					begin={`${-0.375 + 0.15 * index}s`}
         
     | 
| 21 | 
         
            +
            					calcMode="spline"
         
     | 
| 22 | 
         
            +
            					keySplines="0.3 0 0.7 1;0.3 0 0.7 1"
         
     | 
| 23 | 
         
            +
            					values="0.5;1;0.5"
         
     | 
| 24 | 
         
            +
            					keyTimes="0;0.5;1"
         
     | 
| 25 | 
         
            +
            					dur="1s"
         
     | 
| 26 | 
         
            +
            					repeatCount="indefinite"
         
     | 
| 27 | 
         
            +
            				/>
         
     | 
| 28 | 
         
            +
            			</circle>
         
     | 
| 29 | 
         
            +
            		</g>
         
     | 
| 30 | 
         
            +
            	{/each}
         
     | 
| 31 | 
         
            +
            </svg>
         
     | 
    	
        src/lib/utils/dom.ts
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
| 
         | 
|
| 1 | 
         
            +
            export function deepestChild(el: HTMLElement) {
         
     | 
| 2 | 
         
            +
            	let newEl = el;
         
     | 
| 3 | 
         
            +
            	while (newEl.hasChildNodes()) {
         
     | 
| 4 | 
         
            +
            		newEl = newEl.lastElementChild as HTMLElement;
         
     | 
| 5 | 
         
            +
            	}
         
     | 
| 6 | 
         
            +
            	return newEl;
         
     | 
| 7 | 
         
            +
            }
         
     | 
    	
        src/routes/+page.svelte
    CHANGED
    
    | 
         @@ -34,4 +34,4 @@ 
     | 
|
| 34 | 
         
             
            	}
         
     | 
| 35 | 
         
             
            </script>
         
     | 
| 36 | 
         | 
| 37 | 
         
            -
            <ChatWindow on:message={(ev) => createConversation(ev.detail)}  
     | 
| 
         | 
|
| 34 | 
         
             
            	}
         
     | 
| 35 | 
         
             
            </script>
         
     | 
| 36 | 
         | 
| 37 | 
         
            +
            <ChatWindow on:message={(ev) => createConversation(ev.detail)} {loading} />
         
     | 
    	
        src/routes/conversation/[id]/+page.svelte
    CHANGED
    
    | 
         @@ -14,6 +14,7 @@ 
     | 
|
| 14 | 
         
             
            	const hf = new HfInference();
         
     | 
| 15 | 
         | 
| 16 | 
         
             
            	let loading = false;
         
     | 
| 
         | 
|
| 17 | 
         | 
| 18 | 
         
             
            	async function getTextGenerationStream(inputs: string) {
         
     | 
| 19 | 
         
             
            		const response = hf.endpoint($page.url.href).textGenerationStream(
         
     | 
| 
         @@ -39,6 +40,8 @@ 
     | 
|
| 39 | 
         
             
            		);
         
     | 
| 40 | 
         | 
| 41 | 
         
             
            		for await (const data of response) {
         
     | 
| 
         | 
|
| 
         | 
|
| 42 | 
         
             
            			if (!data) break;
         
     | 
| 43 | 
         | 
| 44 | 
         
             
            			if (!data.token.special) {
         
     | 
| 
         @@ -60,6 +63,7 @@ 
     | 
|
| 60 | 
         | 
| 61 | 
         
             
            		try {
         
     | 
| 62 | 
         
             
            			loading = true;
         
     | 
| 
         | 
|
| 63 | 
         | 
| 64 | 
         
             
            			messages = [...messages, { from: 'user', content: message }];
         
     | 
| 65 | 
         | 
| 
         @@ -84,4 +88,4 @@ 
     | 
|
| 84 | 
         
             
            	});
         
     | 
| 85 | 
         
             
            </script>
         
     | 
| 86 | 
         | 
| 87 | 
         
            -
            <ChatWindow  
     | 
| 
         | 
|
| 14 | 
         
             
            	const hf = new HfInference();
         
     | 
| 15 | 
         | 
| 16 | 
         
             
            	let loading = false;
         
     | 
| 17 | 
         
            +
            	let pending = false;
         
     | 
| 18 | 
         | 
| 19 | 
         
             
            	async function getTextGenerationStream(inputs: string) {
         
     | 
| 20 | 
         
             
            		const response = hf.endpoint($page.url.href).textGenerationStream(
         
     | 
| 
         | 
|
| 40 | 
         
             
            		);
         
     | 
| 41 | 
         | 
| 42 | 
         
             
            		for await (const data of response) {
         
     | 
| 43 | 
         
            +
            			pending = false;
         
     | 
| 44 | 
         
            +
             
     | 
| 45 | 
         
             
            			if (!data) break;
         
     | 
| 46 | 
         | 
| 47 | 
         
             
            			if (!data.token.special) {
         
     | 
| 
         | 
|
| 63 | 
         | 
| 64 | 
         
             
            		try {
         
     | 
| 65 | 
         
             
            			loading = true;
         
     | 
| 66 | 
         
            +
            			pending = true;
         
     | 
| 67 | 
         | 
| 68 | 
         
             
            			messages = [...messages, { from: 'user', content: message }];
         
     | 
| 69 | 
         | 
| 
         | 
|
| 88 | 
         
             
            	});
         
     | 
| 89 | 
         
             
            </script>
         
     | 
| 90 | 
         | 
| 91 | 
         
            +
            <ChatWindow {loading} {pending} {messages} on:message={(message) => writeMessage(message.detail)} />
         
     |