|
<script lang="ts"> |
|
import { createEventDispatcher } from 'svelte'; |
|
|
|
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'); |
|
|
|
const dispatch = createEventDispatcher(); |
|
|
|
export let prompt = ''; |
|
export let user = null; |
|
|
|
export let chatInputPlaceholder = ''; |
|
export let messages = []; |
|
|
|
let selectedIdx = 0; |
|
let filteredModels = []; |
|
|
|
$: filteredModels = $models |
|
.filter((p) => |
|
p.name.toLowerCase().includes(prompt.toLowerCase().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) => { |
|
prompt = ''; |
|
dispatch('select', model); |
|
}; |
|
|
|
const confirmSelectCollaborativeChat = async (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 prompt.charAt(0) === '@'} |
|
{#if filteredModels.length > 0} |
|
<div class="pl-1 pr-12 mb-3 text-left w-full absolute bottom-0 left-0 right-0 z-10"> |
|
<div class="flex w-full dark:border dark:border-gray-850 rounded-lg"> |
|
<div class=" bg-gray-50 dark:bg-gray-850 w-10 rounded-l-lg text-center"> |
|
<div class=" text-lg font-semibold mt-2">@</div> |
|
</div> |
|
|
|
<div |
|
class="max-h-60 flex flex-col w-full rounded-r-lg bg-white dark:bg-gray-900 dark:text-gray-100" |
|
> |
|
<div class="m-1 overflow-y-auto p-1 rounded-r-lg space-y-0.5 scrollbar-hidden"> |
|
{#each filteredModels as model, modelIdx} |
|
<button |
|
class=" px-3 py-1.5 rounded-xl w-full text-left {modelIdx === selectedIdx |
|
? ' bg-gray-50 dark:bg-gray-850 selected-command-option-button' |
|
: ''}" |
|
type="button" |
|
on:click={() => { |
|
confirmSelect(model); |
|
}} |
|
on:mousemove={() => { |
|
selectedIdx = modelIdx; |
|
}} |
|
on:focus={() => {}} |
|
> |
|
<div class=" font-medium text-black dark:text-gray-100 line-clamp-1"> |
|
{model.name} |
|
</div> |
|
|
|
|
|
|
|
|
|
</button> |
|
{/each} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
{/if} |
|
{/if} |
|
|