open-webui/src/lib/components/chat/MessageInput/Models.svelte
2024-03-04 11:53:09 +01:00

167 lines
3.9 KiB
Svelte

<script lang="ts">
import { generatePrompt } from '$lib/apis/ollama';
import { models } from '$lib/stores';
import { splitStream } from '$lib/utils';
import { tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let prompt = '';
export let user = null;
export let chatInputPlaceholder = '';
export let messages = [];
let selectedIdx = 0;
let filteredModels = [];
$: filteredModels = $models
.filter(
(p) =>
p.name !== 'hr' &&
!p.external &&
p.name.includes(prompt.split(' ')?.at(0)?.substring(1) ?? '')
)
.sort((a, b) => a.name.localeCompare(b.name));
$: if (prompt) {
selectedIdx = 0;
}
export const selectUp = () => {
selectedIdx = Math.max(0, selectedIdx - 1);
};
export const selectDown = () => {
selectedIdx = Math.min(selectedIdx + 1, filteredModels.length - 1);
};
const confirmSelect = async (model) => {
// dispatch('select', model);
prompt = '';
user = JSON.parse(JSON.stringify(model.name));
await tick();
chatInputPlaceholder = $i18n.t('{{modelName}} is thinking...', { modelName: model.name });
const chatInputElement = document.getElementById('chat-textarea');
await tick();
chatInputElement?.focus();
await tick();
const convoText = messages.reduce((a, message, i, arr) => {
return `${a}### ${message.role.toUpperCase()}\n${message.content}\n\n`;
}, '');
const res = await generatePrompt(localStorage.token, model.name, convoText);
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) {
break;
}
try {
let lines = value.split('\n');
for (const line of lines) {
if (line !== '') {
console.log(line);
let data = JSON.parse(line);
if ('detail' in data) {
throw data;
}
if ('id' in data) {
console.log(data);
} else {
if (data.done == false) {
if (prompt == '' && data.response == '\n') {
continue;
} else {
prompt += data.response;
console.log(data.response);
chatInputElement.scrollTop = chatInputElement.scrollHeight;
await tick();
}
}
}
}
}
} catch (error) {
console.log(error);
if ('detail' in error) {
toast.error(error.detail);
}
break;
}
}
} else {
if (res !== null) {
const error = await res.json();
console.log(error);
if ('detail' in error) {
toast.error(error.detail);
} else {
toast.error(error.error);
}
} else {
toast.error(
$i18n.t('Uh-oh! There was an issue connecting to {{provider}}.', { provider: 'llama' })
);
}
}
chatInputPlaceholder = '';
console.log(user);
};
</script>
{#if filteredModels.length > 0}
<div class="md:px-2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
<div class="flex w-full px-2">
<div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-xl text-center">
<div class=" text-lg font-semibold mt-2">@</div>
</div>
<div class="max-h-60 flex flex-col w-full rounded-r-xl bg-white">
<div class="m-1 overflow-y-auto p-1 rounded-r-xl space-y-0.5">
{#each filteredModels as model, modelIdx}
<button
class=" px-3 py-1.5 rounded-xl w-full text-left {modelIdx === selectedIdx
? ' bg-gray-100 selected-command-option-button'
: ''}"
type="button"
on:click={() => {
confirmSelect(model);
}}
on:mousemove={() => {
selectedIdx = modelIdx;
}}
on:focus={() => {}}
>
<div class=" font-medium text-black line-clamp-1">
{model.name}
</div>
<!-- <div class=" text-xs text-gray-600 line-clamp-1">
{doc.title}
</div> -->
</button>
{/each}
</div>
</div>
</div>
</div>
{/if}