Unverified Commit 89634046 authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge pull request #1107 from open-webui/dev

0.1.111
parents 04ddbf43 88d324b5
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
export let suggestionPrompts = []; export let suggestionPrompts = [];
export let autoScroll = true; export let autoScroll = true;
let chatTextAreaElement:HTMLTextAreaElement let chatTextAreaElement: HTMLTextAreaElement;
let filesInputElement; let filesInputElement;
let promptsElement; let promptsElement;
...@@ -359,12 +359,12 @@ ...@@ -359,12 +359,12 @@
{#if dragged} {#if dragged}
<div <div
class="fixed w-full h-full flex z-50 touch-none pointer-events-none" class="fixed lg:w-[calc(100%-260px)] w-full h-full flex z-50 touch-none pointer-events-none"
id="dropzone" id="dropzone"
role="region" role="region"
aria-label="Drag and Drop Container" aria-label="Drag and Drop Container"
> >
<div class="absolute rounded-xl w-full h-full backdrop-blur bg-gray-800/40 flex justify-center"> <div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
<div class="m-auto pt-64 flex flex-col justify-center"> <div class="m-auto pt-64 flex flex-col justify-center">
<div class="max-w-md"> <div class="max-w-md">
<AddFilesPlaceholder /> <AddFilesPlaceholder />
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
import auto_render from 'katex/dist/contrib/auto-render.mjs'; import auto_render from 'katex/dist/contrib/auto-render.mjs';
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
import { fade } from 'svelte/transition';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
...@@ -276,13 +277,15 @@ ...@@ -276,13 +277,15 @@
const generateImage = async (message) => { const generateImage = async (message) => {
generatingImage = true; generatingImage = true;
const res = await imageGenerations(localStorage.token, message.content); const res = await imageGenerations(localStorage.token, message.content).catch((error) => {
toast.error(error);
});
console.log(res); console.log(res);
if (res) { if (res) {
message.files = res.images.map((image) => ({ message.files = res.map((image) => ({
type: 'image', type: 'image',
url: `data:image/png;base64,${image}` url: `${image.url}`
})); }));
dispatch('save', message); dispatch('save', message);
...@@ -477,7 +480,7 @@ ...@@ -477,7 +480,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -503,7 +506,7 @@ ...@@ -503,7 +506,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -622,7 +625,7 @@ ...@@ -622,7 +625,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -637,7 +640,7 @@ ...@@ -637,7 +640,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -703,7 +706,7 @@ ...@@ -703,7 +706,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -733,7 +736,7 @@ ...@@ -733,7 +736,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -762,7 +765,7 @@ ...@@ -762,7 +765,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -792,7 +795,7 @@ ...@@ -792,7 +795,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
......
...@@ -258,7 +258,7 @@ ...@@ -258,7 +258,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -282,7 +282,7 @@ ...@@ -282,7 +282,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -307,7 +307,7 @@ ...@@ -307,7 +307,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
......
...@@ -271,7 +271,7 @@ ...@@ -271,7 +271,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
on:click={async () => { on:click={async () => {
const res = await submitHandler(); const res = await submitHandler();
......
...@@ -251,7 +251,7 @@ ...@@ -251,7 +251,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit" type="submit"
> >
Save Save
......
...@@ -247,7 +247,7 @@ ...@@ -247,7 +247,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit" type="submit"
> >
Save Save
......
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
<div class=" my-2.5 text-sm font-medium">System Prompt</div> <div class=" my-2.5 text-sm font-medium">System Prompt</div>
<textarea <textarea
bind:value={system} bind:value={system}
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none" class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
rows="4" rows="4"
/> />
</div> </div>
...@@ -262,7 +262,7 @@ ...@@ -262,7 +262,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
on:click={() => { on:click={() => {
saveSettings({ saveSettings({
system: system !== '' ? system : undefined, system: system !== '' ? system : undefined,
......
...@@ -5,16 +5,18 @@ ...@@ -5,16 +5,18 @@
import { config, user } from '$lib/stores'; import { config, user } from '$lib/stores';
import { import {
getAUTOMATIC1111Url, getAUTOMATIC1111Url,
getDefaultDiffusionModel, getImageGenerationModels,
getDiffusionModels, getDefaultImageGenerationModel,
getImageGenerationEnabledStatus, updateDefaultImageGenerationModel,
getImageSize, getImageSize,
toggleImageGenerationEnabledStatus, getImageGenerationConfig,
updateImageGenerationConfig,
updateAUTOMATIC1111Url, updateAUTOMATIC1111Url,
updateDefaultDiffusionModel,
updateImageSize, updateImageSize,
getImageSteps, getImageSteps,
updateImageSteps updateImageSteps,
getOpenAIKey,
updateOpenAIKey
} from '$lib/apis/images'; } from '$lib/apis/images';
import { getBackendConfig } from '$lib/apis'; import { getBackendConfig } from '$lib/apis';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
...@@ -23,8 +25,11 @@ ...@@ -23,8 +25,11 @@
let loading = false; let loading = false;
let imageGenerationEngine = '';
let enableImageGeneration = false; let enableImageGeneration = false;
let AUTOMATIC1111_BASE_URL = ''; let AUTOMATIC1111_BASE_URL = '';
let OPENAI_API_KEY = '';
let selectedModel = ''; let selectedModel = '';
let models = null; let models = null;
...@@ -33,11 +38,11 @@ ...@@ -33,11 +38,11 @@
let steps = 50; let steps = 50;
const getModels = async () => { const getModels = async () => {
models = await getDiffusionModels(localStorage.token).catch((error) => { models = await getImageGenerationModels(localStorage.token).catch((error) => {
toast.error(error); toast.error(error);
return null; return null;
}); });
selectedModel = await getDefaultDiffusionModel(localStorage.token).catch((error) => { selectedModel = await getDefaultImageGenerationModel(localStorage.token).catch((error) => {
return ''; return '';
}); });
}; };
...@@ -62,33 +67,45 @@ ...@@ -62,33 +67,45 @@
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token); AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
} }
}; };
const toggleImageGeneration = async () => { const updateImageGeneration = async () => {
if (AUTOMATIC1111_BASE_URL) { const res = await updateImageGenerationConfig(
enableImageGeneration = await toggleImageGenerationEnabledStatus(localStorage.token).catch( localStorage.token,
(error) => { imageGenerationEngine,
toast.error(error); enableImageGeneration
return false; ).catch((error) => {
} toast.error(error);
); return null;
});
if (enableImageGeneration) { if (res) {
config.set(await getBackendConfig(localStorage.token)); imageGenerationEngine = res.engine;
getModels(); enableImageGeneration = res.enabled;
} }
} else {
enableImageGeneration = false; if (enableImageGeneration) {
toast.error('AUTOMATIC1111_BASE_URL not provided'); config.set(await getBackendConfig(localStorage.token));
getModels();
} }
}; };
onMount(async () => { onMount(async () => {
if ($user.role === 'admin') { if ($user.role === 'admin') {
enableImageGeneration = await getImageGenerationEnabledStatus(localStorage.token); const res = await getImageGenerationConfig(localStorage.token).catch((error) => {
toast.error(error);
return null;
});
if (res) {
imageGenerationEngine = res.engine;
enableImageGeneration = res.enabled;
}
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token); AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
imageSize = await getImageSize(localStorage.token);
steps = await getImageSteps(localStorage.token);
if (enableImageGeneration && AUTOMATIC1111_BASE_URL) { if (enableImageGeneration) {
imageSize = await getImageSize(localStorage.token);
steps = await getImageSteps(localStorage.token);
getModels(); getModels();
} }
} }
...@@ -99,7 +116,11 @@ ...@@ -99,7 +116,11 @@
class="flex flex-col h-full justify-between space-y-3 text-sm" class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => { on:submit|preventDefault={async () => {
loading = true; loading = true;
await updateDefaultDiffusionModel(localStorage.token, selectedModel); await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
await updateDefaultImageGenerationModel(localStorage.token, selectedModel);
await updateDefaultImageGenerationModel(localStorage.token, selectedModel);
await updateImageSize(localStorage.token, imageSize).catch((error) => { await updateImageSize(localStorage.token, imageSize).catch((error) => {
toast.error(error); toast.error(error);
return null; return null;
...@@ -117,6 +138,23 @@ ...@@ -117,6 +138,23 @@
<div> <div>
<div class=" mb-1 text-sm font-medium">Image Settings</div> <div class=" mb-1 text-sm font-medium">Image Settings</div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Image Generation Engine</div>
<div class="flex items-center relative">
<select
class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={imageGenerationEngine}
placeholder="Select a mode"
on:change={async () => {
await updateImageGeneration();
}}
>
<option value="">Default (Automatic1111)</option>
<option value="openai">Open AI (Dall-E)</option>
</select>
</div>
</div>
<div> <div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Image Generation (Experimental)</div> <div class=" self-center text-xs font-medium">Image Generation (Experimental)</div>
...@@ -124,7 +162,17 @@ ...@@ -124,7 +162,17 @@
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
on:click={() => { on:click={() => {
toggleImageGeneration(); if (imageGenerationEngine === '' && AUTOMATIC1111_BASE_URL === '') {
toast.error('AUTOMATIC1111 Base URL is required.');
enableImageGeneration = false;
} else if (imageGenerationEngine === 'openai' && OPENAI_API_KEY === '') {
toast.error('OpenAI API Key is required.');
enableImageGeneration = false;
} else {
enableImageGeneration = !enableImageGeneration;
}
updateImageGeneration();
}} }}
type="button" type="button"
> >
...@@ -139,49 +187,62 @@ ...@@ -139,49 +187,62 @@
</div> </div>
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
<div class=" mb-2.5 text-sm font-medium">AUTOMATIC1111 Base URL</div> {#if imageGenerationEngine === ''}
<div class="flex w-full"> <div class=" mb-2.5 text-sm font-medium">AUTOMATIC1111 Base URL</div>
<div class="flex-1 mr-2"> <div class="flex w-full">
<input <div class="flex-1 mr-2">
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" <input
placeholder="Enter URL (e.g. http://127.0.0.1:7860/)" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={AUTOMATIC1111_BASE_URL} placeholder="Enter URL (e.g. http://127.0.0.1:7860/)"
/> bind:value={AUTOMATIC1111_BASE_URL}
/>
</div>
<button
class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded-lg transition"
type="button"
on:click={() => {
// updateOllamaAPIUrlHandler();
updateAUTOMATIC1111UrlHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
clip-rule="evenodd"
/>
</svg>
</button>
</div> </div>
<button
class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded transition" <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
type="button" Include `--api` flag when running stable-diffusion-webui
on:click={() => { <a
// updateOllamaAPIUrlHandler(); class=" text-gray-300 font-medium"
href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
updateAUTOMATIC1111UrlHandler(); target="_blank"
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
> >
<path (e.g. `sh webui.sh --api`)
fill-rule="evenodd" </a>
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z" </div>
clip-rule="evenodd" {:else if imageGenerationEngine === 'openai'}
<div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter API Key"
bind:value={OPENAI_API_KEY}
/> />
</svg> </div>
</button> </div>
</div> {/if}
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Include `--api` flag when running stable-diffusion-webui
<a
class=" text-gray-300 font-medium"
href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
target="_blank"
>
(e.g. `sh webui.sh --api`)
</a>
</div>
{#if enableImageGeneration} {#if enableImageGeneration}
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
...@@ -191,7 +252,7 @@ ...@@ -191,7 +252,7 @@
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<select <select
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={selectedModel} bind:value={selectedModel}
placeholder="Select a model" placeholder="Select a model"
> >
...@@ -199,9 +260,7 @@ ...@@ -199,9 +260,7 @@
<option value="" disabled selected>Select a model</option> <option value="" disabled selected>Select a model</option>
{/if} {/if}
{#each models ?? [] as model} {#each models ?? [] as model}
<option value={model.title} class="bg-gray-100 dark:bg-gray-700" <option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
>{model.model_name}</option
>
{/each} {/each}
</select> </select>
</div> </div>
...@@ -213,7 +272,7 @@ ...@@ -213,7 +272,7 @@
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<input <input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter Image Size (e.g. 512x512)" placeholder="Enter Image Size (e.g. 512x512)"
bind:value={imageSize} bind:value={imageSize}
/> />
...@@ -226,7 +285,7 @@ ...@@ -226,7 +285,7 @@
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<input <input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter Number of Steps (e.g. 50)" placeholder="Enter Number of Steps (e.g. 50)"
bind:value={steps} bind:value={steps}
/> />
...@@ -238,7 +297,7 @@ ...@@ -238,7 +297,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded flex flex-row space-x-1 items-center {loading class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center {loading
? ' cursor-not-allowed' ? ' cursor-not-allowed'
: ''}" : ''}"
type="submit" type="submit"
......
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
} }
saveSettings({ saveSettings({
titleAutoGenerateModel: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
}); });
}; };
...@@ -186,7 +187,7 @@ ...@@ -186,7 +187,7 @@
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<select <select
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={titleAutoGenerateModel} bind:value={titleAutoGenerateModel}
placeholder="Select a model" placeholder="Select a model"
> >
...@@ -200,35 +201,12 @@ ...@@ -200,35 +201,12 @@
{/each} {/each}
</select> </select>
</div> </div>
<button
class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
on:click={() => {
saveSettings({
titleAutoGenerateModel:
titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined
});
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-3.5 h-3.5"
>
<path
fill-rule="evenodd"
d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
clip-rule="evenodd"
/>
</svg>
</button>
</div> </div>
<div class="mt-3"> <div class="mt-3 mr-2">
<div class=" mb-2.5 text-sm font-medium">Title Generation Prompt</div> <div class=" mb-2.5 text-sm font-medium">Title Generation Prompt</div>
<textarea <textarea
bind:value={titleGenerationPrompt} bind:value={titleGenerationPrompt}
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none" class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
rows="3" rows="3"
/> />
</div> </div>
...@@ -321,7 +299,7 @@ ...@@ -321,7 +299,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit" type="submit"
> >
Save Save
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
import { splitStream } from '$lib/utils'; import { splitStream } from '$lib/utils';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm'; import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm';
import Tooltip from '$lib/components/common/Tooltip.svelte';
export let getModels: Function; export let getModels: Function;
...@@ -27,6 +28,7 @@ ...@@ -27,6 +28,7 @@
let liteLLMAPIBase = ''; let liteLLMAPIBase = '';
let liteLLMAPIKey = ''; let liteLLMAPIKey = '';
let liteLLMRPM = ''; let liteLLMRPM = '';
let liteLLMMaxTokens = '';
let deleteLiteLLMModelId = ''; let deleteLiteLLMModelId = '';
...@@ -36,6 +38,10 @@ ...@@ -36,6 +38,10 @@
let OLLAMA_URLS = []; let OLLAMA_URLS = [];
let selectedOllamaUrlIdx: string | null = null; let selectedOllamaUrlIdx: string | null = null;
let updateModelId = null;
let updateProgress = null;
let showExperimentalOllama = false; let showExperimentalOllama = false;
let ollamaVersion = ''; let ollamaVersion = '';
const MAX_PARALLEL_DOWNLOADS = 3; const MAX_PARALLEL_DOWNLOADS = 3;
...@@ -60,6 +66,71 @@ ...@@ -60,6 +66,71 @@
let deleteModelTag = ''; let deleteModelTag = '';
const updateModelsHandler = async () => {
for (const model of $models.filter(
(m) =>
m.size != null &&
(selectedOllamaUrlIdx === null ? true : (m?.urls ?? []).includes(selectedOllamaUrlIdx))
)) {
console.log(model);
updateModelId = model.id;
const res = await pullModel(localStorage.token, model.id, selectedOllamaUrlIdx).catch(
(error) => {
toast.error(error);
return null;
}
);
if (res) {
const reader = res.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(splitStream('\n'))
.getReader();
while (true) {
try {
const { value, done } = await reader.read();
if (done) break;
let lines = value.split('\n');
for (const line of lines) {
if (line !== '') {
let data = JSON.parse(line);
console.log(data);
if (data.error) {
throw data.error;
}
if (data.detail) {
throw data.detail;
}
if (data.status) {
if (data.digest) {
updateProgress = 0;
if (data.completed) {
updateProgress = Math.round((data.completed / data.total) * 1000) / 10;
} else {
updateProgress = 100;
}
} else {
toast.success(data.status);
}
}
}
}
} catch (error) {
console.log(error);
}
}
}
}
updateModelId = null;
updateProgress = null;
};
const pullModelHandler = async () => { const pullModelHandler = async () => {
const sanitizedModelTag = modelTag.trim(); const sanitizedModelTag = modelTag.trim();
if (modelDownloadStatus[sanitizedModelTag]) { if (modelDownloadStatus[sanitizedModelTag]) {
...@@ -326,7 +397,8 @@ ...@@ -326,7 +397,8 @@
model: liteLLMModel, model: liteLLMModel,
api_base: liteLLMAPIBase, api_base: liteLLMAPIBase,
api_key: liteLLMAPIKey, api_key: liteLLMAPIKey,
rpm: liteLLMRPM rpm: liteLLMRPM,
max_tokens: liteLLMMaxTokens
}).catch((error) => { }).catch((error) => {
toast.error(error); toast.error(error);
return null; return null;
...@@ -346,6 +418,7 @@ ...@@ -346,6 +418,7 @@
liteLLMAPIBase = ''; liteLLMAPIBase = '';
liteLLMAPIKey = ''; liteLLMAPIKey = '';
liteLLMRPM = ''; liteLLMRPM = '';
liteLLMMaxTokens = '';
liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token); liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
models.set(await getModels()); models.set(await getModels());
...@@ -376,7 +449,7 @@ ...@@ -376,7 +449,7 @@
return []; return [];
}); });
if (OLLAMA_URLS.length > 1) { if (OLLAMA_URLS.length > 0) {
selectedOllamaUrlIdx = 0; selectedOllamaUrlIdx = 0;
} }
...@@ -391,18 +464,51 @@ ...@@ -391,18 +464,51 @@
<div class="space-y-2 pr-1.5"> <div class="space-y-2 pr-1.5">
<div class="text-sm font-medium">Manage Ollama Models</div> <div class="text-sm font-medium">Manage Ollama Models</div>
{#if OLLAMA_URLS.length > 1} {#if OLLAMA_URLS.length > 0}
<div class="flex-1 pb-1"> <div class="flex gap-2">
<select <div class="flex-1 pb-1">
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" <select
bind:value={selectedOllamaUrlIdx} class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Select an Ollama instance" bind:value={selectedOllamaUrlIdx}
> placeholder="Select an Ollama instance"
{#each OLLAMA_URLS as url, idx} >
<option value={idx} class="bg-gray-100 dark:bg-gray-700">{url}</option> {#each OLLAMA_URLS as url, idx}
{/each} <option value={idx} class="bg-gray-100 dark:bg-gray-700">{url}</option>
</select> {/each}
</select>
</div>
<div>
<div class="flex w-full justify-end">
<Tooltip content="Update All Models" placement="top">
<button
class="p-2.5 flex gap-2 items-center bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
updateModelsHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M7 1a.75.75 0 0 1 .75.75V6h-1.5V1.75A.75.75 0 0 1 7 1ZM6.25 6v2.94L5.03 7.72a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06L7.75 8.94V6H10a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.25Z"
/>
<path
d="M4.268 14A2 2 0 0 0 6 15h6a2 2 0 0 0 2-2v-3a2 2 0 0 0-1-1.732V11a3 3 0 0 1-3 3H4.268Z"
/>
</svg>
</button>
</Tooltip>
</div>
</div>
</div> </div>
{#if updateModelId}
Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''}
{/if}
{/if} {/if}
<div class="space-y-2"> <div class="space-y-2">
...@@ -467,12 +573,14 @@ ...@@ -467,12 +573,14 @@
</button> </button>
</div> </div>
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500"> <div>
To access the available model names for downloading, <a <div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
class=" text-gray-500 dark:text-gray-300 font-medium underline" To access the available model names for downloading, <a
href="https://ollama.com/library" class=" text-gray-500 dark:text-gray-300 font-medium underline"
target="_blank">click here.</a href="https://ollama.com/library"
> target="_blank">click here.</a
>
</div>
</div> </div>
{#if Object.keys(modelDownloadStatus).length > 0} {#if Object.keys(modelDownloadStatus).length > 0}
...@@ -589,7 +697,7 @@ ...@@ -589,7 +697,7 @@
on:change={() => { on:change={() => {
console.log(modelInputFile); console.log(modelInputFile);
}} }}
accept=".gguf" accept=".gguf,.safetensors"
required required
hidden hidden
/> />
...@@ -722,245 +830,193 @@ ...@@ -722,245 +830,193 @@
<div class=" space-y-3"> <div class=" space-y-3">
<div class="mt-2 space-y-3 pr-1.5"> <div class="mt-2 space-y-3 pr-1.5">
<div> <div>
<div class=" mb-2 text-sm font-medium">Manage LiteLLM Models</div> <div class="mb-2">
<div>
<div class="flex justify-between items-center text-xs"> <div class="flex justify-between items-center text-xs">
<div class=" text-sm font-medium">Add a model</div> <div class=" text-sm font-medium">Manage LiteLLM Models</div>
<button <button
class=" text-xs font-medium text-gray-500" class=" text-xs font-medium text-gray-500"
type="button" type="button"
on:click={() => { on:click={() => {
showLiteLLMParams = !showLiteLLMParams; showLiteLLM = !showLiteLLM;
}}>{showLiteLLMParams ? 'Hide Additional Params' : 'Show Additional Params'}</button }}>{showLiteLLM ? 'Hide' : 'Show'}</button
> >
</div> </div>
</div> </div>
<div class="my-2 space-y-2"> {#if showLiteLLM}
<div class="flex w-full mb-1.5"> <div>
<div class="flex-1 mr-2"> <div class="flex justify-between items-center text-xs">
<input <div class=" text-sm font-medium">Add a model</div>
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" <button
placeholder="Enter LiteLLM Model (litellm_params.model)" class=" text-xs font-medium text-gray-500"
bind:value={liteLLMModel} type="button"
autocomplete="off" on:click={() => {
/> showLiteLLMParams = !showLiteLLMParams;
}}
>{showLiteLLMParams ? 'Hide Additional Params' : 'Show Additional Params'}</button
>
</div> </div>
</div>
<button <div class="my-2 space-y-2">
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition" <div class="flex w-full mb-1.5">
on:click={() => { <div class="flex-1 mr-2">
addLiteLLMModelHandler(); <input
}} class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
> placeholder="Enter LiteLLM Model (litellm_params.model)"
<svg bind:value={liteLLMModel}
xmlns="http://www.w3.org/2000/svg" autocomplete="off"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
/> />
</svg> </div>
</button>
</div>
{#if showLiteLLMParams} <button
<div> class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
<div class=" mb-1.5 text-sm font-medium">Model Name</div> on:click={() => {
<div class="flex w-full"> addLiteLLMModelHandler();
<div class="flex-1"> }}
<input >
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" <svg
placeholder="Enter Model Name (model_name)" xmlns="http://www.w3.org/2000/svg"
bind:value={liteLLMModelName} viewBox="0 0 16 16"
autocomplete="off" fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
/> />
</div> </svg>
</div> </button>
</div> </div>
<div> {#if showLiteLLMParams}
<div class=" mb-1.5 text-sm font-medium">API Base URL</div> <div>
<div class="flex w-full"> <div class=" mb-1.5 text-sm font-medium">Model Name</div>
<div class="flex-1"> <div class="flex w-full">
<input <div class="flex-1">
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" <input
placeholder="Enter LiteLLM API Base URL (litellm_params.api_base)" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={liteLLMAPIBase} placeholder="Enter Model Name (model_name)"
autocomplete="off" bind:value={liteLLMModelName}
/> autocomplete="off"
/>
</div>
</div> </div>
</div> </div>
</div>
<div> <div>
<div class=" mb-1.5 text-sm font-medium">API Key</div> <div class=" mb-1.5 text-sm font-medium">API Base URL</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1"> <div class="flex-1">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM API Key (litellm_params.api_key)" placeholder="Enter LiteLLM API Base URL (litellm_params.api_base)"
bind:value={liteLLMAPIKey} bind:value={liteLLMAPIBase}
autocomplete="off" autocomplete="off"
/> />
</div>
</div> </div>
</div> </div>
</div>
<div> <div>
<div class="mb-1.5 text-sm font-medium">API RPM</div> <div class=" mb-1.5 text-sm font-medium">API Key</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1"> <div class="flex-1">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM API RPM (litellm_params.rpm)" placeholder="Enter LiteLLM API Key (litellm_params.api_key)"
bind:value={liteLLMRPM} bind:value={liteLLMAPIKey}
autocomplete="off" autocomplete="off"
/> />
</div>
</div> </div>
</div> </div>
</div>
{/if}
</div>
<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
Not sure what to add?
<a
class=" text-gray-300 font-medium underline"
href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
target="_blank"
>
Click here for help.
</a>
</div>
<div>
<div class=" mb-2.5 text-sm font-medium">Delete a model</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={deleteLiteLLMModelId}
placeholder="Select a model"
>
{#if !deleteLiteLLMModelId}
<option value="" disabled selected>Select a model</option>
{/if}
{#each liteLLMModelInfo as model}
<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700"
>{model.model_name}</option
>
{/each}
</select>
</div>
<button
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
deleteLiteLLMModelHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- <div class="mt-2 space-y-3 pr-1.5">
<div>
<div class=" mb-2.5 text-sm font-medium">Add LiteLLM Model</div>
<div class="flex w-full mb-2">
<div class="flex-1">
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
placeholder="Enter LiteLLM Model (e.g. ollama/mistral)"
bind:value={liteLLMModel}
autocomplete="off"
/>
</div>
</div>
<div class="flex justify-between items-center text-sm"> <div>
<div class=" font-medium">Advanced Model Params</div> <div class="mb-1.5 text-sm font-medium">API RPM</div>
<button <div class="flex w-full">
class=" text-xs font-medium text-gray-500" <div class="flex-1">
type="button" <input
on:click={() => { class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
showLiteLLMParams = !showLiteLLMParams; placeholder="Enter LiteLLM API RPM (litellm_params.rpm)"
}}>{showLiteLLMParams ? 'Hide' : 'Show'}</button bind:value={liteLLMRPM}
> autocomplete="off"
</div> />
</div>
</div>
</div>
{#if showLiteLLMParams} <div>
<div> <div class="mb-1.5 text-sm font-medium">Max Tokens</div>
<div class=" mb-2.5 text-sm font-medium">LiteLLM API Key</div> <div class="flex w-full">
<div class="flex w-full"> <div class="flex-1">
<div class="flex-1"> <input
<input class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" placeholder="Enter Max Tokens (litellm_params.max_tokens)"
placeholder="Enter LiteLLM API Key (e.g. os.environ/AZURE_API_KEY_CA)" bind:value={liteLLMMaxTokens}
bind:value={liteLLMAPIKey} type="number"
autocomplete="off" min="1"
/> autocomplete="off"
/>
</div>
</div>
</div> </div>
</div> {/if}
</div> </div>
<div> <div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
<div class=" mb-2.5 text-sm font-medium">LiteLLM API Base URL</div> Not sure what to add?
<div class="flex w-full"> <a
<div class="flex-1"> class=" text-gray-300 font-medium underline"
<input href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" target="_blank"
placeholder="Enter LiteLLM API Base URL" >
bind:value={liteLLMAPIBase} Click here for help.
autocomplete="off" </a>
/>
</div>
</div>
</div> </div>
<div> <div>
<div class=" mb-2.5 text-sm font-medium">LiteLLM API RPM</div> <div class=" mb-2.5 text-sm font-medium">Delete a model</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1"> <div class="flex-1 mr-2">
<input <select
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM API RPM" bind:value={deleteLiteLLMModelId}
bind:value={liteLLMRPM} placeholder="Select a model"
autocomplete="off" >
/> {#if !deleteLiteLLMModelId}
<option value="" disabled selected>Select a model</option>
{/if}
{#each liteLLMModelInfo as model}
<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700"
>{model.model_name}</option
>
{/each}
</select>
</div> </div>
<button
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
on:click={() => {
deleteLiteLLMModelHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z"
clip-rule="evenodd"
/>
</svg>
</button>
</div> </div>
</div> </div>
{/if} {/if}
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Not sure what to add?
<a
class=" text-gray-300 font-medium underline"
href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
target="_blank"
>
Click here for help.
</a>
</div>
</div> </div>
</div> --> </div>
</div> </div>
</div> </div>
</div> </div>
...@@ -326,7 +326,7 @@ ...@@ -326,7 +326,7 @@
{getModels} {getModels}
{saveSettings} {saveSettings}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'models'} {:else if selectedTab === 'models'}
...@@ -335,28 +335,28 @@ ...@@ -335,28 +335,28 @@
<Connections <Connections
{getModels} {getModels}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'interface'} {:else if selectedTab === 'interface'}
<Interface <Interface
{saveSettings} {saveSettings}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'audio'} {:else if selectedTab === 'audio'}
<Audio <Audio
{saveSettings} {saveSettings}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'images'} {:else if selectedTab === 'images'}
<Images <Images
{saveSettings} {saveSettings}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'chats'} {:else if selectedTab === 'chats'}
...@@ -364,7 +364,7 @@ ...@@ -364,7 +364,7 @@
{:else if selectedTab === 'account'} {:else if selectedTab === 'account'}
<Account <Account
saveHandler={() => { saveHandler={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'about'} {:else if selectedTab === 'about'}
......
<script lang="ts"> <script lang="ts">
import { WEBUI_BASE_URL } from '$lib/constants';
import ImagePreview from './ImagePreview.svelte'; import ImagePreview from './ImagePreview.svelte';
export let src = ''; export let src = '';
export let alt = ''; export let alt = '';
let _src = '';
$: _src = src.startsWith('/') ? `${WEBUI_BASE_URL}${src}` : src;
let showImagePreview = false; let showImagePreview = false;
</script> </script>
<ImagePreview bind:show={showImagePreview} {src} {alt} /> <ImagePreview bind:show={showImagePreview} src={_src} {alt} />
<button <button
on:click={() => { on:click={() => {
console.log('image preview'); console.log('image preview');
showImagePreview = true; showImagePreview = true;
}} }}
> >
<img {src} {alt} class=" max-h-96 rounded-lg" draggable="false" /> <img src={_src} {alt} class=" max-h-96 rounded-lg" draggable="false" />
</button> </button>
<script lang="ts"> <script lang="ts">
import { getDocs } from '$lib/apis/documents'; import { getDocs } from '$lib/apis/documents';
import { import {
getChunkParams, getRAGConfig,
updateRAGConfig,
getQuerySettings, getQuerySettings,
scanDocs, scanDocs,
updateChunkParams,
updateQuerySettings updateQuerySettings
} from '$lib/apis/rag'; } from '$lib/apis/rag';
import { documents } from '$lib/stores'; import { documents } from '$lib/stores';
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
let chunkSize = 0; let chunkSize = 0;
let chunkOverlap = 0; let chunkOverlap = 0;
let pdfExtractImages = true;
let querySettings = { let querySettings = {
template: '', template: '',
...@@ -35,16 +36,24 @@ ...@@ -35,16 +36,24 @@
}; };
const submitHandler = async () => { const submitHandler = async () => {
const res = await updateChunkParams(localStorage.token, chunkSize, chunkOverlap); const res = await updateRAGConfig(localStorage.token, {
pdf_extract_images: pdfExtractImages,
chunk: {
chunk_overlap: chunkOverlap,
chunk_size: chunkSize
}
});
querySettings = await updateQuerySettings(localStorage.token, querySettings); querySettings = await updateQuerySettings(localStorage.token, querySettings);
}; };
onMount(async () => { onMount(async () => {
const res = await getChunkParams(localStorage.token); const res = await getRAGConfig(localStorage.token);
if (res) { if (res) {
chunkSize = res.chunk_size; pdfExtractImages = res.pdf_extract_images;
chunkOverlap = res.chunk_overlap;
chunkSize = res.chunk.chunk_size;
chunkOverlap = res.chunk.chunk_overlap;
} }
querySettings = await getQuerySettings(localStorage.token); querySettings = await getQuerySettings(localStorage.token);
...@@ -124,82 +133,100 @@ ...@@ -124,82 +133,100 @@
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
<div class=" "> <div class=" space-y-3">
<div class=" text-sm font-medium">Chunk Params</div> <div class=" space-y-3">
<div class=" text-sm font-medium">Chunk Params</div>
<div class=" flex">
<div class=" flex w-full justify-between"> <div class=" flex gap-2">
<div class="self-center text-xs font-medium min-w-fit">Chunk Size</div> <div class=" flex w-full justify-between gap-2">
<div class="self-center text-xs font-medium min-w-fit">Chunk Size</div>
<div class="self-center p-3">
<input <div class="self-center">
class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" <input
type="number" class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
placeholder="Enter Chunk Size" type="number"
bind:value={chunkSize} placeholder="Enter Chunk Size"
autocomplete="off" bind:value={chunkSize}
min="0" autocomplete="off"
/> min="0"
/>
</div>
</div> </div>
</div>
<div class="flex w-full"> <div class="flex w-full gap-2">
<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div> <div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
<div class="self-center p-3"> <div class="self-center">
<input <input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="number" type="number"
placeholder="Enter Chunk Overlap" placeholder="Enter Chunk Overlap"
bind:value={chunkOverlap} bind:value={chunkOverlap}
autocomplete="off" autocomplete="off"
min="0" min="0"
/> />
</div>
</div> </div>
</div> </div>
</div>
<div class=" text-sm font-medium">Query Params</div>
<div class=" flex"> <div>
<div class=" flex w-full justify-between"> <div class="flex justify-between items-center text-xs">
<div class="self-center text-xs font-medium flex-1">Top K</div> <div class=" text-xs font-medium">PDF Extract Images (OCR)</div>
<div class="self-center p-3"> <button
<input class=" text-xs font-medium text-gray-500"
class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" type="button"
type="number" on:click={() => {
placeholder="Enter Top K" pdfExtractImages = !pdfExtractImages;
bind:value={querySettings.k} }}>{pdfExtractImages ? 'On' : 'Off'}</button
autocomplete="off" >
min="0"
/>
</div> </div>
</div> </div>
<!-- <div class="flex w-full">
<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
<div class="self-center p-3">
<input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="number"
placeholder="Enter Chunk Overlap"
bind:value={chunkOverlap}
autocomplete="off"
min="0"
/>
</div>
</div> -->
</div> </div>
<div> <div>
<div class=" mb-2.5 text-sm font-medium">RAG Template</div> <div class=" text-sm font-medium">Query Params</div>
<textarea
bind:value={querySettings.template} <div class=" flex py-2">
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none" <div class=" flex w-full justify-between gap-2">
rows="4" <div class="self-center text-xs font-medium flex-1">Top K</div>
/>
<div class="self-center">
<input
class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="number"
placeholder="Enter Top K"
bind:value={querySettings.k}
autocomplete="off"
min="0"
/>
</div>
</div>
<!-- <div class="flex w-full">
<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
<div class="self-center p-3">
<input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="number"
placeholder="Enter Chunk Overlap"
bind:value={chunkOverlap}
autocomplete="off"
min="0"
/>
</div>
</div> -->
</div>
<div>
<div class=" mb-2.5 text-sm font-medium">RAG Template</div>
<textarea
bind:value={querySettings.template}
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
rows="4"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -61,12 +61,16 @@ ...@@ -61,12 +61,16 @@
}; };
const editChatTitle = async (id, _title) => { const editChatTitle = async (id, _title) => {
title = _title; if (_title === '') {
toast.error('Title cannot be an empty string.');
await updateChatById(localStorage.token, id, { } else {
title: _title title = _title;
});
await chats.set(await getChatList(localStorage.token)); await updateChatById(localStorage.token, id, {
title: _title
});
await chats.set(await getChatList(localStorage.token));
}
}; };
const deleteChat = async (id) => { const deleteChat = async (id) => {
...@@ -388,12 +392,13 @@ ...@@ -388,12 +392,13 @@
show = false; show = false;
} }
}} }}
draggable="false"
> >
<div class=" flex self-center flex-1 w-full"> <div class=" flex self-center flex-1 w-full">
<div <div
class=" text-left self-center overflow-hidden {chat.id === $chatId class=" text-left self-center overflow-hidden {chat.id === $chatId
? 'w-[160px]' ? 'w-[160px]'
: 'w-full'} " : 'w-full'} h-[20px]"
> >
{chat.title} {chat.title}
</div> </div>
......
...@@ -232,53 +232,6 @@ ...@@ -232,53 +232,6 @@
const sendPrompt = async (prompt, parentId) => { const sendPrompt = async (prompt, parentId) => {
const _chatId = JSON.parse(JSON.stringify($chatId)); const _chatId = JSON.parse(JSON.stringify($chatId));
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
if (docs.length > 0) {
processing = 'Reading';
const query = history.messages[parentId].content;
let relevantContexts = await Promise.all(
docs.map(async (doc) => {
if (doc.type === 'collection') {
return await queryCollection(localStorage.token, doc.collection_names, query).catch(
(error) => {
console.log(error);
return null;
}
);
} else {
return await queryDoc(localStorage.token, doc.collection_name, query).catch((error) => {
console.log(error);
return null;
});
}
})
);
relevantContexts = relevantContexts.filter((context) => context);
const contextString = relevantContexts.reduce((a, context, i, arr) => {
return `${a}${context.documents.join(' ')}\n`;
}, '');
console.log(contextString);
history.messages[parentId].raContent = await RAGTemplate(
localStorage.token,
contextString,
query
);
history.messages[parentId].contexts = relevantContexts;
await tick();
processing = '';
}
await Promise.all( await Promise.all(
selectedModels.map(async (modelId) => { selectedModels.map(async (modelId) => {
const model = $models.filter((m) => m.id === modelId).at(0); const model = $models.filter((m) => m.id === modelId).at(0);
...@@ -342,15 +295,25 @@ ...@@ -342,15 +295,25 @@
...messages ...messages
] ]
.filter((message) => message) .filter((message) => message)
.map((message, idx, arr) => ({ .map((message, idx, arr) => {
role: message.role, // Prepare the base message object
content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content, const baseMessage = {
...(message.files && { role: message.role,
images: message.files content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content
.filter((file) => file.type === 'image') };
.map((file) => file.url.slice(file.url.indexOf(',') + 1))
}) // Extract and format image URLs if any exist
})); const imageUrls = message.files
?.filter((file) => file.type === 'image')
.map((file) => file.url.slice(file.url.indexOf(',') + 1));
// Add images array only if it contains elements
if (imageUrls && imageUrls.length > 0) {
baseMessage.images = imageUrls;
}
return baseMessage;
});
let lastImageIndex = -1; let lastImageIndex = -1;
...@@ -368,6 +331,13 @@ ...@@ -368,6 +331,13 @@
} }
}); });
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
const [res, controller] = await generateChatCompletion(localStorage.token, { const [res, controller] = await generateChatCompletion(localStorage.token, {
model: model, model: model,
messages: messagesBody, messages: messagesBody,
...@@ -375,7 +345,8 @@ ...@@ -375,7 +345,8 @@
...($settings.options ?? {}) ...($settings.options ?? {})
}, },
format: $settings.requestFormat ?? undefined, format: $settings.requestFormat ?? undefined,
keep_alive: $settings.keepAlive ?? undefined keep_alive: $settings.keepAlive ?? undefined,
docs: docs.length > 0 ? docs : undefined
}); });
if (res && res.ok) { if (res && res.ok) {
...@@ -535,6 +506,15 @@ ...@@ -535,6 +506,15 @@
const responseMessage = history.messages[responseMessageId]; const responseMessage = history.messages[responseMessageId];
scrollToBottom(); scrollToBottom();
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
const res = await generateOpenAIChatCompletion( const res = await generateOpenAIChatCompletion(
localStorage.token, localStorage.token,
{ {
...@@ -583,7 +563,8 @@ ...@@ -583,7 +563,8 @@
top_p: $settings?.options?.top_p ?? undefined, top_p: $settings?.options?.top_p ?? undefined,
num_ctx: $settings?.options?.num_ctx ?? undefined, num_ctx: $settings?.options?.num_ctx ?? undefined,
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined, frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
max_tokens: $settings?.options?.num_predict ?? undefined max_tokens: $settings?.options?.num_predict ?? undefined,
docs: docs.length > 0 ? docs : undefined
}, },
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}` model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
); );
...@@ -694,7 +675,12 @@ ...@@ -694,7 +675,12 @@
if (messages.length == 2) { if (messages.length == 2) {
window.history.replaceState(history.state, '', `/c/${_chatId}`); window.history.replaceState(history.state, '', `/c/${_chatId}`);
await setChatTitle(_chatId, userPrompt);
if ($settings?.titleAutoGenerateModel) {
await generateChatTitle(_chatId, userPrompt);
} else {
await setChatTitle(_chatId, userPrompt);
}
} }
}; };
......
...@@ -245,53 +245,6 @@ ...@@ -245,53 +245,6 @@
const sendPrompt = async (prompt, parentId) => { const sendPrompt = async (prompt, parentId) => {
const _chatId = JSON.parse(JSON.stringify($chatId)); const _chatId = JSON.parse(JSON.stringify($chatId));
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
if (docs.length > 0) {
processing = 'Reading';
const query = history.messages[parentId].content;
let relevantContexts = await Promise.all(
docs.map(async (doc) => {
if (doc.type === 'collection') {
return await queryCollection(localStorage.token, doc.collection_names, query).catch(
(error) => {
console.log(error);
return null;
}
);
} else {
return await queryDoc(localStorage.token, doc.collection_name, query).catch((error) => {
console.log(error);
return null;
});
}
})
);
relevantContexts = relevantContexts.filter((context) => context);
const contextString = relevantContexts.reduce((a, context, i, arr) => {
return `${a}${context.documents.join(' ')}\n`;
}, '');
console.log(contextString);
history.messages[parentId].raContent = await RAGTemplate(
localStorage.token,
contextString,
query
);
history.messages[parentId].contexts = relevantContexts;
await tick();
processing = '';
}
await Promise.all( await Promise.all(
selectedModels.map(async (modelId) => { selectedModels.map(async (modelId) => {
const model = $models.filter((m) => m.id === modelId).at(0); const model = $models.filter((m) => m.id === modelId).at(0);
...@@ -355,15 +308,25 @@ ...@@ -355,15 +308,25 @@
...messages ...messages
] ]
.filter((message) => message) .filter((message) => message)
.map((message, idx, arr) => ({ .map((message, idx, arr) => {
role: message.role, // Prepare the base message object
content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content, const baseMessage = {
...(message.files && { role: message.role,
images: message.files content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content
.filter((file) => file.type === 'image') };
.map((file) => file.url.slice(file.url.indexOf(',') + 1))
}) // Extract and format image URLs if any exist
})); const imageUrls = message.files
?.filter((file) => file.type === 'image')
.map((file) => file.url.slice(file.url.indexOf(',') + 1));
// Add images array only if it contains elements
if (imageUrls && imageUrls.length > 0) {
baseMessage.images = imageUrls;
}
return baseMessage;
});
let lastImageIndex = -1; let lastImageIndex = -1;
...@@ -381,6 +344,13 @@ ...@@ -381,6 +344,13 @@
} }
}); });
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
const [res, controller] = await generateChatCompletion(localStorage.token, { const [res, controller] = await generateChatCompletion(localStorage.token, {
model: model, model: model,
messages: messagesBody, messages: messagesBody,
...@@ -388,7 +358,8 @@ ...@@ -388,7 +358,8 @@
...($settings.options ?? {}) ...($settings.options ?? {})
}, },
format: $settings.requestFormat ?? undefined, format: $settings.requestFormat ?? undefined,
keep_alive: $settings.keepAlive ?? undefined keep_alive: $settings.keepAlive ?? undefined,
docs: docs.length > 0 ? docs : undefined
}); });
if (res && res.ok) { if (res && res.ok) {
...@@ -548,6 +519,15 @@ ...@@ -548,6 +519,15 @@
const responseMessage = history.messages[responseMessageId]; const responseMessage = history.messages[responseMessageId];
scrollToBottom(); scrollToBottom();
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
const res = await generateOpenAIChatCompletion( const res = await generateOpenAIChatCompletion(
localStorage.token, localStorage.token,
{ {
...@@ -596,7 +576,8 @@ ...@@ -596,7 +576,8 @@
top_p: $settings?.options?.top_p ?? undefined, top_p: $settings?.options?.top_p ?? undefined,
num_ctx: $settings?.options?.num_ctx ?? undefined, num_ctx: $settings?.options?.num_ctx ?? undefined,
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined, frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
max_tokens: $settings?.options?.num_predict ?? undefined max_tokens: $settings?.options?.num_predict ?? undefined,
docs: docs.length > 0 ? docs : undefined
}, },
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}` model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
); );
...@@ -710,6 +691,7 @@ ...@@ -710,6 +691,7 @@
await setChatTitle(_chatId, userPrompt); await setChatTitle(_chatId, userPrompt);
} }
}; };
const stopResponse = () => { const stopResponse = () => {
stopResponseFlag = true; stopResponseFlag = true;
console.log('stopResponse'); console.log('stopResponse');
......
...@@ -267,7 +267,7 @@ ...@@ -267,7 +267,7 @@
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white"> <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
<div class=" flex flex-col justify-between w-full overflow-y-auto h-[100dvh]"> <div class=" flex flex-col justify-between w-full overflow-y-auto h-[100dvh]">
<div class="max-w-2xl mx-auto w-full px-3 p-3 md:px-0 h-full"> <div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10 h-full">
<div class=" flex flex-col h-full"> <div class=" flex flex-col h-full">
<div class="flex flex-col justify-between mb-2.5 gap-1"> <div class="flex flex-col justify-between mb-2.5 gap-1">
<div class="flex justify-between items-center gap-2"> <div class="flex justify-between items-center gap-2">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment