Unverified Commit 0ed54055 authored by Ankur's avatar Ankur Committed by GitHub
Browse files

Merge branch 'dev' into main

parents 6d99ef8b c6317640
ollama:
resources:
requests:
cpu: "2000m"
memory: "2Gi"
limits:
cpu: "4000m"
memory: "4Gi"
nvidia.com/gpu: "0"
service:
type: ClusterIP
gpu:
enabled: false
webui:
resources:
requests:
cpu: "500m"
memory: "500Mi"
limits:
cpu: "1000m"
memory: "1Gi"
ingress:
enabled: true
host: open-webui.minikube.local
service:
type: NodePort
nameOverride: ""
ollama:
externalHost: ""
annotations: {}
podAnnotations: {}
replicaCount: 1
image:
repository: ollama/ollama
tag: latest
pullPolicy: Always
resources: {}
persistence:
enabled: true
size: 30Gi
existingClaim: ""
accessModes:
- ReadWriteOnce
storageClass: ""
selector: {}
annotations: {}
nodeSelector: {}
# -- If using a special runtime container such as nvidia, set it here.
runtimeClassName: ""
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
service:
type: ClusterIP
annotations: {}
port: 80
containerPort: 11434
gpu:
# -- Enable additional ENV values to help Ollama discover GPU usage
enabled: false
webui:
annotations: {}
podAnnotations: {}
replicaCount: 1
image:
repository: ghcr.io/open-webui/open-webui
tag: ""
pullPolicy: Always
resources: {}
ingress:
enabled: false
class: ""
# -- Use appropriate annotations for your Ingress controller, e.g., for NGINX:
# nginx.ingress.kubernetes.io/rewrite-target: /
annotations: {}
host: ""
tls: false
existingSecret: ""
persistence:
enabled: true
size: 2Gi
existingClaim: ""
# -- If using multiple replicas, you must update accessModes to ReadWriteMany
accessModes:
- ReadWriteOnce
storageClass: ""
selector: {}
annotations: {}
nodeSelector: {}
tolerations: []
service:
type: ClusterIP
annotations: {}
port: 80
containerPort: 8080
nodePort: ""
labels: {}
loadBalancerClass: ""
...@@ -82,3 +82,12 @@ select { ...@@ -82,3 +82,12 @@ select {
.katex-mathml { .katex-mathml {
display: none; display: none;
} }
.scrollbar-none:active::-webkit-scrollbar-thumb,
.scrollbar-none:focus::-webkit-scrollbar-thumb,
.scrollbar-none:hover::-webkit-scrollbar-thumb {
visibility: visible;
}
.scrollbar-none::-webkit-scrollbar-thumb {
visibility: hidden;
}
...@@ -6,6 +6,12 @@ ...@@ -6,6 +6,12 @@
<link rel="manifest" href="%sveltekit.assets%/manifest.json" crossorigin="use-credentials" /> <link rel="manifest" href="%sveltekit.assets%/manifest.json" crossorigin="use-credentials" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="robots" content="noindex,nofollow" /> <meta name="robots" content="noindex,nofollow" />
<link
rel="search"
type="application/opensearchdescription+xml"
title="Open WebUI"
href="/opensearch.xml"
/>
<script> <script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC // On page load or when changing themes, best to add inline in `head` to avoid FOUC
(() => { (() => {
......
import { WEBUI_API_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL } from '$lib/constants';
import { getTimeRange } from '$lib/utils';
export const createNewChat = async (token: string, chat: object) => { export const createNewChat = async (token: string, chat: object) => {
let error = null; let error = null;
...@@ -59,7 +60,10 @@ export const getChatList = async (token: string = '') => { ...@@ -59,7 +60,10 @@ export const getChatList = async (token: string = '') => {
throw error; throw error;
} }
return res; return res.map((chat) => ({
...chat,
time_range: getTimeRange(chat.updated_at)
}));
}; };
export const getChatListByUserId = async (token: string = '', userId: string) => { export const getChatListByUserId = async (token: string = '', userId: string) => {
...@@ -90,7 +94,10 @@ export const getChatListByUserId = async (token: string = '', userId: string) => ...@@ -90,7 +94,10 @@ export const getChatListByUserId = async (token: string = '', userId: string) =>
throw error; throw error;
} }
return res; return res.map((chat) => ({
...chat,
time_range: getTimeRange(chat.updated_at)
}));
}; };
export const getArchivedChatList = async (token: string = '') => { export const getArchivedChatList = async (token: string = '') => {
...@@ -220,13 +227,16 @@ export const getAllChatTags = async (token: string) => { ...@@ -220,13 +227,16 @@ export const getAllChatTags = async (token: string) => {
export const getChatListByTagName = async (token: string = '', tagName: string) => { export const getChatListByTagName = async (token: string = '', tagName: string) => {
let error = null; let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/tags/tag/${tagName}`, { const res = await fetch(`${WEBUI_API_BASE_URL}/chats/tags`, {
method: 'GET', method: 'POST',
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` }) ...(token && { authorization: `Bearer ${token}` })
} },
body: JSON.stringify({
name: tagName
})
}) })
.then(async (res) => { .then(async (res) => {
if (!res.ok) throw await res.json(); if (!res.ok) throw await res.json();
...@@ -245,7 +255,10 @@ export const getChatListByTagName = async (token: string = '', tagName: string) ...@@ -245,7 +255,10 @@ export const getChatListByTagName = async (token: string = '', tagName: string)
throw error; throw error;
} }
return res; return res.map((chat) => ({
...chat,
time_range: getTimeRange(chat.updated_at)
}));
}; };
export const getChatById = async (token: string, id: string) => { export const getChatById = async (token: string, id: string) => {
......
...@@ -159,7 +159,11 @@ export const generateTitle = async ( ...@@ -159,7 +159,11 @@ export const generateTitle = async (
body: JSON.stringify({ body: JSON.stringify({
model: model, model: model,
prompt: template, prompt: template,
stream: false stream: false,
options: {
// Restrict the number of tokens generated to 50
num_predict: 50
}
}) })
}) })
.then(async (res) => { .then(async (res) => {
...@@ -178,7 +182,7 @@ export const generateTitle = async ( ...@@ -178,7 +182,7 @@ export const generateTitle = async (
throw error; throw error;
} }
return res?.response ?? 'New Chat'; return res?.response.replace(/["']/g, '') ?? 'New Chat';
}; };
export const generatePrompt = async (token: string = '', model: string, conversation: string) => { export const generatePrompt = async (token: string = '', model: string, conversation: string) => {
......
...@@ -295,7 +295,9 @@ export const generateTitle = async ( ...@@ -295,7 +295,9 @@ export const generateTitle = async (
content: template content: template
} }
], ],
stream: false stream: false,
// Restricting the max tokens to 50 to avoid long titles
max_tokens: 50
}) })
}) })
.then(async (res) => { .then(async (res) => {
...@@ -314,5 +316,5 @@ export const generateTitle = async ( ...@@ -314,5 +316,5 @@ export const generateTitle = async (
throw error; throw error;
} }
return res?.choices[0]?.message?.content ?? 'New Chat'; return res?.choices[0]?.message?.content.replace(/["']/g, '') ?? 'New Chat';
}; };
...@@ -33,8 +33,9 @@ type ChunkConfigForm = { ...@@ -33,8 +33,9 @@ type ChunkConfigForm = {
}; };
type RAGConfigForm = { type RAGConfigForm = {
pdf_extract_images: boolean; pdf_extract_images?: boolean;
chunk: ChunkConfigForm; chunk?: ChunkConfigForm;
web_loader_ssl_verification?: boolean;
}; };
export const updateRAGConfig = async (token: string, payload: RAGConfigForm) => { export const updateRAGConfig = async (token: string, payload: RAGConfigForm) => {
......
...@@ -4,6 +4,8 @@ import type { ParsedEvent } from 'eventsource-parser'; ...@@ -4,6 +4,8 @@ import type { ParsedEvent } from 'eventsource-parser';
type TextStreamUpdate = { type TextStreamUpdate = {
done: boolean; done: boolean;
value: string; value: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
citations?: any;
}; };
// createOpenAITextStream takes a responseBody with a SSE response, // createOpenAITextStream takes a responseBody with a SSE response,
...@@ -45,6 +47,11 @@ async function* openAIStreamToIterator( ...@@ -45,6 +47,11 @@ async function* openAIStreamToIterator(
const parsedData = JSON.parse(data); const parsedData = JSON.parse(data);
console.log(parsedData); console.log(parsedData);
if (parsedData.citations) {
yield { done: false, value: '', citations: parsedData.citations };
continue;
}
yield { done: false, value: parsedData.choices?.[0]?.delta?.content ?? '' }; yield { done: false, value: parsedData.choices?.[0]?.delta?.content ?? '' };
} catch (e) { } catch (e) {
console.error('Error extracting delta from SSE event:', e); console.error('Error extracting delta from SSE event:', e);
...@@ -62,6 +69,10 @@ async function* streamLargeDeltasAsRandomChunks( ...@@ -62,6 +69,10 @@ async function* streamLargeDeltasAsRandomChunks(
yield textStreamUpdate; yield textStreamUpdate;
return; return;
} }
if (textStreamUpdate.citations) {
yield textStreamUpdate;
continue;
}
let content = textStreamUpdate.value; let content = textStreamUpdate.value;
if (content.length < 5) { if (content.length < 5) {
yield { done: false, value: content }; yield { done: false, value: content };
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</script> </script>
<Modal bind:show> <Modal bind:show>
<div class="px-5 py-4 dark:text-gray-300 text-gray-700"> <div class="px-5 pt-4 dark:text-gray-300 text-gray-700">
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<div class="text-xl font-bold"> <div class="text-xl font-bold">
{$i18n.t('What’s New in')} {$i18n.t('What’s New in')}
...@@ -57,10 +57,8 @@ ...@@ -57,10 +57,8 @@
</div> </div>
</div> </div>
<hr class=" dark:border-gray-800" />
<div class=" w-full p-4 px-5 text-gray-700 dark:text-gray-100"> <div class=" w-full p-4 px-5 text-gray-700 dark:text-gray-100">
<div class=" overflow-y-scroll max-h-80"> <div class=" overflow-y-scroll max-h-80 scrollbar-none">
<div class="mb-3"> <div class="mb-3">
{#if changelog} {#if changelog}
{#each Object.keys(changelog) as version} {#each Object.keys(changelog) as version}
......
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
reader.readAsText(file); reader.readAsText(file);
} else { } else {
toast.error(`File not found.`); toast.error($i18n.t('File not found.'));
} }
} }
}; };
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
{ {
name: 'All Documents', name: 'All Documents',
type: 'collection', type: 'collection',
title: 'All Documents', title: $i18n.t('All Documents'),
collection_names: $documents.map((doc) => doc.collection_name) collection_names: $documents.map((doc) => doc.collection_name)
} }
] ]
......
<script lang="ts"> <script lang="ts">
import Bolt from '$lib/components/icons/Bolt.svelte'; import Bolt from '$lib/components/icons/Bolt.svelte';
import { onMount } from 'svelte'; import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
export let submitPrompt: Function; export let submitPrompt: Function;
export let suggestionPrompts = []; export let suggestionPrompts = [];
...@@ -33,7 +35,7 @@ ...@@ -33,7 +35,7 @@
{#if prompts.length > 0} {#if prompts.length > 0}
<div class="mb-2 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600"> <div class="mb-2 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600">
<Bolt /> <Bolt />
Suggested {$i18n.t('Suggested')}
</div> </div>
{/if} {/if}
...@@ -71,7 +73,7 @@ ...@@ -71,7 +73,7 @@
<div <div
class="text-xs text-gray-400 group-hover:text-gray-500 dark:text-gray-600 dark:group-hover:text-gray-500 transition self-center" class="text-xs text-gray-400 group-hover:text-gray-500 dark:text-gray-600 dark:group-hover:text-gray-500 transition self-center"
> >
Prompt {$i18n.t('Prompt')}
</div> </div>
<div <div
......
<script lang="ts">
import { getContext, onMount, tick } from 'svelte';
import Modal from '$lib/components/common/Modal.svelte';
const i18n = getContext('i18n');
export let show = false;
export let citation;
let mergedDocuments = [];
$: if (citation) {
mergedDocuments = citation.document?.map((c, i) => {
return {
source: citation.source,
document: c,
metadata: citation.metadata?.[i]
};
});
}
</script>
<Modal size="lg" bind:show>
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
<div class=" text-lg font-medium self-center capitalize">
{$i18n.t('Citation')}
</div>
<button
class="self-center"
on:click={() => {
show = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
<div class="flex flex-col md:flex-row w-full px-6 pb-5 md:space-x-4">
<div
class="flex flex-col w-full dark:text-gray-200 overflow-y-scroll max-h-[22rem] scrollbar-none"
>
{#each mergedDocuments as document, documentIdx}
<div class="flex flex-col w-full">
<div class="text-sm font-medium dark:text-gray-300">
{$i18n.t('Source')}
</div>
<div class="text-sm dark:text-gray-400">
{document.source?.name ?? $i18n.t('No source available')}
</div>
</div>
<div class="flex flex-col w-full">
<div class=" text-sm font-medium dark:text-gray-300">
{$i18n.t('Content')}
</div>
<pre class="text-sm dark:text-gray-400 whitespace-pre-line">
{document.document}
</pre>
</div>
{#if documentIdx !== mergedDocuments.length - 1}
<hr class=" dark:border-gray-850 my-3" />
{/if}
{/each}
</div>
</div>
</div>
</Modal>
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
submitHandler(); submitHandler();
}} }}
> >
Submit {$i18n.t('Submit')}
</button> </button>
</div> </div>
</div> </div>
...@@ -23,15 +23,16 @@ ...@@ -23,15 +23,16 @@
revertSanitizedResponseContent, revertSanitizedResponseContent,
sanitizeResponseContent sanitizeResponseContent
} from '$lib/utils'; } from '$lib/utils';
import { WEBUI_BASE_URL } from '$lib/constants';
import Name from './Name.svelte'; import Name from './Name.svelte';
import ProfileImage from './ProfileImage.svelte'; import ProfileImage from './ProfileImage.svelte';
import Skeleton from './Skeleton.svelte'; import Skeleton from './Skeleton.svelte';
import CodeBlock from './CodeBlock.svelte'; import CodeBlock from './CodeBlock.svelte';
import Image from '$lib/components/common/Image.svelte'; import Image from '$lib/components/common/Image.svelte';
import { WEBUI_BASE_URL } from '$lib/constants';
import Tooltip from '$lib/components/common/Tooltip.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte';
import RateComment from './RateComment.svelte'; import RateComment from './RateComment.svelte';
import CitationsModal from '$lib/components/chat/Messages/CitationsModal.svelte';
export let modelfiles = []; export let modelfiles = [];
export let message; export let message;
...@@ -65,6 +66,9 @@ ...@@ -65,6 +66,9 @@
let showRateComment = false; let showRateComment = false;
let showCitationModal = false;
let selectedCitation = null;
$: tokens = marked.lexer(sanitizeResponseContent(message.content)); $: tokens = marked.lexer(sanitizeResponseContent(message.content));
const renderer = new marked.Renderer(); const renderer = new marked.Renderer();
...@@ -324,6 +328,8 @@ ...@@ -324,6 +328,8 @@
}); });
</script> </script>
<CitationsModal bind:show={showCitationModal} citation={selectedCitation} />
{#key message.id} {#key message.id}
<div class=" flex w-full message-{message.id}" id="message-{message.id}"> <div class=" flex w-full message-{message.id}" id="message-{message.id}">
<ProfileImage <ProfileImage
...@@ -346,9 +352,6 @@ ...@@ -346,9 +352,6 @@
{/if} {/if}
</Name> </Name>
{#if message.content === ''}
<Skeleton />
{:else}
{#if message.files} {#if message.files}
<div class="my-2.5 w-full flex overflow-x-auto gap-2 flex-wrap"> <div class="my-2.5 w-full flex overflow-x-auto gap-2 flex-wrap">
{#each message.files as file} {#each message.files as file}
...@@ -423,6 +426,8 @@ ...@@ -423,6 +426,8 @@
{message.content} {message.content}
</div> </div>
</div> </div>
{:else if message.content === ''}
<Skeleton />
{:else} {:else}
{#each tokens as token} {#each tokens as token}
{#if token.type === 'code'} {#if token.type === 'code'}
...@@ -442,7 +447,45 @@ ...@@ -442,7 +447,45 @@
<!-- {@html marked(message.content.replaceAll('\\', '\\\\'))} --> <!-- {@html marked(message.content.replaceAll('\\', '\\\\'))} -->
{/if} {/if}
{#if message.done} {#if message.citations}
<hr class=" dark:border-gray-800 my-1" />
<div class="my-2.5 w-full flex flex-col gap-1">
{#each message.citations.reduce((acc, citation) => {
citation.document.forEach((document, index) => {
const metadata = citation.metadata?.[index];
const id = metadata?.source ?? 'N/A';
const existingSource = acc.find((item) => item.id === id);
if (existingSource) {
existingSource.document.push(document);
existingSource.metadata.push(metadata);
} else {
acc.push( { id: id, source: citation?.source, document: [document], metadata: metadata ? [metadata] : [] } );
}
});
return acc;
}, []) as citation, idx}
<div class="flex gap-1 text-xs font-semibold">
<div>
[{idx + 1}]
</div>
<button
class="dark:text-gray-500 underline"
on:click={() => {
showCitationModal = true;
selectedCitation = citation;
}}
>
{citation.source.name}
</button>
</div>
{/each}
</div>
{/if}
{#if message.done || siblings.length > 1}
<div <div
class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500" class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
> >
...@@ -494,6 +537,7 @@ ...@@ -494,6 +537,7 @@
</div> </div>
{/if} {/if}
{#if message.done}
{#if !readOnly} {#if !readOnly}
<Tooltip content={$i18n.t('Edit')} placement="bottom"> <Tooltip content={$i18n.t('Edit')} placement="bottom">
<button <button
...@@ -854,6 +898,7 @@ ...@@ -854,6 +898,7 @@
</button> </button>
</Tooltip> </Tooltip>
{/if} {/if}
{/if}
</div> </div>
{/if} {/if}
...@@ -871,7 +916,6 @@ ...@@ -871,7 +916,6 @@
{/if} {/if}
</div> </div>
</div> </div>
{/if}
</div> </div>
</div> </div>
{/key} {/key}
......
<div class="w-full mt-3"> <div class="w-full mt-3 mb-4">
<div class="animate-pulse flex w-full"> <div class="animate-pulse flex w-full">
<div class="space-y-2 w-full"> <div class="space-y-2 w-full">
<div class="h-2 bg-gray-200 dark:bg-gray-600 rounded mr-14" /> <div class="h-2 bg-gray-200 dark:bg-gray-600 rounded mr-14" />
......
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
</div> </div>
{:else} {:else}
<div class=" self-center disabled:text-gray-600 disabled:hover:text-gray-600 mr-2"> <div class=" self-center disabled:text-gray-600 disabled:hover:text-gray-600 mr-2">
<Tooltip content="Remove Model"> <Tooltip content={$i18n.t('Remove Model')}>
<button <button
{disabled} {disabled}
on:click={() => { on:click={() => {
......
...@@ -151,7 +151,7 @@ ...@@ -151,7 +151,7 @@
models.set(await getModels(localStorage.token)); models.set(await getModels(localStorage.token));
} else { } else {
toast.error('Download canceled'); toast.error($i18n.t('Download canceled'));
} }
delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag]; delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag];
...@@ -305,7 +305,7 @@ ...@@ -305,7 +305,7 @@
{:else} {:else}
<div> <div>
<div class="block px-3 py-2 text-sm text-gray-700 dark:text-gray-100"> <div class="block px-3 py-2 text-sm text-gray-700 dark:text-gray-100">
No results found {$i18n.t('No results found')}
</div> </div>
</div> </div>
{/each} {/each}
...@@ -317,7 +317,7 @@ ...@@ -317,7 +317,7 @@
pullModelHandler(); pullModelHandler();
}} }}
> >
Pull "{searchValue}" from Ollama.com {$i18n.t(`Pull "{{searchValue}}" from Ollama.com`, { searchValue: searchValue })}
</button> </button>
{/if} {/if}
...@@ -368,7 +368,7 @@ ...@@ -368,7 +368,7 @@
</div> </div>
<div class="mr-2 translate-y-0.5"> <div class="mr-2 translate-y-0.5">
<Tooltip content="Cancel"> <Tooltip content={$i18n.t('Cancel')}>
<button <button
class="text-gray-800 dark:text-gray-100" class="text-gray-800 dark:text-gray-100"
on:click={() => { on:click={() => {
......
...@@ -447,7 +447,7 @@ ...@@ -447,7 +447,7 @@
{/if} {/if}
</button> </button>
<Tooltip content="Create new key"> <Tooltip content={$i18n.t('Create new key')}>
<button <button
class=" px-1.5 py-1 dark:hover:bg-gray-850transition rounded-lg" class=" px-1.5 py-1 dark:hover:bg-gray-850transition rounded-lg"
on:click={() => { on:click={() => {
...@@ -479,7 +479,7 @@ ...@@ -479,7 +479,7 @@
> >
<Plus strokeWidth="2" className=" size-3.5" /> <Plus strokeWidth="2" className=" size-3.5" />
Create new secret key</button {$i18n.t('Create new secret key')}</button
> >
{/if} {/if}
</div> </div>
......
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