Merge branch 'dev' into fix/share-chat-reactive-loop

This commit is contained in:
Jonathan Rohde 2024-05-15 07:59:45 +02:00 committed by GitHub
commit ea6f1a0e57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1447 additions and 230 deletions

View File

@ -37,3 +37,21 @@ jobs:
- name: Build Frontend
run: npm run build
test-frontend:
name: 'Frontend Unit Tests'
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Dependencies
run: npm ci
- name: Run vitest
run: npm run test:frontend

1002
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,8 @@
"format": "prettier --plugin-search-dir --write \"**/*.{js,ts,svelte,css,md,html,json}\"",
"format:backend": "black . --exclude \"/venv/\"",
"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write \"src/lib/i18n/**/*.{js,json}\"",
"cy:open": "cypress open"
"cy:open": "cypress open",
"test:frontend": "vitest"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
@ -41,7 +42,8 @@
"tailwindcss": "^3.3.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.4.2"
"vite": "^4.4.2",
"vitest": "^1.6.0"
},
"type": "module",
"dependencies": {

View File

@ -38,7 +38,7 @@
}
</script>
<div class="flex flex-col mt-0.5 w-full">
<div class="flex flex-col w-full items-center md:items-start">
{#each selectedModels as selectedModel, selectedModelIdx}
<div class="flex w-full max-w-fit">
<div class="overflow-hidden w-full">
@ -109,7 +109,7 @@
</div>
{#if showSetDefault}
<div class="text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500">
<div class="hidden md:absolute text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500">
<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
</div>
{/if}

View File

@ -10,7 +10,7 @@
import { cancelOllamaRequest, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
import { user, MODEL_DOWNLOAD_POOL, models } from '$lib/stores';
import { user, MODEL_DOWNLOAD_POOL, models, mobile } from '$lib/stores';
import { toast } from 'svelte-sonner';
import { capitalizeFirstLetter, getModels, splitStream } from '$lib/utils';
import Tooltip from '$lib/components/common/Tooltip.svelte';
@ -201,10 +201,11 @@
<ChevronDown className=" self-center ml-2 size-3" strokeWidth="2.5" />
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Content
class=" z-40 {className} max-w-[calc(100vw-1rem)] justify-start rounded-lg bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/50 outline-none "
class=" z-40 {className} max-w-[calc(100vw-1rem)] justify-start rounded-xl bg-white dark:bg-gray-850 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/50 outline-none "
transition={flyAndScale}
side={'bottom-start'}
side={$mobile ? 'bottom' : 'bottom-start'}
sideOffset={4}
>
<slot>
@ -228,7 +229,7 @@
{#each filteredItems as item}
<button
aria-label="model-item"
class="flex w-full text-left font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
class="flex w-full text-left font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
on:click={() => {
value = item.value;
@ -312,7 +313,7 @@
{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
<button
class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
on:click={() => {
pullModelHandler();
}}

View File

@ -2,7 +2,16 @@
import { getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { WEBUI_NAME, chatId, modelfiles, settings, showSettings } from '$lib/stores';
import {
WEBUI_NAME,
chatId,
mobile,
modelfiles,
settings,
showSettings,
showSidebar,
user
} from '$lib/stores';
import { slide } from 'svelte/transition';
import ShareChatModal from '../chat/ShareChatModal.svelte';
@ -10,6 +19,7 @@
import Tooltip from '../common/Tooltip.svelte';
import Menu from './Navbar/Menu.svelte';
import { page } from '$app/stores';
import UserMenu from './Sidebar/UserMenu.svelte';
const i18n = getContext('i18n');
@ -28,8 +38,34 @@
<ShareChatModal bind:show={showShareChatModal} chatId={$chatId} />
<nav id="nav" class=" sticky py-2.5 top-0 flex flex-row justify-center z-30">
<div class=" flex max-w-full w-full mx-auto px-5 pt-0.5 md:px-[1.3rem]">
<div class=" flex max-w-full w-full mx-auto px-5 pt-0.5 md:px-[1rem]">
<div class="flex items-center w-full max-w-full">
<div class="{$showSidebar ? 'md:hidden' : ''} mr-3 self-start flex flex-none items-center">
<button
id="sidebar-toggle-button"
class="cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
on:click={() => {
showSidebar.set(!$showSidebar);
}}
>
<div class=" m-auto self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="size-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
/>
</svg>
</div>
</button>
</div>
<div class="flex-1 overflow-hidden max-w-full">
{#if showModelSelector}
<ModelSelector bind:selectedModels showSetDefault={!shareEnabled} />
@ -37,12 +73,12 @@
</div>
<div class="self-start flex flex-none items-center">
<div class="md:hidden flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" />
<!-- <div class="md:hidden flex self-center w-[1px] h-5 mx-2 bg-gray-300 dark:bg-stone-700" /> -->
{#if !shareEnabled}
<Tooltip content={$i18n.t('Settings')}>
<button
class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
class="hidden md:flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
id="open-settings-button"
on:click={async () => {
await showSettings.set(!$showSettings);
@ -81,9 +117,9 @@
}}
>
<button
class="cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
class="hidden md:flex cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
id="chat-context-menu-button"
>
>
<div class=" m-auto self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -106,7 +142,9 @@
<Tooltip content={$i18n.t('New Chat')}>
<button
id="new-chat-button"
class=" cursor-pointer p-1.5 flex dark:hover:bg-gray-700 rounded-full transition"
class=" flex {$showSidebar
? 'md:hidden'
: ''} cursor-pointer px-2 py-2 rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
on:click={() => {
initNewChat();
}}
@ -128,6 +166,29 @@
</div>
</button>
</Tooltip>
{#if !$mobile && $user !== undefined}
<UserMenu
role={$user.role}
on:show={(e) => {
if (e.detail === 'archived-chat') {
// showArchivedChatsModal = true;
}
}}
>
<button
class=" flex rounded-xl p-1.5 w-full hover:bg-gray-100 dark:hover:bg-gray-850 transition"
>
<div class=" self-center">
<img
src={$user.profile_image_url}
class=" size-6 object-cover rounded-full"
alt="User profile"
/>
</div>
</button>
</UserMenu>
{/if}
</div>
</div>
</div>

View File

@ -76,14 +76,14 @@
<div slot="content">
<DropdownMenu.Content
class="w-full max-w-[200px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow-lg"
class="w-full max-w-[200px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
sideOffset={8}
side="bottom"
align="end"
transition={flyAndScale}
>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-850 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-800 rounded-md"
on:click={async () => {
await showSettings.set(!$showSettings);
}}
@ -112,7 +112,7 @@
{#if shareEnabled}
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-850 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-800 rounded-md"
id="chat-share-button"
on:click={() => {
shareHandler();
@ -141,7 +141,7 @@
/> -->
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-850 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-800 rounded-md"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -161,12 +161,12 @@
<div class="flex items-center">{$i18n.t('Download')}</div>
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent
class="w-full rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow-lg"
class="w-full rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
transition={flyAndScale}
sideOffset={8}
>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-850 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-800 rounded-md"
on:click={() => {
downloadTxt();
}}
@ -175,7 +175,7 @@
</DropdownMenu.Item>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-850 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer dark:hover:bg-gray-800 rounded-md"
on:click={() => {
downloadPdf();
}}

View File

@ -1,6 +1,15 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { user, chats, settings, showSettings, chatId, tags, showSidebar } from '$lib/stores';
import {
user,
chats,
settings,
showSettings,
chatId,
tags,
showSidebar,
mobile
} from '$lib/stores';
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
@ -183,6 +192,17 @@
}}
/>
<!-- svelte-ignore a11y-no-static-element-interactions -->
{#if $showSidebar}
<div
class=" fixed md:hidden z-40 top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center overflow-hidden overscroll-contain"
on:mousedown={() => {
showSidebar.set(!$showSidebar);
}}
/>
{/if}
<div
bind:this={navElement}
id="sidebar"
@ -193,14 +213,37 @@
data-state={$showSidebar}
>
<div
class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] {$showSidebar
class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] z-50 {$showSidebar
? ''
: 'invisible'}"
>
<div class="px-2 flex justify-center space-x-2">
<div class="px-2 flex justify-between space-x-2">
<button
class=" cursor-pointer px-2 py-2 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
on:click={() => {
showSidebar.set(!$showSidebar);
}}
>
<div class=" m-auto self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="size-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
/>
</svg>
</div>
</button>
<a
id="sidebar-new-chat-button"
class="flex-grow flex justify-between rounded-xl px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
class="flex justify-between rounded-xl px-2 py-2 hover:bg-gray-100 dark:hover:bg-gray-850 transition"
href="/"
on:click={async () => {
selectedChatId = null;
@ -212,24 +255,12 @@
}, 0);
}}
>
<div class="flex self-center">
<div class="self-center mr-1.5">
<img
src="{WEBUI_BASE_URL}/static/favicon.png"
class=" size-6 -translate-x-1.5 rounded-full"
alt="logo"
/>
</div>
<div class=" self-center font-medium text-sm">{$i18n.t('New Chat')}</div>
</div>
<div class="self-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
class="size-5"
>
<path
d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z"
@ -681,43 +712,45 @@
</div>
</div>
<div class="px-2.5">
<!-- <hr class=" border-gray-900 mb-1 w-full" /> -->
{#if $mobile}
<div class="px-2.5">
<!-- <hr class=" border-gray-900 mb-1 w-full" /> -->
<div class="flex flex-col">
{#if $user !== undefined}
<UserMenu
role={$user.role}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChatsModal = true;
}
}}
>
<button
class=" flex rounded-xl py-3 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
on:click={() => {
showDropdown = !showDropdown;
<div class="flex flex-col">
{#if $user !== undefined}
<UserMenu
role={$user.role}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChatsModal = true;
}
}}
>
<div class=" self-center mr-3">
<img
src={$user.profile_image_url}
class=" max-w-[30px] object-cover rounded-full"
alt="User profile"
/>
</div>
<div class=" self-center font-semibold">{$user.name}</div>
</button>
</UserMenu>
{/if}
<button
class=" flex rounded-xl py-3 px-3.5 w-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
on:click={() => {
showDropdown = !showDropdown;
}}
>
<div class=" self-center mr-3">
<img
src={$user.profile_image_url}
class=" max-w-[30px] object-cover rounded-full"
alt="User profile"
/>
</div>
<div class=" self-center font-semibold">{$user.name}</div>
</button>
</UserMenu>
{/if}
</div>
</div>
</div>
{/if}
</div>
<div
id="sidebar-handle"
class="fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
class=" hidden md:fixed left-0 top-[50dvh] -translate-y-1/2 transition-transform translate-x-[255px] md:translate-x-[260px] rotate-0"
>
<Tooltip
placement="right"

View File

@ -36,14 +36,14 @@
<div slot="content">
<DropdownMenu.Content
class="w-full max-w-[180px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-900 dark:text-white shadow"
class="w-full max-w-[180px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
sideOffset={-2}
side="bottom"
align="start"
transition={flyAndScale}
>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-800 rounded-md"
on:click={() => {
shareHandler();
}}
@ -53,7 +53,7 @@
</DropdownMenu.Item>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-800 rounded-md"
on:click={() => {
renameHandler();
}}
@ -63,7 +63,7 @@
</DropdownMenu.Item>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-850 rounded-md"
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer dark:hover:bg-gray-800 rounded-md"
on:click={() => {
deleteHandler();
}}

View File

@ -28,7 +28,7 @@
<slot name="content">
<DropdownMenu.Content
class="w-full max-w-[240px] rounded-lg p-1 py-1 border border-gray-850 z-50 bg-gray-900 text-white text-sm"
class="w-full max-w-[240px] rounded-lg p-1 py-1 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-gray-850 text-white text-sm"
sideOffset={8}
side="bottom"
align="start"

View File

@ -1,8 +1,8 @@
import { dev } from '$app/environment';
import { browser, dev } from '$app/environment';
// import { version } from '../../package.json';
export const APP_NAME = 'Open WebUI';
export const WEBUI_BASE_URL = dev ? `http://${location.hostname}:8080` : ``;
export const WEBUI_BASE_URL = browser ? (dev ? `http://${location.hostname}:8080` : ``) : ``;
export const WEBUI_API_BASE_URL = `${WEBUI_BASE_URL}/api/v1`;

View File

@ -1,34 +1,34 @@
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' o '-1' per nessuna scadenza.",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "",
"(e.g. `sh webui.sh --api`)": "(p.e. `sh webui.sh --api`)",
"(latest)": "(ultima)",
"{{modelName}} is thinking...": "{{modelName}} sta pensando...",
"{{user}}'s Chats": "",
"{{user}}'s Chats": "{{user}} Chat",
"{{webUIName}} Backend Required": "{{webUIName}} Backend richiesto",
"a user": "un utente",
"About": "Informazioni",
"Account": "Account",
"Accurate information": "",
"Accurate information": "Informazioni accurate",
"Add a model": "Aggiungi un modello",
"Add a model tag name": "Aggiungi un nome tag del modello",
"Add a short description about what this modelfile does": "Aggiungi una breve descrizione di ciò che fa questo file modello",
"Add a short title for this prompt": "Aggiungi un titolo breve per questo prompt",
"Add a tag": "Aggiungi un tag",
"Add custom prompt": "",
"Add custom prompt": "Aggiungi un prompt custom",
"Add Docs": "Aggiungi documenti",
"Add Files": "Aggiungi file",
"Add message": "Aggiungi messaggio",
"Add Model": "",
"Add Tags": "aggiungi tag",
"Add User": "",
"Add Model": "Aggiungi modello",
"Add Tags": "Aggiungi tag",
"Add User": "Aggiungi utente",
"Adjusting these settings will apply changes universally to all users.": "La modifica di queste impostazioni applicherà le modifiche universalmente a tutti gli utenti.",
"admin": "amministratore",
"Admin Panel": "Pannello di amministrazione",
"Admin Settings": "Impostazioni amministratore",
"Advanced Parameters": "Parametri avanzati",
"all": "tutti",
"All Documents": "",
"All Documents": "Tutti i documenti",
"All Users": "Tutti gli utenti",
"Allow": "Consenti",
"Allow Chat Deletion": "Consenti l'eliminazione della chat",
@ -36,32 +36,32 @@
"Already have an account?": "Hai già un account?",
"an assistant": "un assistente",
"and": "e",
"and create a new shared link.": "",
"and create a new shared link.": "e crea un nuovo link condiviso.",
"API Base URL": "URL base API",
"API Key": "Chiave API",
"API Key created.": "",
"API keys": "",
"API Key created.": "Chiave API creata.",
"API keys": "Chiavi API",
"API RPM": "API RPM",
"April": "",
"Archive": "",
"April": "Aprile",
"Archive": "Archivio",
"Archived Chats": "Chat archiviate",
"are allowed - Activate this command by typing": "sono consentiti - Attiva questo comando digitando",
"Are you sure?": "Sei sicuro?",
"Attach file": "",
"Attention to detail": "",
"Attach file": "Allega file",
"Attention to detail": "Attenzione ai dettagli",
"Audio": "Audio",
"August": "",
"August": "Agosto",
"Auto-playback response": "Riproduzione automatica della risposta",
"Auto-send input after 3 sec.": "Invio automatico dell'input dopo 3 secondi.",
"AUTOMATIC1111 Base URL": "URL base AUTOMATIC1111",
"AUTOMATIC1111 Base URL is required.": "L'URL base AUTOMATIC1111 è obbligatorio.",
"available!": "disponibile!",
"Back": "Indietro",
"Bad Response": "",
"before": "",
"Being lazy": "",
"Bad Response": "Risposta non valida",
"before": "prima",
"Being lazy": "Essere pigri",
"Builder Mode": "Modalità costruttore",
"Bypass SSL verification for Websites": "",
"Bypass SSL verification for Websites": "Aggira la verifica SSL per i siti web",
"Cancel": "Annulla",
"Categories": "Categorie",
"Change Password": "Cambia password",
@ -78,66 +78,66 @@
"Chunk Size": "Dimensione chunk",
"Citation": "Citazione",
"Click here for help.": "Clicca qui per aiuto.",
"Click here to": "",
"Click here to": "Clicca qui per",
"Click here to check other modelfiles.": "Clicca qui per controllare altri file modello.",
"Click here to select": "Clicca qui per selezionare",
"Click here to select a csv file.": "",
"Click here to select a csv file.": "Clicca qui per selezionare un file csv.",
"Click here to select documents.": "Clicca qui per selezionare i documenti.",
"click here.": "clicca qui.",
"Click on the user role button to change a user's role.": "Clicca sul pulsante del ruolo utente per modificare il ruolo di un utente.",
"Close": "Chiudi",
"Collection": "Collezione",
"ComfyUI": "",
"ComfyUI Base URL": "",
"ComfyUI Base URL is required.": "",
"ComfyUI": "ComfyUI",
"ComfyUI Base URL": "URL base ComfyUI",
"ComfyUI Base URL is required.": "L'URL base ComfyUI è obbligatorio.",
"Command": "Comando",
"Confirm Password": "Conferma password",
"Connections": "Connessioni",
"Content": "Contenuto",
"Context Length": "Lunghezza contesto",
"Continue Response": "",
"Continue Response": "Continua risposta",
"Conversation Mode": "Modalità conversazione",
"Copied shared chat URL to clipboard!": "",
"Copy": "",
"Copied shared chat URL to clipboard!": "URL della chat condivisa copiato negli appunti!",
"Copy": "Copia",
"Copy last code block": "Copia ultimo blocco di codice",
"Copy last response": "Copia ultima risposta",
"Copy Link": "",
"Copy Link": "Copia link",
"Copying to clipboard was successful!": "Copia negli appunti riuscita!",
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Crea una frase concisa di 3-5 parole come intestazione per la seguente query, aderendo rigorosamente al limite di 3-5 parole ed evitando l'uso della parola 'titolo':",
"Create a modelfile": "Crea un file modello",
"Create Account": "Crea account",
"Create new key": "",
"Create new secret key": "",
"Create new key": "Crea nuova chiave",
"Create new secret key": "Crea nuova chiave segreta",
"Created at": "Creato il",
"Created At": "",
"Created At": "Creato il",
"Current Model": "Modello corrente",
"Current Password": "Password corrente",
"Custom": "Personalizzato",
"Customize Ollama models for a specific purpose": "Personalizza i modelli Ollama per uno scopo specifico",
"Dark": "Scuro",
"Dashboard": "",
"Dashboard": "Pannello di controllo",
"Database": "Database",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"December": "",
"December": "Dicembre",
"Default": "Predefinito",
"Default (Automatic1111)": "Predefinito (Automatic1111)",
"Default (SentenceTransformers)": "",
"Default (SentenceTransformers)": "Predefinito (SentenceTransformers)",
"Default (Web API)": "Predefinito (API Web)",
"Default model updated": "Modello predefinito aggiornato",
"Default Prompt Suggestions": "Suggerimenti prompt predefiniti",
"Default User Role": "Ruolo utente predefinito",
"delete": "elimina",
"Delete": "",
"Delete": "Elimina",
"Delete a model": "Elimina un modello",
"Delete chat": "Elimina chat",
"Delete Chat": "",
"Delete Chats": "Elimina chat",
"delete this link": "",
"Delete User": "",
"Delete Chat": "Elimina chat",
"Delete Chats": "Elimina le chat",
"delete this link": "elimina questo link",
"Delete User": "Elimina utente",
"Deleted {{deleteModelTag}}": "Eliminato {{deleteModelTag}}",
"Deleted {{tagName}}": "",
"Deleted {{tagName}}": "Eliminato {{tagName}}",
"Description": "Descrizione",
"Didn't fully follow instructions": "",
"Didn't fully follow instructions": "Non ha seguito completamente le istruzioni",
"Disabled": "Disabilitato",
"Discover a modelfile": "Scopri un file modello",
"Discover a prompt": "Scopri un prompt",
@ -150,28 +150,28 @@
"does not make any external connections, and your data stays securely on your locally hosted server.": "non effettua connessioni esterne e i tuoi dati rimangono al sicuro sul tuo server ospitato localmente.",
"Don't Allow": "Non consentire",
"Don't have an account?": "Non hai un account?",
"Don't like the style": "",
"Download": "",
"Download canceled": "",
"Don't like the style": "Non ti piace lo stile",
"Download": "Scarica",
"Download canceled": "Scaricamento annullato",
"Download Database": "Scarica database",
"Drop any files here to add to the conversation": "Trascina qui i file da aggiungere alla conversazione",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "ad esempio '30s','10m'. Le unità di tempo valide sono 's', 'm', 'h'.",
"Edit": "",
"Edit": "Modifica",
"Edit Doc": "Modifica documento",
"Edit User": "Modifica utente",
"Email": "Email",
"Embedding Model": "",
"Embedding Model Engine": "",
"Embedding model set to \"{{embedding_model}}\"": "",
"Embedding Model": "Modello di embedding",
"Embedding Model Engine": "Motore del modello di embedding",
"Embedding model set to \"{{embedding_model}}\"": "Modello di embedding impostato su \"{{embedding_model}}\"",
"Enable Chat History": "Abilita cronologia chat",
"Enable New Sign Ups": "Abilita nuove iscrizioni",
"Enabled": "Abilitato",
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "",
"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assicurati che il tuo file CSV includa 4 colonne in questo ordine: Nome, Email, Password, Ruolo.",
"Enter {{role}} message here": "Inserisci il messaggio per {{role}} qui",
"Enter Chunk Overlap": "Inserisci la sovrapposizione chunk",
"Enter Chunk Size": "Inserisci la dimensione chunk",
"Enter Image Size (e.g. 512x512)": "Inserisci la dimensione dell'immagine (ad esempio 512x512)",
"Enter language codes": "",
"Enter language codes": "Inserisci i codici lingua",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "Inserisci l'URL base dell'API LiteLLM (litellm_params.api_base)",
"Enter LiteLLM API Key (litellm_params.api_key)": "Inserisci la chiave API LiteLLM (litellm_params.api_key)",
"Enter LiteLLM API RPM (litellm_params.rpm)": "Inserisci LiteLLM API RPM (litellm_params.rpm)",
@ -179,45 +179,45 @@
"Enter Max Tokens (litellm_params.max_tokens)": "Inserisci Max Tokens (litellm_params.max_tokens)",
"Enter model tag (e.g. {{modelTag}})": "Inserisci il tag del modello (ad esempio {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "Inserisci il numero di passaggi (ad esempio 50)",
"Enter Score": "",
"Enter Score": "Inserisci il punteggio",
"Enter stop sequence": "Inserisci la sequenza di arresto",
"Enter Top K": "Inserisci Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Inserisci URL (ad esempio http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "",
"Enter URL (e.g. http://localhost:11434)": "Inserisci URL (ad esempio http://localhost:11434)",
"Enter Your Email": "Inserisci la tua email",
"Enter Your Full Name": "Inserisci il tuo nome completo",
"Enter Your Password": "Inserisci la tua password",
"Enter Your Role": "",
"Enter Your Role": "Inserisci il tuo ruolo",
"Experimental": "Sperimentale",
"Export All Chats (All Users)": "Esporta tutte le chat (tutti gli utenti)",
"Export Chats": "Esporta chat",
"Export Documents Mapping": "Esporta mappatura documenti",
"Export Modelfiles": "Esporta file modello",
"Export Prompts": "Esporta prompt",
"Failed to create API Key.": "",
"Failed to create API Key.": "Impossibile creare la chiave API.",
"Failed to read clipboard contents": "Impossibile leggere il contenuto degli appunti",
"February": "",
"Feel free to add specific details": "",
"February": "Febbraio",
"Feel free to add specific details": "Sentiti libero/a di aggiungere dettagli specifici",
"File Mode": "Modalità file",
"File not found.": "File non trovato.",
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "Rilevato spoofing delle impronte digitali: impossibile utilizzare le iniziali come avatar. Ripristino all'immagine del profilo predefinita.",
"Fluidly stream large external response chunks": "Trasmetti in modo fluido blocchi di risposta esterni di grandi dimensioni",
"Focus chat input": "Metti a fuoco l'input della chat",
"Followed instructions perfectly": "",
"Followed instructions perfectly": "Ha seguito le istruzioni alla perfezione",
"Format your variables using square brackets like this:": "Formatta le tue variabili usando parentesi quadre come questa:",
"From (Base Model)": "Da (modello base)",
"Full Screen Mode": "Modalità a schermo intero",
"General": "Generale",
"General Settings": "Impostazioni generali",
"Generation Info": "",
"Good Response": "",
"has no conversations.": "",
"Generation Info": "Informazioni generazione",
"Good Response": "Buona risposta",
"has no conversations.": "non ha conversazioni.",
"Hello, {{name}}": "Ciao, {{name}}",
"Help": "",
"Help": "Aiuto",
"Hide": "Nascondi",
"Hide Additional Params": "Nascondi parametri aggiuntivi",
"How can I help you today?": "Come posso aiutarti oggi?",
"Hybrid Search": "",
"Hybrid Search": "Ricerca ibrida",
"Image Generation (Experimental)": "Generazione di immagini (sperimentale)",
"Image Generation Engine": "Motore di generazione immagini",
"Image Settings": "Impostazioni immagine",
@ -226,21 +226,21 @@
"Import Documents Mapping": "Importa mappatura documenti",
"Import Modelfiles": "Importa file modello",
"Import Prompts": "Importa prompt",
"Include `--api` flag when running stable-diffusion-webui": "",
"Input commands": "",
"Include `--api` flag when running stable-diffusion-webui": "Includi il flag `--api` quando esegui stable-diffusion-webui",
"Input commands": "Comandi di input",
"Interface": "Interfaccia",
"Invalid Tag": "",
"January": "",
"Invalid Tag": "Tag non valido",
"January": "Gennaio",
"join our Discord for help.": "unisciti al nostro Discord per ricevere aiuto.",
"JSON": "JSON",
"July": "",
"June": "",
"July": "Luglio",
"June": "Giugno",
"JWT Expiration": "Scadenza JWT",
"JWT Token": "Token JWT",
"Keep Alive": "Mantieni attivo",
"Keyboard shortcuts": "Scorciatoie da tastiera",
"Language": "Lingua",
"Last Active": "",
"Last Active": "Ultima attività",
"Light": "Chiaro",
"Listening...": "Ascolto...",
"LLMs can make mistakes. Verify important information.": "Gli LLM possono commettere errori. Verifica le informazioni importanti.",
@ -249,22 +249,22 @@
"Manage LiteLLM Models": "Gestisci modelli LiteLLM",
"Manage Models": "Gestisci modelli",
"Manage Ollama Models": "Gestisci modelli Ollama",
"March": "",
"March": "Marzo",
"Max Tokens": "Max token",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "È possibile scaricare un massimo di 3 modelli contemporaneamente. Riprova più tardi.",
"May": "",
"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "",
"Minimum Score": "",
"May": "Maggio",
"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "I messaggi che invii dopo aver creato il tuo link non verranno condivisi. Gli utenti con l'URL potranno visualizzare la chat condivisa.",
"Minimum Score": "Punteggio minimo",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"MMMM DD, YYYY HH:mm": "",
"MMMM DD, YYYY HH:mm": "MMMM DD, YYYY HH:mm",
"Model '{{modelName}}' has been successfully downloaded.": "Il modello '{{modelName}}' è stato scaricato con successo.",
"Model '{{modelTag}}' is already in queue for downloading.": "Il modello '{{modelTag}}' è già in coda per il download.",
"Model {{modelId}} not found": "Modello {{modelId}} non trovato",
"Model {{modelName}} already exists.": "Il modello {{modelName}} esiste già.",
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "",
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Percorso del filesystem del modello rilevato. Il nome breve del modello è richiesto per l'aggiornamento, impossibile continuare.",
"Model Name": "Nome modello",
"Model not selected": "Modello non selezionato",
"Model Tag Name": "Nome tag del modello",
@ -275,7 +275,7 @@
"Modelfile Content": "Contenuto del file modello",
"Modelfiles": "File modello",
"Models": "Modelli",
"More": "",
"More": "Altro",
"My Documents": "I miei documenti",
"My Modelfiles": "I miei file modello",
"My Prompts": "I miei prompt",
@ -284,19 +284,19 @@
"Name your modelfile": "Assegna un nome al tuo file modello",
"New Chat": "Nuova chat",
"New Password": "Nuova password",
"No results found": "",
"No results found": "Nessun risultato trovato",
"No source available": "Nessuna fonte disponibile",
"Not factually correct": "",
"Not factually correct": "Non corretto dal punto di vista fattuale",
"Not sure what to add?": "Non sei sicuro di cosa aggiungere?",
"Not sure what to write? Switch to": "Non sei sicuro di cosa scrivere? Passa a",
"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.": "Nota: se imposti un punteggio minimo, la ricerca restituirà solo i documenti con un punteggio maggiore o uguale al punteggio minimo.",
"Notifications": "Notifiche desktop",
"November": "",
"October": "",
"November": "Novembre",
"October": "Ottobre",
"Off": "Disattivato",
"Okay, Let's Go!": "Ok, andiamo!",
"OLED Dark": "",
"Ollama": "",
"OLED Dark": "OLED scuro",
"Ollama": "Ollama",
"Ollama Base URL": "URL base Ollama",
"Ollama Version": "Versione Ollama",
"On": "Attivato",
@ -309,52 +309,52 @@
"Open AI": "Open AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "Apri nuova chat",
"OpenAI": "",
"OpenAI": "OpenAI",
"OpenAI API": "API OpenAI",
"OpenAI API Config": "",
"OpenAI API Config": "Configurazione API OpenAI",
"OpenAI API Key is required.": "La chiave API OpenAI è obbligatoria.",
"OpenAI URL/Key required.": "",
"OpenAI URL/Key required.": "URL/Chiave OpenAI obbligatori.",
"or": "o",
"Other": "",
"Overview": "",
"Other": "Altro",
"Overview": "Panoramica",
"Parameters": "Parametri",
"Password": "Password",
"PDF document (.pdf)": "",
"PDF document (.pdf)": "Documento PDF (.pdf)",
"PDF Extract Images (OCR)": "Estrazione immagini PDF (OCR)",
"pending": "in sospeso",
"Permission denied when accessing microphone: {{error}}": "Autorizzazione negata durante l'accesso al microfono: {{error}}",
"Plain text (.txt)": "",
"Plain text (.txt)": "Testo normale (.txt)",
"Playground": "Terreno di gioco",
"Positive attitude": "",
"Previous 30 days": "",
"Previous 7 days": "",
"Profile Image": "",
"Prompt": "",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "",
"Positive attitude": "Attitudine positiva",
"Previous 30 days": "Ultimi 30 giorni",
"Previous 7 days": "Ultimi 7 giorni",
"Profile Image": "Immagine del profilo",
"Prompt": "Prompt",
"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Prompt (ad esempio Dimmi un fatto divertente sull'Impero Romano)",
"Prompt Content": "Contenuto del prompt",
"Prompt suggestions": "Suggerimenti prompt",
"Prompts": "Prompt",
"Pull \"{{searchValue}}\" from Ollama.com": "",
"Pull \"{{searchValue}}\" from Ollama.com": "Estrai \"{{searchValue}}\" da Ollama.com",
"Pull a model from Ollama.com": "Estrai un modello da Ollama.com",
"Pull Progress": "Avanzamento estrazione",
"Query Params": "Parametri query",
"RAG Template": "Modello RAG",
"Raw Format": "Formato raw",
"Read Aloud": "",
"Read Aloud": "Leggi ad alta voce",
"Record voice": "Registra voce",
"Redirecting you to OpenWebUI Community": "Reindirizzamento alla comunità OpenWebUI",
"Refused when it shouldn't have": "",
"Regenerate": "",
"Refused when it shouldn't have": "Rifiutato quando non avrebbe dovuto",
"Regenerate": "Rigenera",
"Release Notes": "Note di rilascio",
"Remove": "",
"Remove Model": "",
"Rename": "",
"Remove": "Rimuovi",
"Remove Model": "Rimuovi modello",
"Rename": "Rinomina",
"Repeat Last N": "Ripeti ultimi N",
"Repeat Penalty": "Penalità di ripetizione",
"Request Mode": "Modalità richiesta",
"Reranking Model": "",
"Reranking model disabled": "",
"Reranking model set to \"{{reranking_model}}\"": "",
"Reranking Model": "Modello di riclassificazione",
"Reranking model disabled": "Modello di riclassificazione disabilitato",
"Reranking model set to \"{{reranking_model}}\"": "Modello di riclassificazione impostato su \"{{reranking_model}}\"",
"Reset Vector Storage": "Reimposta archivio vettoriale",
"Response AutoCopy to Clipboard": "Copia automatica della risposta negli appunti",
"Role": "Ruolo",
@ -369,7 +369,7 @@
"Scan complete!": "Scansione completata!",
"Scan for documents from {{path}}": "Cerca documenti da {{path}}",
"Search": "Cerca",
"Search a model": "",
"Search a model": "Cerca un modello",
"Search Documents": "Cerca documenti",
"Search Prompts": "Cerca prompt",
"See readme.md for instructions": "Vedi readme.md per le istruzioni",
@ -378,35 +378,35 @@
"Select a mode": "Seleziona una modalità",
"Select a model": "Seleziona un modello",
"Select an Ollama instance": "Seleziona un'istanza Ollama",
"Select model": "",
"Select model": "Seleziona modello",
"Send a Message": "Invia un messaggio",
"Send message": "Invia messaggio",
"September": "",
"September": "Settembre",
"Server connection verified": "Connessione al server verificata",
"Set as default": "Imposta come predefinito",
"Set Default Model": "Imposta modello predefinito",
"Set embedding model (e.g. {{model}})": "",
"Set embedding model (e.g. {{model}})": "Imposta modello di embedding (ad esempio {{model}})",
"Set Image Size": "Imposta dimensione immagine",
"Set Model": "Imposta modello",
"Set reranking model (e.g. {{model}})": "",
"Set reranking model (e.g. {{model}})": "Imposta modello di riclassificazione (ad esempio {{model}})",
"Set Steps": "Imposta passaggi",
"Set Title Auto-Generation Model": "Imposta modello di generazione automatica del titolo",
"Set Voice": "Imposta voce",
"Settings": "Impostazioni",
"Settings saved successfully!": "Impostazioni salvate con successo!",
"Share": "",
"Share Chat": "",
"Share": "Condividi",
"Share Chat": "Condividi chat",
"Share to OpenWebUI Community": "Condividi con la comunità OpenWebUI",
"short-summary": "riassunto-breve",
"Show": "Mostra",
"Show Additional Params": "Mostra parametri aggiuntivi",
"Show shortcuts": "Mostra",
"Showcased creativity": "",
"Showcased creativity": "Creatività messa in mostra",
"sidebar": "barra laterale",
"Sign in": "Accedi",
"Sign Out": "Esci",
"Sign up": "Registrati",
"Signing in": "",
"Signing in": "Accesso in corso",
"Source": "Fonte",
"Speech recognition error: {{error}}": "Errore di riconoscimento vocale: {{error}}",
"Speech-to-Text Engine": "Motore da voce a testo",
@ -414,37 +414,37 @@
"Stop Sequence": "Sequenza di arresto",
"STT Settings": "Impostazioni STT",
"Submit": "Invia",
"Subtitle (e.g. about the Roman Empire)": "",
"Subtitle (e.g. about the Roman Empire)": "Sottotitolo (ad esempio sull'Impero Romano)",
"Success": "Successo",
"Successfully updated.": "Aggiornato con successo.",
"Suggested": "",
"Suggested": "Suggerito",
"Sync All": "Sincronizza tutto",
"System": "Sistema",
"System Prompt": "Prompt di sistema",
"Tags": "Tag",
"Tell us more:": "",
"Tell us more:": "Raccontaci di più:",
"Temperature": "Temperatura",
"Template": "Modello",
"Text Completion": "Completamento del testo",
"Text-to-Speech Engine": "Motore da testo a voce",
"Tfs Z": "Tfs Z",
"Thanks for your feedback!": "",
"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
"Thanks for your feedback!": "Grazie per il tuo feedback!",
"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Il punteggio dovrebbe essere un valore compreso tra 0.0 (0%) e 1.0 (100%).",
"Theme": "Tema",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ciò garantisce che le tue preziose conversazioni siano salvate in modo sicuro nel tuo database backend. Grazie!",
"This setting does not sync across browsers or devices.": "Questa impostazione non si sincronizza tra browser o dispositivi.",
"Thorough explanation": "",
"Thorough explanation": "Spiegazione dettagliata",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Suggerimento: aggiorna più slot di variabili consecutivamente premendo il tasto tab nell'input della chat dopo ogni sostituzione.",
"Title": "Titolo",
"Title (e.g. Tell me a fun fact)": "",
"Title (e.g. Tell me a fun fact)": "Titolo (ad esempio Dimmi un fatto divertente)",
"Title Auto-Generation": "Generazione automatica del titolo",
"Title cannot be an empty string.": "",
"Title cannot be an empty string.": "Il titolo non può essere una stringa vuota.",
"Title Generation Prompt": "Prompt di generazione del titolo",
"to": "a",
"To access the available model names for downloading,": "Per accedere ai nomi dei modelli disponibili per il download,",
"To access the GGUF models available for downloading,": "Per accedere ai modelli GGUF disponibili per il download,",
"to chat input.": "all'input della chat.",
"Today": "",
"Today": "Oggi",
"Toggle settings": "Attiva/disattiva impostazioni",
"Toggle sidebar": "Attiva/disattiva barra laterale",
"Top K": "Top K",
@ -454,7 +454,7 @@
"Type Hugging Face Resolve (Download) URL": "Digita l'URL di Hugging Face Resolve (Download)",
"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Si è verificato un problema durante la connessione a {{provider}}.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Tipo di file sconosciuto '{{file_type}}', ma accettato e trattato come testo normale",
"Update and Copy Link": "",
"Update and Copy Link": "Aggiorna e copia link",
"Update password": "Aggiorna password",
"Upload a GGUF model": "Carica un modello GGUF",
"Upload files": "Carica file",
@ -462,7 +462,7 @@
"URL Mode": "Modalità URL",
"Use '#' in the prompt input to load and select your documents.": "Usa '#' nell'input del prompt per caricare e selezionare i tuoi documenti.",
"Use Gravatar": "Usa Gravatar",
"Use Initials": "",
"Use Initials": "Usa iniziali",
"user": "utente",
"User Permissions": "Autorizzazioni utente",
"Users": "Utenti",
@ -471,11 +471,11 @@
"variable": "variabile",
"variable to have them replaced with clipboard content.": "variabile per farli sostituire con il contenuto degli appunti.",
"Version": "Versione",
"Warning: If you update or change your embedding model, you will need to re-import all documents.": "",
"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Attenzione: se aggiorni o cambi il tuo modello di embedding, dovrai reimportare tutti i documenti.",
"Web": "Web",
"Web Loader Settings": "",
"Web Params": "",
"Webhook URL": "",
"Web Loader Settings": "Impostazioni del caricatore Web",
"Web Params": "Parametri Web",
"Webhook URL": "URL webhook",
"WebUI Add-ons": "Componenti aggiuntivi WebUI",
"WebUI Settings": "Impostazioni WebUI",
"WebUI will make requests to": "WebUI effettuerà richieste a",
@ -484,12 +484,12 @@
"Whisper (Local)": "Whisper (locale)",
"Write a prompt suggestion (e.g. Who are you?)": "Scrivi un suggerimento per il prompt (ad esempio Chi sei?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "Scrivi un riassunto in 50 parole che riassume [argomento o parola chiave].",
"Yesterday": "",
"Yesterday": "Ieri",
"You": "Tu",
"You have no archived conversations.": "",
"You have shared this chat": "",
"You have no archived conversations.": "Non hai conversazioni archiviate.",
"You have shared this chat": "Hai condiviso questa chat",
"You're a helpful assistant.": "Sei un assistente utile.",
"You're now logged in.": "Ora hai effettuato l'accesso.",
"Youtube": "",
"Youtube Loader Settings": ""
"Youtube": "Youtube",
"Youtube Loader Settings": "Impostazioni del caricatore Youtube"
}

View File

@ -9,6 +9,8 @@ export const user: Writable<SessionUser | undefined> = writable(undefined);
// Frontend
export const MODEL_DOWNLOAD_POOL = writable({});
export const mobile = writable(false);
export const theme = writable('system');
export const chatId = writable('');

View File

@ -0,0 +1,66 @@
import { promptTemplate } from '$lib/utils/index';
import { expect, test } from 'vitest';
test('promptTemplate correctly replaces {{prompt}} placeholder', () => {
const template = 'Hello {{prompt}}!';
const prompt = 'world';
const expected = 'Hello world!';
const actual = promptTemplate(template, prompt);
expect(actual).toBe(expected);
});
test('promptTemplate correctly replaces {{prompt:start:<length>}} placeholder', () => {
const template = 'Hello {{prompt:start:3}}!';
const prompt = 'world';
const expected = 'Hello wor!';
const actual = promptTemplate(template, prompt);
expect(actual).toBe(expected);
});
test('promptTemplate correctly replaces {{prompt:end:<length>}} placeholder', () => {
const template = 'Hello {{prompt:end:3}}!';
const prompt = 'world';
const expected = 'Hello rld!';
const actual = promptTemplate(template, prompt);
expect(actual).toBe(expected);
});
test('promptTemplate correctly replaces {{prompt:middletruncate:<length>}} placeholder when prompt length is greater than length', () => {
const template = 'Hello {{prompt:middletruncate:4}}!';
const prompt = 'world';
const expected = 'Hello wo...ld!';
const actual = promptTemplate(template, prompt);
expect(actual).toBe(expected);
});
test('promptTemplate correctly replaces {{prompt:middletruncate:<length>}} placeholder when prompt length is less than or equal to length', () => {
const template = 'Hello {{prompt:middletruncate:5}}!';
const prompt = 'world';
const expected = 'Hello world!';
const actual = promptTemplate(template, prompt);
expect(actual).toBe(expected);
});
test('promptTemplate returns original template when no placeholders are present', () => {
const template = 'Hello world!';
const prompt = 'world';
const expected = 'Hello world!';
const actual = promptTemplate(template, prompt);
expect(actual).toBe(expected);
});
test('promptTemplate does not replace placeholders inside of replaced placeholders', () => {
const template = 'Hello {{prompt}}!';
const prompt = 'World, {{prompt}} injection';
const expected = 'Hello World, {{prompt}} injection!';
const actual = promptTemplate(template, prompt);
expect(actual).toBe(expected);
});
test('promptTemplate correctly replaces multiple placeholders', () => {
const template = 'Hello {{prompt}}! This is {{prompt:start:3}}!';
const prompt = 'world';
const expected = 'Hello world! This is wor!';
const actual = promptTemplate(template, prompt);
expect(actual).toBe(expected);
});

View File

@ -472,22 +472,39 @@ export const blobToFile = (blob, fileName) => {
return file;
};
export const promptTemplate = (template: string, prompt: string) => {
prompt = prompt.replace(/{{prompt}}|{{prompt:start:\d+}}|{{prompt:end:\d+}}/g, '');
template = template.replace(/{{prompt}}/g, prompt);
// Replace all instances of {{prompt:start:<length>}} with the first <length> characters of the prompt
template = template.replace(/{{prompt:start:(\d+)}}/g, (match, length) =>
prompt.substring(0, parseInt(length))
/**
* This function is used to replace placeholders in a template string with the provided prompt.
* The placeholders can be in the following formats:
* - `{{prompt}}`: This will be replaced with the entire prompt.
* - `{{prompt:start:<length>}}`: This will be replaced with the first <length> characters of the prompt.
* - `{{prompt:end:<length>}}`: This will be replaced with the last <length> characters of the prompt.
* - `{{prompt:middletruncate:<length>}}`: This will be replaced with the prompt truncated to <length> characters, with '...' in the middle.
*
* @param {string} template - The template string containing placeholders.
* @param {string} prompt - The string to replace the placeholders with.
* @returns {string} The template string with the placeholders replaced by the prompt.
*/
export const promptTemplate = (template: string, prompt: string): string => {
return template.replace(
/{{prompt}}|{{prompt:start:(\d+)}}|{{prompt:end:(\d+)}}|{{prompt:middletruncate:(\d+)}}/g,
(match, startLength, endLength, middleLength) => {
if (match === '{{prompt}}') {
return prompt;
} else if (match.startsWith('{{prompt:start:')) {
return prompt.substring(0, startLength);
} else if (match.startsWith('{{prompt:end:')) {
return prompt.slice(-endLength);
} else if (match.startsWith('{{prompt:middletruncate:')) {
if (prompt.length <= middleLength) {
return prompt;
}
const start = prompt.slice(0, Math.ceil(middleLength / 2));
const end = prompt.slice(-Math.floor(middleLength / 2));
return `${start}...${end}`;
}
return '';
}
);
// Replace all instances of {{prompt:end:<length>}} with the last <length> characters of the prompt
template = template.replace(/{{prompt:end:(\d+)}}/g, (match, length) =>
prompt.slice(-parseInt(length))
);
return template;
};
export const approximateToHumanReadable = (nanoseconds: number) => {

View File

@ -1,6 +1,6 @@
<script>
import { onMount, tick, setContext } from 'svelte';
import { config, user, theme, WEBUI_NAME } from '$lib/stores';
import { config, user, theme, WEBUI_NAME, mobile } from '$lib/stores';
import { goto } from '$app/navigation';
import { Toaster, toast } from 'svelte-sonner';
@ -18,9 +18,22 @@
setContext('i18n', i18n);
let loaded = false;
const BREAKPOINT = 1024;
onMount(async () => {
theme.set(localStorage.theme);
mobile.set(window.innerWidth < BREAKPOINT);
const onResize = () => {
if (window.innerWidth < BREAKPOINT) {
mobile.set(true);
} else {
mobile.set(false);
}
};
window.addEventListener('resize', onResize);
let backendConfig = null;
try {
backendConfig = await getBackendConfig();
@ -67,6 +80,10 @@
document.getElementById('splash-screen')?.remove();
loaded = true;
return () => {
window.removeEventListener('resize', onResize);
};
});
</script>