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

Merge pull request #2735 from open-webui/dev

0.2.1
parents 72354e06 6e818e36
...@@ -70,6 +70,7 @@ jobs: ...@@ -70,6 +70,7 @@ jobs:
images: ${{ env.FULL_IMAGE_NAME }} images: ${{ env.FULL_IMAGE_NAME }}
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=ref,event=tag
flavor: | flavor: |
prefix=cache-${{ matrix.platform }}- prefix=cache-${{ matrix.platform }}-
......
...@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. ...@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.2.1] - 2024-06-02
### Added
- **🖱️ Single Model Export Button**: Easily export models with just one click using the new single model export button.
- **🖥️ Advanced Parameters Support**: Added support for `num_thread`, `use_mmap`, and `use_mlock` parameters for Ollama.
- **🌐 Improved Vietnamese Translation**: Enhanced Vietnamese language support for a better user experience for our Vietnamese-speaking community.
### Fixed
- **🔧 OpenAI URL API Save Issue**: Corrected a problem preventing the saving of OpenAI URL API settings.
- **🚫 Display Issue with Disabled Ollama API**: Fixed the display bug causing models to appear in settings when the Ollama API was disabled.
### Changed
- **💡 Versioning Update**: As a reminder from our previous update, version 0.2.y will focus primarily on bug fixes, while major updates will be designated as 0.x from now on for better version tracking.
## [0.2.0] - 2024-06-01 ## [0.2.0] - 2024-06-01
### Added ### Added
......
...@@ -906,44 +906,77 @@ async def generate_chat_completion( ...@@ -906,44 +906,77 @@ async def generate_chat_completion(
if model_info.params: if model_info.params:
payload["options"] = {} payload["options"] = {}
payload["options"]["mirostat"] = model_info.params.get("mirostat", None) if model_info.params.get("mirostat", None):
payload["options"]["mirostat_eta"] = model_info.params.get( payload["options"]["mirostat"] = model_info.params.get("mirostat", None)
"mirostat_eta", None
)
payload["options"]["mirostat_tau"] = model_info.params.get(
"mirostat_tau", None
)
payload["options"]["num_ctx"] = model_info.params.get("num_ctx", None)
payload["options"]["repeat_last_n"] = model_info.params.get( if model_info.params.get("mirostat_eta", None):
"repeat_last_n", None payload["options"]["mirostat_eta"] = model_info.params.get(
) "mirostat_eta", None
payload["options"]["repeat_penalty"] = model_info.params.get( )
"frequency_penalty", None
)
payload["options"]["temperature"] = model_info.params.get( if model_info.params.get("mirostat_tau", None):
"temperature", None
)
payload["options"]["seed"] = model_info.params.get("seed", None)
payload["options"]["stop"] = ( payload["options"]["mirostat_tau"] = model_info.params.get(
[ "mirostat_tau", None
bytes(stop, "utf-8").decode("unicode_escape") )
for stop in model_info.params["stop"]
]
if model_info.params.get("stop", None)
else None
)
payload["options"]["tfs_z"] = model_info.params.get("tfs_z", None) if model_info.params.get("num_ctx", None):
payload["options"]["num_ctx"] = model_info.params.get("num_ctx", None)
payload["options"]["num_predict"] = model_info.params.get( if model_info.params.get("repeat_last_n", None):
"max_tokens", None payload["options"]["repeat_last_n"] = model_info.params.get(
) "repeat_last_n", None
payload["options"]["top_k"] = model_info.params.get("top_k", None) )
if model_info.params.get("frequency_penalty", None):
payload["options"]["repeat_penalty"] = model_info.params.get(
"frequency_penalty", None
)
if model_info.params.get("temperature", None):
payload["options"]["temperature"] = model_info.params.get(
"temperature", None
)
if model_info.params.get("seed", None):
payload["options"]["seed"] = model_info.params.get("seed", None)
if model_info.params.get("stop", None):
payload["options"]["stop"] = (
[
bytes(stop, "utf-8").decode("unicode_escape")
for stop in model_info.params["stop"]
]
if model_info.params.get("stop", None)
else None
)
if model_info.params.get("tfs_z", None):
payload["options"]["tfs_z"] = model_info.params.get("tfs_z", None)
payload["options"]["top_p"] = model_info.params.get("top_p", None) if model_info.params.get("max_tokens", None):
payload["options"]["num_predict"] = model_info.params.get(
"max_tokens", None
)
if model_info.params.get("top_k", None):
payload["options"]["top_k"] = model_info.params.get("top_k", None)
if model_info.params.get("top_p", None):
payload["options"]["top_p"] = model_info.params.get("top_p", None)
if model_info.params.get("use_mmap", None):
payload["options"]["use_mmap"] = model_info.params.get("use_mmap", None)
if model_info.params.get("use_mlock", None):
payload["options"]["use_mlock"] = model_info.params.get(
"use_mlock", None
)
if model_info.params.get("num_thread", None):
payload["options"]["num_thread"] = model_info.params.get(
"num_thread", None
)
if model_info.params.get("system", None): if model_info.params.get("system", None):
# Check if the payload already has a system message # Check if the payload already has a system message
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.2.0", "version": "0.2.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "open-webui", "name": "open-webui",
"version": "0.2.0", "version": "0.2.1",
"dependencies": { "dependencies": {
"@pyscript/core": "^0.4.32", "@pyscript/core": "^0.4.32",
"@sveltejs/adapter-node": "^1.3.1", "@sveltejs/adapter-node": "^1.3.1",
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.2.0", "version": "0.2.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "npm run pyodide:fetch && vite dev --host", "dev": "npm run pyodide:fetch && vite dev --host",
......
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
tfs_z: '', tfs_z: '',
num_ctx: '', num_ctx: '',
max_tokens: '', max_tokens: '',
use_mmap: null,
use_mlock: null,
num_thread: null,
template: null template: null
}; };
...@@ -559,6 +562,7 @@ ...@@ -559,6 +562,7 @@
</div> </div>
{/if} {/if}
</div> </div>
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens (num_predict)')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens (num_predict)')}</div>
...@@ -604,6 +608,93 @@ ...@@ -604,6 +608,93 @@
</div> </div>
{/if} {/if}
</div> </div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('use_mmap (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.use_mmap = (params?.use_mmap ?? null) === null ? true : null;
}}
>
{#if (params?.use_mmap ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
</div>
</div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('use_mlock (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.use_mlock = (params?.use_mlock ?? null) === null ? true : null;
}}
>
{#if (params?.use_mlock ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
</div>
</div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('num_thread (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.num_thread = (params?.num_thread ?? null) === null ? 2 : null;
}}
>
{#if (params?.num_thread ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if (params?.num_thread ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="1"
max="256"
step="1"
bind:value={params.num_thread}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div class="">
<input
bind:value={params.num_thread}
type="number"
class=" bg-transparent text-center w-14"
min="1"
max="256"
step="1"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Template')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('Template')}</div>
......
<script lang="ts"> <script lang="ts">
import { models, user } from '$lib/stores'; import { models, user } from '$lib/stores';
import { createEventDispatcher, onMount, getContext } from 'svelte'; import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { import {
...@@ -74,23 +74,48 @@ ...@@ -74,23 +74,48 @@
}; };
const updateOpenAIHandler = async () => { const updateOpenAIHandler = async () => {
// Check if API KEYS length is same than API URLS length
if (OPENAI_API_KEYS.length !== OPENAI_API_BASE_URLS.length) {
// if there are more keys than urls, remove the extra keys
if (OPENAI_API_KEYS.length > OPENAI_API_BASE_URLS.length) {
OPENAI_API_KEYS = OPENAI_API_KEYS.slice(0, OPENAI_API_BASE_URLS.length);
}
// if there are more urls than keys, add empty keys
if (OPENAI_API_KEYS.length < OPENAI_API_BASE_URLS.length) {
const diff = OPENAI_API_BASE_URLS.length - OPENAI_API_KEYS.length;
for (let i = 0; i < diff; i++) {
OPENAI_API_KEYS.push('');
}
}
}
OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS); OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS); OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS);
await models.set(await getModels()); await models.set(await getModels());
}; };
const updateOllamaUrlsHandler = async () => { const updateOllamaUrlsHandler = async () => {
OLLAMA_BASE_URLS = await updateOllamaUrls(localStorage.token, OLLAMA_BASE_URLS); OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url) => url !== '');
console.log(OLLAMA_BASE_URLS);
const ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => { if (OLLAMA_BASE_URLS.length === 0) {
toast.error(error); ENABLE_OLLAMA_API = false;
return null; await updateOllamaConfig(localStorage.token, ENABLE_OLLAMA_API);
});
if (ollamaVersion) { toast.info($i18n.t('Ollama API disabled'));
toast.success($i18n.t('Server connection verified')); } else {
await models.set(await getModels()); OLLAMA_BASE_URLS = await updateOllamaUrls(localStorage.token, OLLAMA_BASE_URLS);
const ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
toast.error(error);
return null;
});
if (ollamaVersion) {
toast.success($i18n.t('Server connection verified'));
await models.set(await getModels());
}
} }
}; };
...@@ -286,6 +311,10 @@ ...@@ -286,6 +311,10 @@
bind:state={ENABLE_OLLAMA_API} bind:state={ENABLE_OLLAMA_API}
on:change={async () => { on:change={async () => {
updateOllamaConfig(localStorage.token, ENABLE_OLLAMA_API); updateOllamaConfig(localStorage.token, ENABLE_OLLAMA_API);
if (OLLAMA_BASE_URLS.length === 0) {
OLLAMA_BASE_URLS = [''];
}
}} }}
/> />
</div> </div>
......
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
getOllamaVersion, getOllamaVersion,
pullModel, pullModel,
cancelOllamaRequest, cancelOllamaRequest,
uploadModel uploadModel,
getOllamaConfig
} from '$lib/apis/ollama'; } 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';
...@@ -28,6 +29,8 @@ ...@@ -28,6 +29,8 @@
// Models // Models
let ollamaEnabled = null;
let OLLAMA_URLS = []; let OLLAMA_URLS = [];
let selectedOllamaUrlIdx: string | null = null; let selectedOllamaUrlIdx: string | null = null;
...@@ -431,53 +434,138 @@ ...@@ -431,53 +434,138 @@
}; };
onMount(async () => { onMount(async () => {
await Promise.all([ const ollamaConfig = await getOllamaConfig(localStorage.token);
(async () => {
OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
toast.error(error);
return [];
});
if (OLLAMA_URLS.length > 0) { if (ollamaConfig.ENABLE_OLLAMA_API) {
selectedOllamaUrlIdx = 0; ollamaEnabled = true;
}
})(), await Promise.all([
(async () => { (async () => {
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false); OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
})() toast.error(error);
]); return [];
});
if (OLLAMA_URLS.length > 0) {
selectedOllamaUrlIdx = 0;
}
})(),
(async () => {
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
})()
]);
} else {
ollamaEnabled = false;
toast.error('Ollama API is disabled');
}
}); });
</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-[24rem]"> <div class=" space-y-3 pr-1.5 overflow-y-scroll h-[24rem]">
{#if ollamaVersion !== null} {#if ollamaEnabled}
<div class="space-y-2 pr-1.5"> {#if ollamaVersion !== null}
<div class="text-sm font-medium">{$i18n.t('Manage Ollama Models')}</div> <div class="space-y-2 pr-1.5">
<div class="text-sm font-medium">{$i18n.t('Manage Ollama Models')}</div>
{#if OLLAMA_URLS.length > 0}
<div class="flex gap-2"> {#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={$i18n.t('Select an Ollama instance')} bind:value={selectedOllamaUrlIdx}
> placeholder={$i18n.t('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}
<div class="space-y-2">
<div> <div>
<div class="flex w-full justify-end"> <div class=" mb-2 text-sm font-medium">{$i18n.t('Pull a model from Ollama.com')}</div>
<Tooltip content="Update All Models" placement="top"> <div class="flex w-full">
<button <div class="flex-1 mr-2">
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" <input
on:click={() => { class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
updateModelsHandler(); placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
}} modelTag: 'mistral:7b'
> })}
bind:value={modelTag}
/>
</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={() => {
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 <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16" viewBox="0 0 16 16"
...@@ -485,74 +573,111 @@ ...@@ -485,74 +573,111 @@
class="w-4 h-4" class="w-4 h-4"
> >
<path <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" 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 <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" 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> </svg>
</button> {/if}
</Tooltip> </button>
</div> </div>
</div>
</div>
{#if updateModelId} <div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''} {$i18n.t('To access the available model names for downloading,')}
{/if} <a
{/if} class=" text-gray-500 dark:text-gray-300 font-medium underline"
href="https://ollama.com/library"
<div class="space-y-2"> target="_blank">{$i18n.t('click here.')}</a
<div> >
<div class=" mb-2 text-sm font-medium">{$i18n.t('Pull a model from Ollama.com')}</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={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
modelTag: 'mistral:7b'
})}
bind:value={modelTag}
/>
</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" {#if Object.keys($MODEL_DOWNLOAD_POOL).length > 0}
on:click={() => { {#each Object.keys($MODEL_DOWNLOAD_POOL) as model}
pullModelHandler(); {#if 'pullProgress' in $MODEL_DOWNLOAD_POOL[model]}
}} <div class="flex flex-col">
disabled={modelTransferring} <div class="font-medium mb-1">{model}</div>
> <div class="">
{#if modelTransferring} <div class="flex flex-row justify-between space-x-4 pr-2">
<div class="self-center"> <div class=" flex-1">
<svg <div
class=" w-4 h-4" class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
viewBox="0 0 24 24" style="width: {Math.max(
fill="currentColor" 15,
xmlns="http://www.w3.org/2000/svg" $MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0
> )}%"
<style> >
.spinner_ajPY { {$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0}%
transform-origin: center; </div>
animation: spinner_AtaB 0.75s infinite linear; </div>
}
<Tooltip content={$i18n.t('Cancel')}>
@keyframes spinner_AtaB { <button
100% { class="text-gray-800 dark:text-gray-100"
transform: rotate(360deg); on:click={() => {
} cancelModelPullHandler(model);
} }}
</style> >
<path <svg
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" class="w-4 h-4 text-gray-800 dark:text-white"
opacity=".25" aria-hidden="true"
/> xmlns="http://www.w3.org/2000/svg"
<path width="24"
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" height="24"
class="spinner_ajPY" fill="currentColor"
/> viewBox="0 0 24 24"
</svg> >
</div> <path
{:else} stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6"
/>
</svg>
</button>
</Tooltip>
</div>
{#if 'digest' in $MODEL_DOWNLOAD_POOL[model]}
<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
{$MODEL_DOWNLOAD_POOL[model].digest}
</div>
{/if}
</div>
</div>
{/if}
{/each}
{/if}
</div>
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('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={deleteModelTag}
placeholder={$i18n.t('Select a model')}
>
{#if !deleteModelTag}
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
{/if}
{#each $models.filter((m) => !(m?.preset ?? false) && m.owned_by === 'ollama' && (selectedOllamaUrlIdx === null ? true : (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))) as model}
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
>{model.name +
' (' +
(model.ollama.size / 1024 ** 3).toFixed(1) +
' GB)'}</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={() => {
deleteModelHandler();
}}
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16" viewBox="0 0 16 16"
...@@ -560,330 +685,229 @@ ...@@ -560,330 +685,229 @@
class="w-4 h-4" class="w-4 h-4"
> >
<path <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" 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"
<path clip-rule="evenodd"
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> </svg>
{/if} </button>
</button> </div>
</div>
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t('To access the available model names for downloading,')}
<a
class=" text-gray-500 dark:text-gray-300 font-medium underline"
href="https://ollama.com/library"
target="_blank">{$i18n.t('click here.')}</a
>
</div> </div>
{#if Object.keys($MODEL_DOWNLOAD_POOL).length > 0} <div class="pt-1">
{#each Object.keys($MODEL_DOWNLOAD_POOL) as model} <div class="flex justify-between items-center text-xs">
{#if 'pullProgress' in $MODEL_DOWNLOAD_POOL[model]} <div class=" text-sm font-medium">{$i18n.t('Experimental')}</div>
<div class="flex flex-col"> <button
<div class="font-medium mb-1">{model}</div> class=" text-xs font-medium text-gray-500"
<div class=""> type="button"
<div class="flex flex-row justify-between space-x-4 pr-2"> on:click={() => {
<div class=" flex-1"> showExperimentalOllama = !showExperimentalOllama;
<div }}>{showExperimentalOllama ? $i18n.t('Hide') : $i18n.t('Show')}</button
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,
$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0
)}%"
>
{$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0}%
</div>
</div>
<Tooltip content={$i18n.t('Cancel')}>
<button
class="text-gray-800 dark:text-gray-100"
on:click={() => {
cancelModelPullHandler(model);
}}
>
<svg
class="w-4 h-4 text-gray-800 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6"
/>
</svg>
</button>
</Tooltip>
</div>
{#if 'digest' in $MODEL_DOWNLOAD_POOL[model]}
<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
{$MODEL_DOWNLOAD_POOL[model].digest}
</div>
{/if}
</div>
</div>
{/if}
{/each}
{/if}
</div>
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('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={deleteModelTag}
placeholder={$i18n.t('Select a model')}
> >
{#if !deleteModelTag}
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
{/if}
{#each $models.filter((m) => !(m?.preset ?? false) && m.owned_by === 'ollama' && (selectedOllamaUrlIdx === null ? true : (m?.ollama?.urls ?? []).includes(selectedOllamaUrlIdx))) as model}
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
>{model.name +
' (' +
(model.ollama.size / 1024 ** 3).toFixed(1) +
' GB)'}</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={() => {
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>
<div class="pt-1"> {#if showExperimentalOllama}
<div class="flex justify-between items-center text-xs"> <form
<div class=" text-sm font-medium">{$i18n.t('Experimental')}</div> on:submit|preventDefault={() => {
<button uploadModelHandler();
class=" text-xs font-medium text-gray-500" }}
type="button"
on:click={() => {
showExperimentalOllama = !showExperimentalOllama;
}}>{showExperimentalOllama ? $i18n.t('Hide') : $i18n.t('Show')}</button
> >
</div> <div class=" mb-2 flex w-full justify-between">
</div> <div class=" text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div>
{#if showExperimentalOllama} <button
<form class="p-1 px-3 text-xs flex rounded transition"
on:submit|preventDefault={() => { on:click={() => {
uploadModelHandler(); if (modelUploadMode === 'file') {
}} modelUploadMode = 'url';
> } else {
<div class=" mb-2 flex w-full justify-between"> modelUploadMode = 'file';
<div class=" text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div> }
}}
type="button"
>
{#if modelUploadMode === 'file'}
<span class="ml-2 self-center">{$i18n.t('File Mode')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('URL Mode')}</span>
{/if}
</button>
</div>
<button <div class="flex w-full mb-1.5">
class="p-1 px-3 text-xs flex rounded transition" <div class="flex flex-col w-full">
on:click={() => { {#if modelUploadMode === 'file'}
if (modelUploadMode === 'file') { <div
modelUploadMode = 'url'; class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}"
} else { >
modelUploadMode = 'file'; <input
} id="model-upload-input"
}} bind:this={modelUploadInputElement}
type="button" type="file"
> bind:files={modelInputFile}
{#if modelUploadMode === 'file'} on:change={() => {
<span class="ml-2 self-center">{$i18n.t('File Mode')}</span> console.log(modelInputFile);
{:else} }}
<span class="ml-2 self-center">{$i18n.t('URL Mode')}</span> accept=".gguf,.safetensors"
{/if} required
</button> hidden
</div> />
<div class="flex w-full mb-1.5"> <button
<div class="flex flex-col w-full"> type="button"
{#if modelUploadMode === 'file'} class="w-full rounded-lg text-left py-2 px-4 bg-white dark:text-gray-300 dark:bg-gray-850"
<div class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}"> on:click={() => {
<input modelUploadInputElement.click();
id="model-upload-input" }}
bind:this={modelUploadInputElement} >
type="file" {#if modelInputFile && modelInputFile.length > 0}
bind:files={modelInputFile} {modelInputFile[0].name}
on:change={() => { {:else}
console.log(modelInputFile); {$i18n.t('Click here to select')}
}} {/if}
accept=".gguf,.safetensors" </button>
required </div>
hidden {:else}
/> <div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
<input
class="w-full rounded-lg text-left py-2 px-4 bg-white dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
''
? 'mr-2'
: ''}"
type="url"
required
bind:value={modelFileUrl}
placeholder={$i18n.t('Type Hugging Face Resolve (Download) URL')}
/>
</div>
{/if}
</div>
<button {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
type="button" <button
class="w-full rounded-lg text-left py-2 px-4 bg-white dark:text-gray-300 dark:bg-gray-850" 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 disabled:cursor-not-allowed transition"
on:click={() => { type="submit"
modelUploadInputElement.click(); disabled={modelTransferring}
}} >
> {#if modelTransferring}
{#if modelInputFile && modelInputFile.length > 0} <div class="self-center">
{modelInputFile[0].name} <svg
{:else} class=" w-4 h-4"
{$i18n.t('Click here to select')} viewBox="0 0 24 24"
{/if} fill="currentColor"
</button> xmlns="http://www.w3.org/2000/svg"
</div> >
{:else} <style>
<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}"> .spinner_ajPY {
<input transform-origin: center;
class="w-full rounded-lg text-left py-2 px-4 bg-white dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !== animation: spinner_AtaB 0.75s infinite linear;
'' }
? 'mr-2'
: ''}"
type="url"
required
bind:value={modelFileUrl}
placeholder={$i18n.t('Type Hugging Face Resolve (Download) URL')}
/>
</div>
{/if}
</div>
{#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')} @keyframes spinner_AtaB {
<button 100% {
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 disabled:cursor-not-allowed transition" transform: rotate(360deg);
type="submit" }
disabled={modelTransferring} }
> </style>
{#if modelTransferring} <path
<div class="self-center"> 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 <svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
> >
<style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style>
<path <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" 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"
opacity=".25"
/> />
<path <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" 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"
class="spinner_ajPY"
/> />
</svg> </svg>
</div> {/if}
{:else} </button>
<svg {/if}
xmlns="http://www.w3.org/2000/svg" </div>
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 !== '')} {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')}
<div>
<div> <div>
<div class=" my-2.5 text-sm font-medium">{$i18n.t('Modelfile Content')}</div> <div>
<textarea <div class=" my-2.5 text-sm font-medium">{$i18n.t('Modelfile Content')}</div>
bind:value={modelFileContent} <textarea
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-100 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none" bind:value={modelFileContent}
rows="6" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-100 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none"
/> rows="6"
/>
</div>
</div> </div>
{/if}
<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t('To access the GGUF models available for downloading,')}
<a
class=" text-gray-500 dark:text-gray-300 font-medium underline"
href="https://huggingface.co/models?search=gguf"
target="_blank">{$i18n.t('click here.')}</a
>
</div> </div>
{/if}
<div class=" mt-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t('To access the GGUF models available for downloading,')}
<a
class=" text-gray-500 dark:text-gray-300 font-medium underline"
href="https://huggingface.co/models?search=gguf"
target="_blank">{$i18n.t('click here.')}</a
>
</div>
{#if uploadMessage} {#if uploadMessage}
<div class="mt-2"> <div class="mt-2">
<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div> <div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
<div class="w-full rounded-full dark:bg-gray-800"> <div class="w-full rounded-full dark:bg-gray-800">
<div <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" 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: 100%" style="width: 100%"
> >
{uploadMessage} {uploadMessage}
</div>
</div> </div>
</div> <div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;">
<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;"> {modelFileDigest}
{modelFileDigest}
</div>
</div>
{:else if uploadProgress !== null}
<div class="mt-2">
<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
<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> </div>
<div class="mt-1 text-xs dark:text-gray-500" style="font-size: 0.5rem;"> {:else if uploadProgress !== null}
{modelFileDigest} <div class="mt-2">
<div class=" mb-2 text-xs">{$i18n.t('Upload Progress')}</div>
<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> </div>
</div> {/if}
{/if} </form>
</form> {/if}
{/if} </div>
</div> </div>
</div> {:else if ollamaVersion === false}
{:else if ollamaVersion === false} <div>Ollama Not Detected</div>
<div>Ollama Not Detected</div> {:else}
<div class="flex h-full justify-center">
<div class="my-auto">
<Spinner className="size-6" />
</div>
</div>
{/if}
{:else if ollamaEnabled === false}
<div>Ollama API is disabled</div>
{:else} {:else}
<div class="flex h-full justify-center"> <div class="flex h-full justify-center">
<div class="my-auto"> <div class="my-auto">
......
<script lang="ts">
export let className = 'size-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"
/>
</svg>
...@@ -126,6 +126,13 @@ ...@@ -126,6 +126,13 @@
saveAs(blob, `models-export-${Date.now()}.json`); saveAs(blob, `models-export-${Date.now()}.json`);
}; };
const exportModelHandler = async (model) => {
let blob = new Blob([JSON.stringify([model])], {
type: 'application/json'
});
saveAs(blob, `${model.id}-${Date.now()}.json`);
};
const positionChangeHanlder = async () => { const positionChangeHanlder = async () => {
// Get the new order of the models // Get the new order of the models
const modelIds = Array.from(document.getElementById('model-list').children).map((child) => const modelIds = Array.from(document.getElementById('model-list').children).map((child) =>
...@@ -322,6 +329,9 @@ ...@@ -322,6 +329,9 @@
cloneHandler={() => { cloneHandler={() => {
cloneModelHandler(model); cloneModelHandler(model);
}} }}
exportHandler={() => {
exportModelHandler(model);
}}
hideHandler={() => { hideHandler={() => {
hideModelHandler(model); hideModelHandler(model);
}} }}
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
import Share from '$lib/components/icons/Share.svelte'; import Share from '$lib/components/icons/Share.svelte';
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte'; import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte'; import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
...@@ -18,6 +19,8 @@ ...@@ -18,6 +19,8 @@
export let shareHandler: Function; export let shareHandler: Function;
export let cloneHandler: Function; export let cloneHandler: Function;
export let exportHandler: Function;
export let hideHandler: Function; export let hideHandler: Function;
export let deleteHandler: Function; export let deleteHandler: Function;
export let onClose: Function; export let onClose: Function;
...@@ -66,6 +69,17 @@ ...@@ -66,6 +69,17 @@
<div class="flex items-center">{$i18n.t('Clone')}</div> <div class="flex items-center">{$i18n.t('Clone')}</div>
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
exportHandler();
}}
>
<ArrowDownTray />
<div class="flex items-center">{$i18n.t('Export')}</div>
</DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md" class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => { on:click={() => {
......
...@@ -204,6 +204,7 @@ ...@@ -204,6 +204,7 @@
"Enter Your Role": "أدخل الصلاحيات", "Enter Your Role": "أدخل الصلاحيات",
"Error": "", "Error": "",
"Experimental": "تجريبي", "Experimental": "تجريبي",
"Export": "",
"Export All Chats (All Users)": "تصدير جميع الدردشات (جميع المستخدمين)", "Export All Chats (All Users)": "تصدير جميع الدردشات (جميع المستخدمين)",
"Export Chats": "تصدير جميع الدردشات", "Export Chats": "تصدير جميع الدردشات",
"Export Documents Mapping": "تصدير وثائق الخرائط", "Export Documents Mapping": "تصدير وثائق الخرائط",
...@@ -311,12 +312,14 @@ ...@@ -311,12 +312,14 @@
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ملاحظة: إذا قمت بتعيين الحد الأدنى من النقاط، فلن يؤدي البحث إلا إلى إرجاع المستندات التي لها نقاط أكبر من أو تساوي الحد الأدنى من النقاط.", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "ملاحظة: إذا قمت بتعيين الحد الأدنى من النقاط، فلن يؤدي البحث إلا إلى إرجاع المستندات التي لها نقاط أكبر من أو تساوي الحد الأدنى من النقاط.",
"Notifications": "إشعارات", "Notifications": "إشعارات",
"November": "نوفمبر", "November": "نوفمبر",
"num_thread (Ollama)": "",
"October": "اكتوبر", "October": "اكتوبر",
"Off": "أغلاق", "Off": "أغلاق",
"Okay, Let's Go!": "حسنا دعنا نذهب!", "Okay, Let's Go!": "حسنا دعنا نذهب!",
"OLED Dark": "OLED داكن", "OLED Dark": "OLED داكن",
"Ollama": "Ollama", "Ollama": "Ollama",
"Ollama API": "", "Ollama API": "",
"Ollama API disabled": "",
"Ollama Version": "Ollama الاصدار", "Ollama Version": "Ollama الاصدار",
"On": "تشغيل", "On": "تشغيل",
"Only": "فقط", "Only": "فقط",
...@@ -497,6 +500,8 @@ ...@@ -497,6 +500,8 @@
"Use '#' in the prompt input to load and select your documents.": "أستخدم '#' في المحادثة لربطهامن المستندات", "Use '#' in the prompt input to load and select your documents.": "أستخدم '#' في المحادثة لربطهامن المستندات",
"Use Gravatar": "Gravatar أستخدم", "Use Gravatar": "Gravatar أستخدم",
"Use Initials": "Initials أستخدم", "Use Initials": "Initials أستخدم",
"use_mlock (Ollama)": "",
"use_mmap (Ollama)": "",
"user": "مستخدم", "user": "مستخدم",
"User Permissions": "صلاحيات المستخدم", "User Permissions": "صلاحيات المستخدم",
"Users": "المستخدمين", "Users": "المستخدمين",
......
...@@ -204,6 +204,7 @@ ...@@ -204,6 +204,7 @@
"Enter Your Role": "Въведете вашата роля", "Enter Your Role": "Въведете вашата роля",
"Error": "", "Error": "",
"Experimental": "Експериментално", "Experimental": "Експериментално",
"Export": "",
"Export All Chats (All Users)": "Експортване на всички чатове (За всички потребители)", "Export All Chats (All Users)": "Експортване на всички чатове (За всички потребители)",
"Export Chats": "Експортване на чатове", "Export Chats": "Експортване на чатове",
"Export Documents Mapping": "Експортване на документен мапинг", "Export Documents Mapping": "Експортване на документен мапинг",
...@@ -311,12 +312,14 @@ ...@@ -311,12 +312,14 @@
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Забележка: Ако зададете минимален резултат, търсенето ще върне само документи с резултат, по-голям или равен на минималния резултат.", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Забележка: Ако зададете минимален резултат, търсенето ще върне само документи с резултат, по-голям или равен на минималния резултат.",
"Notifications": "Десктоп Известия", "Notifications": "Десктоп Известия",
"November": "Ноември", "November": "Ноември",
"num_thread (Ollama)": "",
"October": "Октомври", "October": "Октомври",
"Off": "Изкл.", "Off": "Изкл.",
"Okay, Let's Go!": "ОК, Нека започваме!", "Okay, Let's Go!": "ОК, Нека започваме!",
"OLED Dark": "OLED тъмно", "OLED Dark": "OLED тъмно",
"Ollama": "Ollama", "Ollama": "Ollama",
"Ollama API": "", "Ollama API": "",
"Ollama API disabled": "",
"Ollama Version": "Ollama Версия", "Ollama Version": "Ollama Версия",
"On": "Вкл.", "On": "Вкл.",
"Only": "Само", "Only": "Само",
...@@ -493,6 +496,8 @@ ...@@ -493,6 +496,8 @@
"Use '#' in the prompt input to load and select your documents.": "Използвайте '#' във промпта за да заредите и изберете вашите документи.", "Use '#' in the prompt input to load and select your documents.": "Използвайте '#' във промпта за да заредите и изберете вашите документи.",
"Use Gravatar": "Използвайте Gravatar", "Use Gravatar": "Използвайте Gravatar",
"Use Initials": "Използвайте Инициали", "Use Initials": "Използвайте Инициали",
"use_mlock (Ollama)": "",
"use_mmap (Ollama)": "",
"user": "потребител", "user": "потребител",
"User Permissions": "Права на потребителя", "User Permissions": "Права на потребителя",
"Users": "Потребители", "Users": "Потребители",
......
...@@ -204,6 +204,7 @@ ...@@ -204,6 +204,7 @@
"Enter Your Role": "আপনার রোল লিখুন", "Enter Your Role": "আপনার রোল লিখুন",
"Error": "", "Error": "",
"Experimental": "পরিক্ষামূলক", "Experimental": "পরিক্ষামূলক",
"Export": "",
"Export All Chats (All Users)": "সব চ্যাট এক্সপোর্ট করুন (সব ইউজারের)", "Export All Chats (All Users)": "সব চ্যাট এক্সপোর্ট করুন (সব ইউজারের)",
"Export Chats": "চ্যাটগুলো এক্সপোর্ট করুন", "Export Chats": "চ্যাটগুলো এক্সপোর্ট করুন",
"Export Documents Mapping": "ডকুমেন্টসমূহ ম্যাপিং এক্সপোর্ট করুন", "Export Documents Mapping": "ডকুমেন্টসমূহ ম্যাপিং এক্সপোর্ট করুন",
...@@ -311,12 +312,14 @@ ...@@ -311,12 +312,14 @@
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "দ্রষ্টব্য: আপনি যদি ন্যূনতম স্কোর সেট করেন তবে অনুসন্ধানটি কেবলমাত্র ন্যূনতম স্কোরের চেয়ে বেশি বা সমান স্কোর সহ নথিগুলি ফেরত দেবে।", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "দ্রষ্টব্য: আপনি যদি ন্যূনতম স্কোর সেট করেন তবে অনুসন্ধানটি কেবলমাত্র ন্যূনতম স্কোরের চেয়ে বেশি বা সমান স্কোর সহ নথিগুলি ফেরত দেবে।",
"Notifications": "নোটিফিকেশনসমূহ", "Notifications": "নোটিফিকেশনসমূহ",
"November": "নভেম্বর", "November": "নভেম্বর",
"num_thread (Ollama)": "",
"October": "অক্টোবর", "October": "অক্টোবর",
"Off": "বন্ধ", "Off": "বন্ধ",
"Okay, Let's Go!": "ঠিক আছে, চলুন যাই!", "Okay, Let's Go!": "ঠিক আছে, চলুন যাই!",
"OLED Dark": "OLED ডার্ক", "OLED Dark": "OLED ডার্ক",
"Ollama": "Ollama", "Ollama": "Ollama",
"Ollama API": "", "Ollama API": "",
"Ollama API disabled": "",
"Ollama Version": "Ollama ভার্সন", "Ollama Version": "Ollama ভার্সন",
"On": "চালু", "On": "চালু",
"Only": "শুধুমাত্র", "Only": "শুধুমাত্র",
...@@ -493,6 +496,8 @@ ...@@ -493,6 +496,8 @@
"Use '#' in the prompt input to load and select your documents.": "আপনার ডকুমেন্টসমূহ নির্বাচন করার জন্য আপনার প্রম্পট ইনপুটে '# ব্যবহার করুন।", "Use '#' in the prompt input to load and select your documents.": "আপনার ডকুমেন্টসমূহ নির্বাচন করার জন্য আপনার প্রম্পট ইনপুটে '# ব্যবহার করুন।",
"Use Gravatar": "Gravatar ব্যবহার করুন", "Use Gravatar": "Gravatar ব্যবহার করুন",
"Use Initials": "নামের আদ্যক্ষর ব্যবহার করুন", "Use Initials": "নামের আদ্যক্ষর ব্যবহার করুন",
"use_mlock (Ollama)": "",
"use_mmap (Ollama)": "",
"user": "ব্যবহারকারী", "user": "ব্যবহারকারী",
"User Permissions": "ইউজার পারমিশনসমূহ", "User Permissions": "ইউজার পারমিশনসমূহ",
"Users": "ব্যাবহারকারীগণ", "Users": "ব্যাবহারকারীগণ",
......
...@@ -204,6 +204,7 @@ ...@@ -204,6 +204,7 @@
"Enter Your Role": "Introdueix el Teu Ròl", "Enter Your Role": "Introdueix el Teu Ròl",
"Error": "", "Error": "",
"Experimental": "Experimental", "Experimental": "Experimental",
"Export": "",
"Export All Chats (All Users)": "Exporta Tots els Xats (Tots els Usuaris)", "Export All Chats (All Users)": "Exporta Tots els Xats (Tots els Usuaris)",
"Export Chats": "Exporta Xats", "Export Chats": "Exporta Xats",
"Export Documents Mapping": "Exporta el Mapatge de Documents", "Export Documents Mapping": "Exporta el Mapatge de Documents",
...@@ -311,12 +312,14 @@ ...@@ -311,12 +312,14 @@
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Si establiscs una puntuació mínima, la cerca només retornarà documents amb una puntuació major o igual a la puntuació mínima.", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Si establiscs una puntuació mínima, la cerca només retornarà documents amb una puntuació major o igual a la puntuació mínima.",
"Notifications": "Notificacions d'Escriptori", "Notifications": "Notificacions d'Escriptori",
"November": "Novembre", "November": "Novembre",
"num_thread (Ollama)": "",
"October": "Octubre", "October": "Octubre",
"Off": "Desactivat", "Off": "Desactivat",
"Okay, Let's Go!": "D'acord, Anem!", "Okay, Let's Go!": "D'acord, Anem!",
"OLED Dark": "OLED Fosc", "OLED Dark": "OLED Fosc",
"Ollama": "Ollama", "Ollama": "Ollama",
"Ollama API": "", "Ollama API": "",
"Ollama API disabled": "",
"Ollama Version": "Versió d'Ollama", "Ollama Version": "Versió d'Ollama",
"On": "Activat", "On": "Activat",
"Only": "Només", "Only": "Només",
...@@ -494,6 +497,8 @@ ...@@ -494,6 +497,8 @@
"Use '#' in the prompt input to load and select your documents.": "Utilitza '#' a l'entrada del prompt per carregar i seleccionar els teus documents.", "Use '#' in the prompt input to load and select your documents.": "Utilitza '#' a l'entrada del prompt per carregar i seleccionar els teus documents.",
"Use Gravatar": "Utilitza Gravatar", "Use Gravatar": "Utilitza Gravatar",
"Use Initials": "Utilitza Inicials", "Use Initials": "Utilitza Inicials",
"use_mlock (Ollama)": "",
"use_mmap (Ollama)": "",
"user": "usuari", "user": "usuari",
"User Permissions": "Permisos d'Usuari", "User Permissions": "Permisos d'Usuari",
"Users": "Usuaris", "Users": "Usuaris",
......
...@@ -204,6 +204,7 @@ ...@@ -204,6 +204,7 @@
"Enter Your Role": "", "Enter Your Role": "",
"Error": "", "Error": "",
"Experimental": "Eksperimento", "Experimental": "Eksperimento",
"Export": "",
"Export All Chats (All Users)": "I-export ang tanan nga mga chat (Tanan nga tiggamit)", "Export All Chats (All Users)": "I-export ang tanan nga mga chat (Tanan nga tiggamit)",
"Export Chats": "I-export ang mga chat", "Export Chats": "I-export ang mga chat",
"Export Documents Mapping": "I-export ang pagmapa sa dokumento", "Export Documents Mapping": "I-export ang pagmapa sa dokumento",
...@@ -311,12 +312,14 @@ ...@@ -311,12 +312,14 @@
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
"Notifications": "Mga pahibalo sa desktop", "Notifications": "Mga pahibalo sa desktop",
"November": "", "November": "",
"num_thread (Ollama)": "",
"October": "", "October": "",
"Off": "Napuo", "Off": "Napuo",
"Okay, Let's Go!": "Okay, lakaw na!", "Okay, Let's Go!": "Okay, lakaw na!",
"OLED Dark": "", "OLED Dark": "",
"Ollama": "", "Ollama": "",
"Ollama API": "", "Ollama API": "",
"Ollama API disabled": "",
"Ollama Version": "Ollama nga bersyon", "Ollama Version": "Ollama nga bersyon",
"On": "Gipaandar", "On": "Gipaandar",
"Only": "Lamang", "Only": "Lamang",
...@@ -493,6 +496,8 @@ ...@@ -493,6 +496,8 @@
"Use '#' in the prompt input to load and select your documents.": "Gamita ang '#' sa dali nga pagsulod aron makarga ug mapili ang imong mga dokumento.", "Use '#' in the prompt input to load and select your documents.": "Gamita ang '#' sa dali nga pagsulod aron makarga ug mapili ang imong mga dokumento.",
"Use Gravatar": "Paggamit sa Gravatar", "Use Gravatar": "Paggamit sa Gravatar",
"Use Initials": "", "Use Initials": "",
"use_mlock (Ollama)": "",
"use_mmap (Ollama)": "",
"user": "tiggamit", "user": "tiggamit",
"User Permissions": "Mga permiso sa tiggamit", "User Permissions": "Mga permiso sa tiggamit",
"Users": "Mga tiggamit", "Users": "Mga tiggamit",
......
...@@ -204,6 +204,7 @@ ...@@ -204,6 +204,7 @@
"Enter Your Role": "Gebe deine Rolle ein", "Enter Your Role": "Gebe deine Rolle ein",
"Error": "", "Error": "",
"Experimental": "Experimentell", "Experimental": "Experimentell",
"Export": "",
"Export All Chats (All Users)": "Alle Chats exportieren (alle Benutzer)", "Export All Chats (All Users)": "Alle Chats exportieren (alle Benutzer)",
"Export Chats": "Chats exportieren", "Export Chats": "Chats exportieren",
"Export Documents Mapping": "Dokumentenmapping exportieren", "Export Documents Mapping": "Dokumentenmapping exportieren",
...@@ -311,12 +312,14 @@ ...@@ -311,12 +312,14 @@
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn du einen Mindestscore festlegst, wird die Suche nur Dokumente zurückgeben, deren Score größer oder gleich dem Mindestscore ist.", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Hinweis: Wenn du einen Mindestscore festlegst, wird die Suche nur Dokumente zurückgeben, deren Score größer oder gleich dem Mindestscore ist.",
"Notifications": "Desktop-Benachrichtigungen", "Notifications": "Desktop-Benachrichtigungen",
"November": "November", "November": "November",
"num_thread (Ollama)": "",
"October": "Oktober", "October": "Oktober",
"Off": "Aus", "Off": "Aus",
"Okay, Let's Go!": "Okay, los geht's!", "Okay, Let's Go!": "Okay, los geht's!",
"OLED Dark": "OLED Dunkel", "OLED Dark": "OLED Dunkel",
"Ollama": "Ollama", "Ollama": "Ollama",
"Ollama API": "", "Ollama API": "",
"Ollama API disabled": "",
"Ollama Version": "Ollama-Version", "Ollama Version": "Ollama-Version",
"On": "Ein", "On": "Ein",
"Only": "Nur", "Only": "Nur",
...@@ -493,6 +496,8 @@ ...@@ -493,6 +496,8 @@
"Use '#' in the prompt input to load and select your documents.": "Verwende '#' in der Prompt-Eingabe, um deine Dokumente zu laden und auszuwählen.", "Use '#' in the prompt input to load and select your documents.": "Verwende '#' in der Prompt-Eingabe, um deine Dokumente zu laden und auszuwählen.",
"Use Gravatar": "Gravatar verwenden", "Use Gravatar": "Gravatar verwenden",
"Use Initials": "Initialen verwenden", "Use Initials": "Initialen verwenden",
"use_mlock (Ollama)": "",
"use_mmap (Ollama)": "",
"user": "Benutzer", "user": "Benutzer",
"User Permissions": "Benutzerberechtigungen", "User Permissions": "Benutzerberechtigungen",
"Users": "Benutzer", "Users": "Benutzer",
......
...@@ -204,6 +204,7 @@ ...@@ -204,6 +204,7 @@
"Enter Your Role": "", "Enter Your Role": "",
"Error": "", "Error": "",
"Experimental": "Much Experiment", "Experimental": "Much Experiment",
"Export": "",
"Export All Chats (All Users)": "Export All Chats (All Doggos)", "Export All Chats (All Users)": "Export All Chats (All Doggos)",
"Export Chats": "Export Barks", "Export Chats": "Export Barks",
"Export Documents Mapping": "Export Mappings of Dogos", "Export Documents Mapping": "Export Mappings of Dogos",
...@@ -311,12 +312,14 @@ ...@@ -311,12 +312,14 @@
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
"Notifications": "Notifications", "Notifications": "Notifications",
"November": "", "November": "",
"num_thread (Ollama)": "",
"October": "", "October": "",
"Off": "Off", "Off": "Off",
"Okay, Let's Go!": "Okay, Let's Go!", "Okay, Let's Go!": "Okay, Let's Go!",
"OLED Dark": "OLED Dark", "OLED Dark": "OLED Dark",
"Ollama": "", "Ollama": "",
"Ollama API": "", "Ollama API": "",
"Ollama API disabled": "",
"Ollama Version": "Ollama Version", "Ollama Version": "Ollama Version",
"On": "On", "On": "On",
"Only": "Only", "Only": "Only",
...@@ -493,6 +496,8 @@ ...@@ -493,6 +496,8 @@
"Use '#' in the prompt input to load and select your documents.": "Use '#' in the prompt input to load and select your documents. Much use.", "Use '#' in the prompt input to load and select your documents.": "Use '#' in the prompt input to load and select your documents. Much use.",
"Use Gravatar": "Use Gravatar much avatar", "Use Gravatar": "Use Gravatar much avatar",
"Use Initials": "Use Initials much initial", "Use Initials": "Use Initials much initial",
"use_mlock (Ollama)": "",
"use_mmap (Ollama)": "",
"user": "user much user", "user": "user much user",
"User Permissions": "User Permissions much permissions", "User Permissions": "User Permissions much permissions",
"Users": "Users much users", "Users": "Users much users",
......
...@@ -204,6 +204,7 @@ ...@@ -204,6 +204,7 @@
"Enter Your Role": "", "Enter Your Role": "",
"Error": "", "Error": "",
"Experimental": "", "Experimental": "",
"Export": "",
"Export All Chats (All Users)": "", "Export All Chats (All Users)": "",
"Export Chats": "", "Export Chats": "",
"Export Documents Mapping": "", "Export Documents Mapping": "",
...@@ -311,12 +312,14 @@ ...@@ -311,12 +312,14 @@
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
"Notifications": "", "Notifications": "",
"November": "", "November": "",
"num_thread (Ollama)": "",
"October": "", "October": "",
"Off": "", "Off": "",
"Okay, Let's Go!": "", "Okay, Let's Go!": "",
"OLED Dark": "", "OLED Dark": "",
"Ollama": "", "Ollama": "",
"Ollama API": "", "Ollama API": "",
"Ollama API disabled": "",
"Ollama Version": "", "Ollama Version": "",
"On": "", "On": "",
"Only": "", "Only": "",
...@@ -493,6 +496,8 @@ ...@@ -493,6 +496,8 @@
"Use '#' in the prompt input to load and select your documents.": "", "Use '#' in the prompt input to load and select your documents.": "",
"Use Gravatar": "", "Use Gravatar": "",
"Use Initials": "", "Use Initials": "",
"use_mlock (Ollama)": "",
"use_mmap (Ollama)": "",
"user": "", "user": "",
"User Permissions": "", "User Permissions": "",
"Users": "", "Users": "",
......
...@@ -204,6 +204,7 @@ ...@@ -204,6 +204,7 @@
"Enter Your Role": "", "Enter Your Role": "",
"Error": "", "Error": "",
"Experimental": "", "Experimental": "",
"Export": "",
"Export All Chats (All Users)": "", "Export All Chats (All Users)": "",
"Export Chats": "", "Export Chats": "",
"Export Documents Mapping": "", "Export Documents Mapping": "",
...@@ -311,12 +312,14 @@ ...@@ -311,12 +312,14 @@
"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "",
"Notifications": "", "Notifications": "",
"November": "", "November": "",
"num_thread (Ollama)": "",
"October": "", "October": "",
"Off": "", "Off": "",
"Okay, Let's Go!": "", "Okay, Let's Go!": "",
"OLED Dark": "", "OLED Dark": "",
"Ollama": "", "Ollama": "",
"Ollama API": "", "Ollama API": "",
"Ollama API disabled": "",
"Ollama Version": "", "Ollama Version": "",
"On": "", "On": "",
"Only": "", "Only": "",
...@@ -493,6 +496,8 @@ ...@@ -493,6 +496,8 @@
"Use '#' in the prompt input to load and select your documents.": "", "Use '#' in the prompt input to load and select your documents.": "",
"Use Gravatar": "", "Use Gravatar": "",
"Use Initials": "", "Use Initials": "",
"use_mlock (Ollama)": "",
"use_mmap (Ollama)": "",
"user": "", "user": "",
"User Permissions": "", "User Permissions": "",
"Users": "", "Users": "",
......
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