Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
02fb517b
Unverified
Commit
02fb517b
authored
Feb 25, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Feb 25, 2024
Browse files
Merge pull request #907 from open-webui/dev
0.1.103
parents
6676f0b4
0bd3ec9e
Changes
27
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
770 additions
and
437 deletions
+770
-437
src/lib/components/chat/Settings/Images.svelte
src/lib/components/chat/Settings/Images.svelte
+29
-6
src/lib/components/chat/Settings/Models.svelte
src/lib/components/chat/Settings/Models.svelte
+596
-265
src/lib/components/chat/SettingsModal.svelte
src/lib/components/chat/SettingsModal.svelte
+21
-14
src/lib/constants.ts
src/lib/constants.ts
+2
-0
src/routes/(app)/+layout.svelte
src/routes/(app)/+layout.svelte
+23
-74
src/routes/(app)/+page.svelte
src/routes/(app)/+page.svelte
+93
-78
test.json
test.json
+6
-0
No files found.
src/lib/components/chat/Settings/Images.svelte
View file @
02fb517b
...
@@ -12,7 +12,9 @@
...
@@ -12,7 +12,9 @@
toggleImageGenerationEnabledStatus,
toggleImageGenerationEnabledStatus,
updateAUTOMATIC1111Url,
updateAUTOMATIC1111Url,
updateDefaultDiffusionModel,
updateDefaultDiffusionModel,
updateImageSize
updateImageSize,
getImageSteps,
updateImageSteps
} from '$lib/apis/images';
} from '$lib/apis/images';
import { getBackendConfig } from '$lib/apis';
import { getBackendConfig } from '$lib/apis';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
...
@@ -21,20 +23,23 @@
...
@@ -21,20 +23,23 @@
let loading = false;
let loading = false;
let enableImageGeneration =
tru
e;
let enableImageGeneration =
fals
e;
let AUTOMATIC1111_BASE_URL = '';
let AUTOMATIC1111_BASE_URL = '';
let selectedModel = '';
let selectedModel = '';
let models =
[]
;
let models =
null
;
let imageSize = '';
let imageSize = '';
let steps = 50;
const getModels = async () => {
const getModels = async () => {
models = await getDiffusionModels(localStorage.token).catch((error) => {
models = await getDiffusionModels(localStorage.token).catch((error) => {
toast.error(error);
toast.error(error);
return null;
return null;
});
});
selectedModel = await getDefaultDiffusionModel(localStorage.token);
selectedModel = await getDefaultDiffusionModel(localStorage.token).catch((error) => {
return '';
});
};
};
const updateAUTOMATIC1111UrlHandler = async () => {
const updateAUTOMATIC1111UrlHandler = async () => {
...
@@ -83,6 +88,7 @@
...
@@ -83,6 +88,7 @@
if (enableImageGeneration && AUTOMATIC1111_BASE_URL) {
if (enableImageGeneration && AUTOMATIC1111_BASE_URL) {
imageSize = await getImageSize(localStorage.token);
imageSize = await getImageSize(localStorage.token);
steps = await getImageSteps(localStorage.token);
getModels();
getModels();
}
}
}
}
...
@@ -98,12 +104,16 @@
...
@@ -98,12 +104,16 @@
toast.error(error);
toast.error(error);
return null;
return null;
});
});
await updateImageSteps(localStorage.token, steps).catch((error) => {
toast.error(error);
return null;
});
dispatch('save');
dispatch('save');
loading = false;
loading = false;
}}
}}
>
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[2
1
rem]">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[2
0.5
rem]">
<div>
<div>
<div class=" mb-1 text-sm font-medium">Image Settings</div>
<div class=" mb-1 text-sm font-medium">Image Settings</div>
...
@@ -188,7 +198,7 @@
...
@@ -188,7 +198,7 @@
{#if !selectedModel}
{#if !selectedModel}
<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.title} class="bg-gray-100 dark:bg-gray-700"
>{model.model_name}</option
>{model.model_name}</option
>
>
...
@@ -210,6 +220,19 @@
...
@@ -210,6 +220,19 @@
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div class=" mb-2.5 text-sm font-medium">Set Steps</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
placeholder="Enter Number of Steps (e.g. 50)"
bind:value={steps}
/>
</div>
</div>
</div>
{/if}
{/if}
</div>
</div>
...
...
src/lib/components/chat/Settings/Models.svelte
View file @
02fb517b
...
@@ -2,14 +2,33 @@
...
@@ -2,14 +2,33 @@
import queue from 'async/queue';
import queue from 'async/queue';
import toast from 'svelte-french-toast';
import toast from 'svelte-french-toast';
import { createModel, deleteModel, pullModel } from '$lib/apis/ollama';
import { createModel, deleteModel,
getOllamaVersion,
pullModel } from '$lib/apis/ollama';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_NAME, models, user } from '$lib/stores';
import { WEBUI_NAME, models, user } from '$lib/stores';
import { splitStream } from '$lib/utils';
import { splitStream } from '$lib/utils';
import { onMount } from 'svelte';
import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm';
export let getModels: Function;
export let getModels: Function;
let showLiteLLM = false;
let showLiteLLMParams = false;
let liteLLMModelInfo = [];
let liteLLMModel = '';
let liteLLMModelName = '';
let liteLLMAPIBase = '';
let liteLLMAPIKey = '';
let liteLLMRPM = '';
let deleteLiteLLMModelId = '';
$: liteLLMModelName = liteLLMModel;
// Models
// Models
let showExperimentalOllama = false;
let ollamaVersion = '';
const MAX_PARALLEL_DOWNLOADS = 3;
const MAX_PARALLEL_DOWNLOADS = 3;
const modelDownloadQueue = queue(
const modelDownloadQueue = queue(
(task: { modelName: string }, cb) =>
(task: { modelName: string }, cb) =>
...
@@ -286,311 +305,623 @@
...
@@ -286,311 +305,623 @@
opts.callback({ success: true, modelName: opts.modelName });
opts.callback({ success: true, modelName: opts.modelName });
}
}
};
};
const addLiteLLMModelHandler = async () => {
if (!liteLLMModelInfo.find((info) => info.model_name === liteLLMModelName)) {
const res = await addLiteLLMModel(localStorage.token, {
name: liteLLMModelName,
model: liteLLMModel,
api_base: liteLLMAPIBase,
api_key: liteLLMAPIKey,
rpm: liteLLMRPM
}).catch((error) => {
toast.error(error);
return null;
});
if (res) {
if (res.message) {
toast.success(res.message);
}
}
} else {
toast.error(`Model ${liteLLMModelName} already exists.`);
}
liteLLMModelName = '';
liteLLMModel = '';
liteLLMAPIBase = '';
liteLLMAPIKey = '';
liteLLMRPM = '';
liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
models.set(await getModels());
};
const deleteLiteLLMModelHandler = async () => {
const res = await deleteLiteLLMModel(localStorage.token, deleteLiteLLMModelId).catch(
(error) => {
toast.error(error);
return null;
}
);
if (res) {
if (res.message) {
toast.success(res.message);
}
}
deleteLiteLLMModelId = '';
liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
models.set(await getModels());
};
onMount(async () => {
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
});
</script>
</script>
<div class="flex flex-col h-full justify-between text-sm">
<div class="flex flex-col h-full justify-between text-sm">
<div class=" space-y-3 pr-1.5 overflow-y-scroll h-80">
<div class=" space-y-3 pr-1.5 overflow-y-scroll h-[23rem]">
<div>
{#if ollamaVersion}
<div class=" mb-2.5 text-sm font-medium">Pull a model from Ollama.com</div>
<div class="space-y-2 pr-1.5">
<div class="flex w-full">
<div>
<div class="flex-1 mr-2">
<div class=" mb-2 text-sm font-medium">Manage Ollama Models</div>
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
placeholder="Enter model tag (e.g. mistral:7b)"
bind:value={modelTag}
/>
</div>
<button
class="px-3 text-gray-100 bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded transition"
on:click={() => {
pullModelHandler();
}}
disabled={modelTransferring}
>
{#if modelTransferring}
<div class="self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
{/if}
</button>
</div>
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
To access the available model names for downloading, <a
class=" text-gray-500 dark:text-gray-300 font-medium"
href="https://ollama.com/library"
target="_blank">click here.</a
>
</div>
{#if Object.keys(modelDownloadStatus).length > 0}
<div class=" mb-2 text-sm font-medium">Pull a model from Ollama.com</div>
{#each Object.keys(modelDownloadStatus) as model}
<div class="flex w-full">
<div class="flex flex-col">
<div class="flex-1 mr-2">
<div class="font-medium mb-1">{model}</div>
<input
<div class="">
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
<div
placeholder="Enter model tag (e.g. mistral:7b)"
class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
bind:value={modelTag}
style="width: {Math.max(15, modelDownloadStatus[model].pullProgress ?? 0)}%"
/>
>
{modelDownloadStatus[model].pullProgress ?? 0}%
</div>
<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
{modelDownloadStatus[model].digest}
</div>
</div>
</div>
<button
class="px-3 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
on:click={() => {
pullModelHandler();
}}
disabled={modelTransferring}
>
{#if modelTransferring}
<div class="self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
{/if}
</button>
</div>
</div>
{/each}
{/if}
</div>
<hr class=" dark:border-gray-700" />
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
To access the available model names for downloading, <a
class=" text-gray-500 dark:text-gray-300 font-medium"
href="https://ollama.com/library"
target="_blank">click here.</a
>
</div>
<div>
{#if Object.keys(modelDownloadStatus).length > 0}
<div class=" mb-2.5 text-sm font-medium">Delete
a model
</div>
{#each Object.keys(modelDownloadStatus)
a
s
model
}
<div class="flex
w-ful
l">
<div class="flex
flex-co
l">
<div class="f
lex-1 mr-2"
>
<div class="f
ont-medium mb-1">{model}</div
>
<select
<div class="">
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
<div
bind:value={deleteModelTag}
class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
placeholder="Select a model
"
style="width: {Math.max(15, modelDownloadStatus[model].pullProgress ?? 0)}%
"
>
>
{#if !deleteModelTag}
{modelDownloadStatus[model].pullProgress ?? 0}%
<option value="" disabled selected>Select a model</option
>
</div
>
{/if}
<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
{#each $models.filter((m) => m.size != null) as model
}
{modelDownloadStatus[model].digest
}
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
</div>
>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
</div>
>
</div
>
{/each}
{/each}
</select>
{/if}
</div>
</div>
<button
class="px-3 bg-red-700 hover:bg-red-800 text-gray-100 rounded transition"
on:click={() => {
deleteModelHandler();
}}
>
<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>
<hr class=" dark:border-gray-700" />
<div>
<div class=" mb-2 text-sm font-medium">Delete a model</div>
<form
<div class="flex w-full">
on:submit|preventDefault={() => {
<div class="flex-1 mr-2">
uploadModelHandler();
<select
}}
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
>
bind:value={deleteModelTag}
<div class=" mb-2 flex w-full justify-between">
placeholder="Select a model"
<div class=" text-sm font-medium">
>
Upload a GGUF model <a
{#if !deleteModelTag}
class=" text-xs font-medium text-gray-500 underline"
<option value="" disabled selected>Select a model</option>
href="https://github.com/jmorganca/ollama/blob/main/README.md#import-from-gguf"
{/if}
target="_blank">(Experimental)</a
{#each $models.filter((m) => m.size != null) as model}
>
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
>
{/each}
</select>
</div>
<button
class="px-3 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
on:click={() => {
deleteModelHandler();
}}
>
<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>
<button
<div>
class="p-1 px-3 text-xs flex rounded transition"
<div class="flex justify-between items-center text-xs">
on:click={() => {
<div class=" text-sm font-medium">Experimental</div>
if (modelUploadMode === 'file') {
<button
modelUploadMode = 'url';
class=" text-xs font-medium text-gray-500"
} else {
type="button"
modelUploadMode = 'file';
on:click={() => {
}
showExperimentalOllama = !showExperimentalOllama;
}}
}}>{showExperimentalOllama ? 'Show' : 'Hide'}</button
type="button"
>
>
</div>
{#if modelUploadMode === 'file'}
</div>
<span class="ml-2 self-center">File Mode</span>
{:else}
<span class="ml-2 self-center">URL Mode</span>
{/if}
</button>
</div>
<div class="flex w-full mb-1.5">
{#if showExperimentalOllama}
<div class="flex flex-col w-full">
<form
{#if modelUploadMode === 'file'}
on:submit|preventDefault={() => {
<div class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}">
uploadModelHandler();
<input
}}
id="model-upload-input"
>
type="file"
<div class=" mb-2 flex w-full justify-between">
bind:files={modelInputFile}
<div class=" text-sm font-medium">Upload a GGUF model</div>
on:change={() => {
console.log(modelInputFile);
}}
accept=".gguf"
required
hidden
/>
<button
<button
type="button"
class="p-1 px-3 text-xs flex rounded transition"
class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-800"
on:click={() => {
on:click={() => {
document.getElementById('model-upload-input').click();
if (modelUploadMode === 'file') {
modelUploadMode = 'url';
} else {
modelUploadMode = 'file';
}
}}
}}
type="button"
>
>
{#if model
InputFile && modelInputFile.length > 0
}
{#if model
UploadMode === 'file'
}
{modelInputFile[0].name}
<span class="ml-2 self-center">File Mode</span>
{:else}
{:else}
Click here to select
<span class="ml-2 self-center">URL Mode</span>
{/if}
{/if}
</button>
</button>
</div>
</div>
{:else}
<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
<input
class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-800 outline-none {modelFileUrl !==
''
? 'mr-2'
: ''}"
type="url"
required
bind:value={modelFileUrl}
placeholder="Type HuggingFace Resolve (Download) URL"
/>
</div>
{/if}
</div>
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
<div class="flex w-full mb-1.5">
<button
<div class="flex flex-col w-full">
class="px-3 text-gray-100 bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded transition"
{#if modelUploadMode === 'file'}
type="submit"
<div class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}">
disabled={modelTransferring}
<input
>
id="model-upload-input"
{#if modelTransferring}
type="file"
<div class="self-center">
bind:files={modelInputFile}
<svg
on:change={() => {
class=" w-4 h-4"
console.log(modelInputFile);
viewBox="0 0 24 24"
}}
fill="currentColor"
accept=".gguf"
xmlns="http://www.w3.org/2000/svg"
required
><style>
hidden
.spinner_ajPY {
/>
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
<button
}
type="button"
@keyframes spinner_AtaB {
class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850"
100% {
on:click={() => {
transform: rotate(360deg);
document.getElementById('model-upload-input').click();
}
}}
}
>
</style><path
{#if modelInputFile && modelInputFile.length > 0}
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
{modelInputFile[0].name}
opacity=".25"
{:else}
/><path
Click here to select
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
{/if}
class="spinner_ajPY"
</button>
/></svg
</div>
{:else}
<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
<input
class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
''
? 'mr-2'
: ''}"
type="url"
required
bind:value={modelFileUrl}
placeholder="Type HuggingFace Resolve (Download) URL"
/>
</div>
{/if}
</div>
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
<button
class="px-3 text-gray-100 bg-emerald-600 hover:bg-emerald-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded transition"
type="submit"
disabled={modelTransferring}
>
>
{#if modelTransferring}
<div class="self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
{/if}
</button>
{/if}
</div>
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
<div>
<div>
<div class=" my-2.5 text-sm font-medium">Modelfile Content</div>
<textarea
bind:value={modelFileContent}
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
rows="6"
/>
</div>
</div>
</div>
{
:else
}
{
/if
}
<svg
<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
xmlns="http://www.w3.org/2000/svg"
To access the GGUF models available for downloading, <a
viewBox="0 0 16 16
"
class=" text-gray-500 dark:text-gray-300 font-medium
"
fill="currentColor
"
href="https://huggingface.co/models?search=gguf
"
class="w-4 h-4"
target="_blank">click here.</a
>
>
<path
</div>
d="M7.25 10.25a.75.75 0 0 0 1.5 0V4.56l2.22 2.22a.75.75 0 1 0 1.06-1.06l-3.5-3.5a.75.75 0 0 0-1.06 0l-3.5 3.5a.75.75 0 0 0 1.06 1.06l2.22-2.22v5.69Z"
/>
{#if uploadProgress !== null}
<path
<div class="mt-2">
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
<div class=" mb-2 text-xs">Upload Progress</div>
/>
</svg>
<div class="w-full rounded-full dark:bg-gray-800">
<div
class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
style="width: {Math.max(15, uploadProgress ?? 0)}%"
>
{uploadProgress ?? 0}%
</div>
</div>
<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
{modelFileDigest}
</div>
</div>
{/if}
{/if}
</
button
>
</
form
>
{/if}
{/if}
</div>
</div>
<hr class=" dark:border-gray-700 my-2" />
{/if}
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
<div class=" space-y-3">
<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>
<div class="flex justify-between items-center text-xs">
<div class=" text-sm font-medium">Add a model</div>
<button
class=" text-xs font-medium text-gray-500"
type="button"
on:click={() => {
showLiteLLMParams = !showLiteLLMParams;
}}>{showLiteLLMParams ? 'Advanced' : 'Default'}</button
>
</div>
</div>
<div class="my-2 space-y-2">
<div class="flex w-full mb-1.5">
<div class="flex-1 mr-2">
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM Model (litellm_params.model)"
bind:value={liteLLMModel}
autocomplete="off"
/>
</div>
<button
class="px-3 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
on:click={() => {
addLiteLLMModelHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
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>
</button>
</div>
{#if showLiteLLMParams}
<div>
<div class=" mb-1.5 text-sm font-medium">Model Name</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter Model Name (model_name)"
bind:value={liteLLMModelName}
autocomplete="off"
/>
</div>
</div>
</div>
<div>
<div class=" mb-1.5 text-sm font-medium">API Base URL</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM API Base URL (litellm_params.api_base)"
bind:value={liteLLMAPIBase}
autocomplete="off"
/>
</div>
</div>
</div>
<div>
<div class=" mb-1.5 text-sm font-medium">API Key</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded 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)"
bind:value={liteLLMAPIKey}
autocomplete="off"
/>
</div>
</div>
</div>
<div>
<div class="mb-1.5 text-sm font-medium">API RPM</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM API RPM (litellm_params.rpm)"
bind:value={liteLLMRPM}
autocomplete="off"
/>
</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"
href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
target="_blank"
>
Click here for help.
</a>
</div>
<div>
<div>
<div class=" my-2.5 text-sm font-medium">Modelfile Content</div>
<div class=" mb-2.5 text-sm font-medium">Delete a model</div>
<textarea
<div class="flex w-full">
bind:value={modelFileContent}
<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 resize-none"
<select
rows="6"
class="w-full rounded 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-3 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded 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>
{/if}
<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
To access the GGUF models available for downloading, <a
class=" text-gray-500 dark:text-gray-300 font-medium"
href="https://huggingface.co/models?search=gguf"
target="_blank">click here.</a
>
</div>
</div>
{#if uploadProgress !== null}
<!-- <div class="mt-2 space-y-3 pr-1.5">
<div class="mt-2">
<div>
<div class=" mb-2 text-xs">Upload Progress</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="w-full rounded-full dark:bg-gray-800">
<div class="flex justify-between items-center text-sm">
<div
<div class=" font-medium">Advanced Model Params</div>
class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
<button
style="width: {Math.max(15, uploadProgress ?? 0)}%"
class=" text-xs font-medium text-gray-500"
type="button"
on:click={() => {
showLiteLLMParams = !showLiteLLMParams;
}}>{showLiteLLMParams ? 'Hide' : 'Show'}</button
>
>
{uploadProgress ?? 0}%
</div>
</div>
</div>
<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
{modelFileDigest}
{#if showLiteLLMParams}
<div>
<div class=" mb-2.5 text-sm font-medium">LiteLLM API Key</div>
<div class="flex w-full">
<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 API Key (e.g. os.environ/AZURE_API_KEY_CA)"
bind:value={liteLLMAPIKey}
autocomplete="off"
/>
</div>
</div>
</div>
<div>
<div class=" mb-2.5 text-sm font-medium">LiteLLM API Base URL</div>
<div class="flex w-full">
<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 API Base URL"
bind:value={liteLLMAPIBase}
autocomplete="off"
/>
</div>
</div>
</div>
<div>
<div class=" mb-2.5 text-sm font-medium">LiteLLM API RPM</div>
<div class="flex w-full">
<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 API RPM"
bind:value={liteLLMRPM}
autocomplete="off"
/>
</div>
</div>
</div>
{/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"
href="https://litellm.vercel.app/docs/proxy/configs#quick-start"
target="_blank"
>
Click here for help.
</a>
</div>
</div>
</div>
</div>
{/if}
</div> -->
</
form
>
</
div
>
</div>
</div>
</div>
</div>
src/lib/components/chat/SettingsModal.svelte
View file @
02fb517b
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
import { getOllamaModels } from '$lib/apis/ollama';
import { getOllamaModels } from '$lib/apis/ollama';
import { getOpenAIModels } from '$lib/apis/openai';
import { getOpenAIModels } from '$lib/apis/openai';
import { getLiteLLMModels } from '$lib/apis/litellm';
import Modal from '../common/Modal.svelte';
import Modal from '../common/Modal.svelte';
import Account from './Settings/Account.svelte';
import Account from './Settings/Account.svelte';
...
@@ -27,23 +28,29 @@
...
@@ -27,23 +28,29 @@
let selectedTab = 'general';
let selectedTab = 'general';
const getModels = async (type = 'all') => {
const getModels = async () => {
const models = [];
let models = await Promise.all([
models.push(
await getOllamaModels(localStorage.token).catch((error) => {
...(await getOllamaModels(localStorage.token).catch((error) => {
console.log(error);
toast.error(error);
return null;
return [];
}),
}))
await getOpenAIModels(localStorage.token).catch((error) => {
);
if (type === 'all') {
const openAIModels = await getOpenAIModels(localStorage.token).catch((error) => {
console.log(error);
console.log(error);
return null;
return null;
});
}),
models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
await getLiteLLMModels(localStorage.token).catch((error) => {
}
console.log(error);
return null;
})
]);
models = models
.filter((models) => models)
.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
// models.push(...(ollamaModels ? [{ name: 'hr' }, ...ollamaModels] : []));
// models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
// models.push(...(liteLLMModels ? [{ name: 'hr' }, ...liteLLMModels] : []));
return models;
return models;
};
};
</script>
</script>
...
...
src/lib/constants.ts
View file @
02fb517b
...
@@ -5,6 +5,8 @@ export const APP_NAME = 'Open WebUI';
...
@@ -5,6 +5,8 @@ export const APP_NAME = 'Open WebUI';
export
const
WEBUI_BASE_URL
=
dev
?
`http://
${
location
.
hostname
}
:8080`
:
``
;
export
const
WEBUI_BASE_URL
=
dev
?
`http://
${
location
.
hostname
}
:8080`
:
``
;
export
const
WEBUI_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/api/v1`
;
export
const
WEBUI_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/api/v1`
;
export
const
LITELLM_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/litellm/api`
;
export
const
OLLAMA_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/ollama/api`
;
export
const
OLLAMA_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/ollama/api`
;
export
const
OPENAI_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/openai/api`
;
export
const
OPENAI_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/openai/api`
;
export
const
AUDIO_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/audio/api/v1`
;
export
const
AUDIO_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/audio/api/v1`
;
...
...
src/routes/(app)/+layout.svelte
View file @
02fb517b
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
import { getModelfiles } from '$lib/apis/modelfiles';
import { getModelfiles } from '$lib/apis/modelfiles';
import { getPrompts } from '$lib/apis/prompts';
import { getPrompts } from '$lib/apis/prompts';
import { getOpenAIModels } from '$lib/apis/openai';
import { getOpenAIModels } from '$lib/apis/openai';
import { getLiteLLMModels } from '$lib/apis/litellm';
import { getDocs } from '$lib/apis/documents';
import { getDocs } from '$lib/apis/documents';
import { getAllChatTags } from '$lib/apis/chats';
import { getAllChatTags } from '$lib/apis/chats';
...
@@ -43,24 +44,28 @@
...
@@ -43,24 +44,28 @@
let showShortcuts = false;
let showShortcuts = false;
const getModels = async () => {
const getModels = async () => {
let models = [];
let models = await Promise.all([
models.push(
await getOllamaModels(localStorage.token).catch((error) => {
...(await getOllamaModels(localStorage.token).catch((error) => {
console.log(error);
toast.error(error);
return null;
return [];
}),
}))
await getOpenAIModels(localStorage.token).catch((error) => {
);
console.log(error);
return null;
// $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1',
}),
// $settings.OPENAI_API_KEY
await getLiteLLMModels(localStorage.token).catch((error) => {
console.log(error);
const openAIModels = await getOpenAIModels(localStorage.token).catch((error) => {
return null;
console.log(error);
})
return null;
]);
});
models = models
models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
.filter((models) => models)
.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
// models.push(...(ollamaModels ? [{ name: 'hr' }, ...ollamaModels] : []));
// models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
// models.push(...(liteLLMModels ? [{ name: 'hr' }, ...liteLLMModels] : []));
return models;
return models;
};
};
...
@@ -117,8 +122,6 @@
...
@@ -117,8 +122,6 @@
await models.set(await getModels());
await models.set(await getModels());
});
});
await setOllamaVersion();
document.addEventListener('keydown', function (event) {
document.addEventListener('keydown', function (event) {
const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
// Check if the Shift key is pressed
// Check if the Shift key is pressed
...
@@ -250,60 +253,6 @@
...
@@ -250,60 +253,6 @@
</div>
</div>
</div>
</div>
</div>
</div>
{:else if checkVersion(REQUIRED_OLLAMA_VERSION, ollamaVersion ?? '0')}
<div class="fixed w-full h-full flex z-50">
<div
class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 flex justify-center"
>
<div class="m-auto pb-44 flex flex-col justify-center">
<div class="max-w-md">
<div class="text-center dark:text-white text-2xl font-medium z-50">
Connection Issue or Update Needed
</div>
<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
Oops! It seems like your Ollama needs a little attention. <br
class=" hidden sm:flex"
/>We've detected either a connection hiccup or observed that you're using an older
version. Ensure you're on the latest Ollama version
<br class=" hidden sm:flex" />(version
<span class=" dark:text-white font-medium">{REQUIRED_OLLAMA_VERSION} or higher</span
>) or check your connection.
<div class="mt-1 text-sm">
Trouble accessing Ollama?
<a
class=" text-black dark:text-white font-semibold underline"
href="https://github.com/open-webui/open-webui#troubleshooting"
target="_blank"
>
Click here for help.
</a>
</div>
</div>
<div class=" mt-6 mx-auto relative group w-fit">
<button
class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
on:click={async () => {
location.href = '/';
// await setOllamaVersion();
}}
>
Check Again
</button>
<button
class="text-xs text-center w-full mt-2 text-gray-400 underline"
on:click={async () => {
await setOllamaVersion(REQUIRED_OLLAMA_VERSION);
}}>Close</button
>
</div>
</div>
</div>
</div>
</div>
{:else if localDBChats.length > 0}
{:else if localDBChats.length > 0}
<div class="fixed w-full h-full flex z-50">
<div class="fixed w-full h-full flex z-50">
<div
<div
...
...
src/routes/(app)/+page.svelte
View file @
02fb517b
...
@@ -36,6 +36,7 @@
...
@@ -36,6 +36,7 @@
import
ModelSelector
from
'$lib/components/chat/ModelSelector.svelte'
;
import
ModelSelector
from
'$lib/components/chat/ModelSelector.svelte'
;
import
Navbar
from
'$lib/components/layout/Navbar.svelte'
;
import
Navbar
from
'$lib/components/layout/Navbar.svelte'
;
import
{
RAGTemplate
}
from
'$lib/utils/rag'
;
import
{
RAGTemplate
}
from
'$lib/utils/rag'
;
import
{
LITELLM_API_BASE_URL
,
OPENAI_API_BASE_URL
}
from
'$lib/constants'
;
import
{
WEBUI_BASE_URL
}
from
'$lib/constants'
;
import
{
WEBUI_BASE_URL
}
from
'$lib/constants'
;
let
stopResponseFlag
=
false
;
let
stopResponseFlag
=
false
;
...
@@ -132,6 +133,10 @@
...
@@ -132,6 +133,10 @@
selectedModels
=
[
''
];
selectedModels
=
[
''
];
}
}
selectedModels
=
selectedModels
.
map
((
modelId
)
=>
$
models
.
map
((
m
)
=>
m
.
id
).
includes
(
modelId
)
?
modelId
:
''
);
let
_settings
=
JSON
.
parse
(
localStorage
.
getItem
(
'settings'
)
??
'{}'
);
let
_settings
=
JSON
.
parse
(
localStorage
.
getItem
(
'settings'
)
??
'{}'
);
settings
.
set
({
settings
.
set
({
...
_settings
...
_settings
...
@@ -150,6 +155,10 @@
...
@@ -150,6 +155,10 @@
const
submitPrompt
=
async
(
userPrompt
,
_user
=
null
)
=>
{
const
submitPrompt
=
async
(
userPrompt
,
_user
=
null
)
=>
{
console
.
log
(
'submitPrompt'
,
$
chatId
);
console
.
log
(
'submitPrompt'
,
$
chatId
);
selectedModels
=
selectedModels
.
map
((
modelId
)
=>
$
models
.
map
((
m
)
=>
m
.
id
).
includes
(
modelId
)
?
modelId
:
''
);
if
(
selectedModels
.
includes
(
''
))
{
if
(
selectedModels
.
includes
(
''
))
{
toast
.
error
(
'Model not selected'
);
toast
.
error
(
'Model not selected'
);
}
else
if
(
messages
.
length
!= 0 && messages.at(-1).done != true) {
}
else
if
(
messages
.
length
!= 0 && messages.at(-1).done != true) {
...
@@ -278,40 +287,41 @@
...
@@ -278,40 +287,41 @@
}
}
await
Promise
.
all
(
await
Promise
.
all
(
selectedModels.map(async (model) => {
selectedModels
.
map
(
async
(
model
Id
)
=>
{
cons
ole.log(model
);
cons
t
model
=
$
models
.
filter
((
m
)
=>
m
.
id
===
modelId
).
at
(
0
);
const modelTag = $models.filter((m) => m.name === model).at(0);
if
(
model
)
{
// Create response message
//
Create
response
message
let responseMessageId = uuidv4();
let
responseMessageId
=
uuidv4
();
let responseMessage = {
let
responseMessage
=
{
parentId: parentId,
parentId
:
parentId
,
id: responseMessageId,
id
:
responseMessageId
,
childrenIds: [],
childrenIds
:
[],
role: 'assistant',
role
:
'assistant'
,
content: '',
content
:
''
,
model: model,
model
:
model
.
id
,
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
timestamp
:
Math
.
floor
(
Date
.
now
()
/
1000
)
//
Unix
epoch
};
};
// Add message to history and Set currentId to messageId
//
Add
message
to
history
and
Set
currentId
to
messageId
history.messages[responseMessageId] = responseMessage;
history
.
messages
[
responseMessageId
]
=
responseMessage
;
history.currentId = responseMessageId;
history
.
currentId
=
responseMessageId
;
// Append messageId to childrenIds of parent message
//
Append
messageId
to
childrenIds
of
parent
message
if (parentId !== null) {
if
(
parentId
!== null) {
history.messages[parentId].childrenIds = [
history
.
messages
[
parentId
].
childrenIds
=
[
...history.messages[parentId].childrenIds,
...
history
.
messages
[
parentId
].
childrenIds
,
responseMessageId
responseMessageId
];
];
}
}
if (modelTag?.external) {
if
(
model
?.
external
)
{
await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
await
sendPromptOpenAI
(
model
,
prompt
,
responseMessageId
,
_chatId
);
} else if (modelTag) {
}
else
if
(
model
)
{
await sendPromptOllama(model, prompt, responseMessageId, _chatId);
await
sendPromptOllama
(
model
,
prompt
,
responseMessageId
,
_chatId
);
}
}
else
{
}
else
{
toast.error(`Model ${model} not found`);
toast
.
error
(`
Model
${
model
Id
}
not
found
`);
}
}
})
})
);
);
...
@@ -320,6 +330,7 @@
...
@@ -320,6 +330,7 @@
};
};
const
sendPromptOllama
=
async
(
model
,
userPrompt
,
responseMessageId
,
_chatId
)
=>
{
const
sendPromptOllama
=
async
(
model
,
userPrompt
,
responseMessageId
,
_chatId
)
=>
{
model
=
model
.
id
;
const
responseMessage
=
history
.
messages
[
responseMessageId
];
const
responseMessage
=
history
.
messages
[
responseMessageId
];
//
Wait
until
history
/
message
have
been
updated
//
Wait
until
history
/
message
have
been
updated
...
@@ -531,54 +542,58 @@
...
@@ -531,54 +542,58 @@
const
responseMessage
=
history
.
messages
[
responseMessageId
];
const
responseMessage
=
history
.
messages
[
responseMessageId
];
scrollToBottom
();
scrollToBottom
();
const res = await generateOpenAIChatCompletion(localStorage.token, {
const
res
=
await
generateOpenAIChatCompletion
(
model: model,
localStorage
.
token
,
stream: true,
{
messages: [
model
:
model
.
id
,
$settings.system
stream
:
true
,
? {
messages
:
[
role: 'system',
$
settings
.
system
content: $settings.system
}
: undefined,
...messages.filter((message) => !message.deleted)
]
.filter((message) => message)
.map((message, idx, arr) => ({
role: message.role,
...(message.files?.filter((file) => file.type === 'image').length > 0 ?? false
?
{
?
{
content: [
role
:
'system'
,
{
content
:
$
settings
.
system
type: 'text',
text:
arr.length - 1 !== idx
? message.content
: message?.raContent ?? message.content
},
...message.files
.filter((file) => file.type === 'image')
.map((file) => ({
type: 'image_url',
image_url: {
url: file.url
}
}))
]
}
}
: {
:
undefined
,
content:
...
messages
.
filter
((
message
)
=>
!message.deleted)
arr.length - 1 !== idx ? message.content : message?.raContent ?? message.content
]
})
.
filter
((
message
)
=>
message
)
})),
.
map
((
message
,
idx
,
arr
)
=>
({
seed: $settings?.options?.seed ?? undefined,
role
:
message
.
role
,
stop: $settings?.options?.stop ?? undefined,
...(
message
.
files
?.
filter
((
file
)
=>
file
.
type
===
'image'
).
length
>
0
??
false
temperature: $settings?.options?.temperature ?? undefined,
?
{
top_p: $settings?.options?.top_p ?? undefined,
content
:
[
num_ctx: $settings?.options?.num_ctx ?? undefined,
{
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
type
:
'text'
,
max_tokens: $settings?.options?.num_predict ?? undefined
text
:
});
arr
.
length
-
1
!== idx
?
message
.
content
:
message
?.
raContent
??
message
.
content
},
...
message
.
files
.
filter
((
file
)
=>
file
.
type
===
'image'
)
.
map
((
file
)
=>
({
type
:
'image_url'
,
image_url
:
{
url
:
file
.
url
}
}))
]
}
:
{
content
:
arr
.
length
-
1
!== idx ? message.content : message?.raContent ?? message.content
})
})),
seed
:
$
settings
?.
options
?.
seed
??
undefined
,
stop
:
$
settings
?.
options
?.
stop
??
undefined
,
temperature
:
$
settings
?.
options
?.
temperature
??
undefined
,
top_p
:
$
settings
?.
options
?.
top_p
??
undefined
,
num_ctx
:
$
settings
?.
options
?.
num_ctx
??
undefined
,
frequency_penalty
:
$
settings
?.
options
?.
repeat_penalty
??
undefined
,
max_tokens
:
$
settings
?.
options
?.
num_predict
??
undefined
},
model
.
source
===
'litellm'
?
`${
LITELLM_API_BASE_URL
}/
v1
`
:
`${
OPENAI_API_BASE_URL
}`
);
if
(
res
&&
res
.
ok
)
{
if
(
res
&&
res
.
ok
)
{
const
reader
=
res
.
body
const
reader
=
res
.
body
...
...
test.json
0 → 100644
View file @
02fb517b
{
"model_name"
:
"string"
,
"litellm_params"
:
{
"model"
:
"ollama/mistral"
}
}
\ No newline at end of file
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment