|
<script lang="ts"> |
|
import { toast } from 'svelte-sonner'; |
|
|
|
import { goto } from '$app/navigation'; |
|
import { onMount, tick, getContext } from 'svelte'; |
|
|
|
import { WEBUI_BASE_URL } from '$lib/constants'; |
|
import { WEBUI_NAME, config, user, models, settings, showSidebar } from '$lib/stores'; |
|
import { generateOpenAIChatCompletion } from '$lib/apis/openai'; |
|
|
|
import { splitStream } from '$lib/utils'; |
|
import Selector from '$lib/components/chat/ModelSelector/Selector.svelte'; |
|
import MenuLines from '../icons/MenuLines.svelte'; |
|
|
|
const i18n = getContext('i18n'); |
|
|
|
let loaded = false; |
|
let text = ''; |
|
|
|
let selectedModelId = ''; |
|
|
|
let loading = false; |
|
let stopResponseFlag = false; |
|
|
|
let textCompletionAreaElement: HTMLTextAreaElement; |
|
|
|
const scrollToBottom = () => { |
|
const element = textCompletionAreaElement; |
|
|
|
if (element) { |
|
element.scrollTop = element?.scrollHeight; |
|
} |
|
}; |
|
|
|
const stopResponse = () => { |
|
stopResponseFlag = true; |
|
console.log('stopResponse'); |
|
}; |
|
|
|
const textCompletionHandler = async () => { |
|
const model = $models.find((model) => model.id === selectedModelId); |
|
|
|
const [res, controller] = await generateOpenAIChatCompletion( |
|
localStorage.token, |
|
{ |
|
model: model.id, |
|
stream: true, |
|
messages: [ |
|
{ |
|
role: 'assistant', |
|
content: text |
|
} |
|
] |
|
}, |
|
`${WEBUI_BASE_URL}/api` |
|
); |
|
|
|
if (res && res.ok) { |
|
const reader = res.body |
|
.pipeThrough(new TextDecoderStream()) |
|
.pipeThrough(splitStream('\n')) |
|
.getReader(); |
|
|
|
while (true) { |
|
const { value, done } = await reader.read(); |
|
if (done || stopResponseFlag) { |
|
if (stopResponseFlag) { |
|
controller.abort('User: Stop Response'); |
|
} |
|
break; |
|
} |
|
|
|
try { |
|
let lines = value.split('\n'); |
|
|
|
for (const line of lines) { |
|
if (line !== '') { |
|
if (line.includes('[DONE]')) { |
|
console.log('done'); |
|
} else { |
|
let data = JSON.parse(line.replace(/^data: /, '')); |
|
console.log(data); |
|
|
|
text += data.choices[0].delta.content ?? ''; |
|
} |
|
} |
|
} |
|
} catch (error) { |
|
console.log(error); |
|
} |
|
|
|
scrollToBottom(); |
|
} |
|
} |
|
}; |
|
|
|
const submitHandler = async () => { |
|
if (selectedModelId) { |
|
loading = true; |
|
await textCompletionHandler(); |
|
|
|
loading = false; |
|
stopResponseFlag = false; |
|
} |
|
}; |
|
|
|
onMount(async () => { |
|
if ($user?.role !== 'admin') { |
|
await goto('/'); |
|
} |
|
|
|
if ($settings?.models) { |
|
selectedModelId = $settings?.models[0]; |
|
} else if ($config?.default_models) { |
|
selectedModelId = $config?.default_models.split(',')[0]; |
|
} else { |
|
selectedModelId = ''; |
|
} |
|
loaded = true; |
|
}); |
|
</script> |
|
|
|
<div class=" flex flex-col justify-between w-full overflow-y-auto h-full"> |
|
<div class="mx-auto w-full md:px-0 h-full"> |
|
<div class=" flex flex-col h-full px-4"> |
|
<div class="flex flex-col justify-between mb-1 gap-1"> |
|
<div class="flex flex-col gap-1 w-full"> |
|
<div class="flex w-full"> |
|
<div class="overflow-hidden w-full"> |
|
<div class="max-w-full"> |
|
<Selector |
|
placeholder={$i18n.t('Select a model')} |
|
items={$models.map((model) => ({ |
|
value: model.id, |
|
label: model.name, |
|
model: model |
|
}))} |
|
bind:value={selectedModelId} |
|
/> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div |
|
class=" pt-0.5 pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0" |
|
id="messages-container" |
|
> |
|
<div class=" h-full w-full flex flex-col"> |
|
<div class="flex-1"> |
|
<textarea |
|
id="text-completion-textarea" |
|
bind:this={textCompletionAreaElement} |
|
class="w-full h-full p-3 bg-transparent border border-gray-50 dark:border-gray-850 outline-none resize-none rounded-lg text-sm" |
|
bind:value={text} |
|
placeholder={$i18n.t("You're a helpful assistant.")} |
|
/> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="pb-3 flex justify-end"> |
|
{#if !loading} |
|
<button |
|
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full" |
|
on:click={() => { |
|
submitHandler(); |
|
}} |
|
> |
|
{$i18n.t('Run')} |
|
</button> |
|
{:else} |
|
<button |
|
class="px-3 py-1.5 text-sm font-medium bg-gray-300 text-black transition rounded-full" |
|
on:click={() => { |
|
stopResponse(); |
|
}} |
|
> |
|
{$i18n.t('Cancel')} |
|
</button> |
|
{/if} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<style> |
|
.scrollbar-hidden::-webkit-scrollbar { |
|
display: none; |
|
} |
|
|
|
.scrollbar-hidden { |
|
-ms-overflow-style: none; |
|
scrollbar-width: none; |
|
} |
|
</style> |
|
|