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 {
.katex-mathml {
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 @@
<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="robots" content="noindex,nofollow" />
<link
rel="search"
type="application/opensearchdescription+xml"
title="Open WebUI"
href="/opensearch.xml"
/>
<script>
// 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 { getTimeRange } from '$lib/utils';
export const createNewChat = async (token: string, chat: object) => {
let error = null;
......@@ -59,7 +60,10 @@ export const getChatList = async (token: string = '') => {
throw error;
}
return res;
return res.map((chat) => ({
...chat,
time_range: getTimeRange(chat.updated_at)
}));
};
export const getChatListByUserId = async (token: string = '', userId: string) => {
......@@ -90,7 +94,10 @@ export const getChatListByUserId = async (token: string = '', userId: string) =>
throw error;
}
return res;
return res.map((chat) => ({
...chat,
time_range: getTimeRange(chat.updated_at)
}));
};
export const getArchivedChatList = async (token: string = '') => {
......@@ -220,13 +227,16 @@ export const getAllChatTags = async (token: string) => {
export const getChatListByTagName = async (token: string = '', tagName: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/tags/tag/${tagName}`, {
method: 'GET',
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/tags`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
},
body: JSON.stringify({
name: tagName
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
......@@ -245,7 +255,10 @@ export const getChatListByTagName = async (token: string = '', tagName: string)
throw error;
}
return res;
return res.map((chat) => ({
...chat,
time_range: getTimeRange(chat.updated_at)
}));
};
export const getChatById = async (token: string, id: string) => {
......
......@@ -159,7 +159,11 @@ export const generateTitle = async (
body: JSON.stringify({
model: model,
prompt: template,
stream: false
stream: false,
options: {
// Restrict the number of tokens generated to 50
num_predict: 50
}
})
})
.then(async (res) => {
......@@ -178,7 +182,7 @@ export const generateTitle = async (
throw error;
}
return res?.response ?? 'New Chat';
return res?.response.replace(/["']/g, '') ?? 'New Chat';
};
export const generatePrompt = async (token: string = '', model: string, conversation: string) => {
......
......@@ -295,7 +295,9 @@ export const generateTitle = async (
content: template
}
],
stream: false
stream: false,
// Restricting the max tokens to 50 to avoid long titles
max_tokens: 50
})
})
.then(async (res) => {
......@@ -314,5 +316,5 @@ export const generateTitle = async (
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 = {
};
type RAGConfigForm = {
pdf_extract_images: boolean;
chunk: ChunkConfigForm;
pdf_extract_images?: boolean;
chunk?: ChunkConfigForm;
web_loader_ssl_verification?: boolean;
};
export const updateRAGConfig = async (token: string, payload: RAGConfigForm) => {
......
......@@ -4,6 +4,8 @@ import type { ParsedEvent } from 'eventsource-parser';
type TextStreamUpdate = {
done: boolean;
value: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
citations?: any;
};
// createOpenAITextStream takes a responseBody with a SSE response,
......@@ -45,6 +47,11 @@ async function* openAIStreamToIterator(
const parsedData = JSON.parse(data);
console.log(parsedData);
if (parsedData.citations) {
yield { done: false, value: '', citations: parsedData.citations };
continue;
}
yield { done: false, value: parsedData.choices?.[0]?.delta?.content ?? '' };
} catch (e) {
console.error('Error extracting delta from SSE event:', e);
......@@ -62,6 +69,10 @@ async function* streamLargeDeltasAsRandomChunks(
yield textStreamUpdate;
return;
}
if (textStreamUpdate.citations) {
yield textStreamUpdate;
continue;
}
let content = textStreamUpdate.value;
if (content.length < 5) {
yield { done: false, value: content };
......
......@@ -22,7 +22,7 @@
</script>
<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="text-xl font-bold">
{$i18n.t('What’s New in')}
......@@ -57,10 +57,8 @@
</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=" overflow-y-scroll max-h-80">
<div class=" overflow-y-scroll max-h-80 scrollbar-none">
<div class="mb-3">
{#if changelog}
{#each Object.keys(changelog) as version}
......
......@@ -107,7 +107,7 @@
reader.readAsText(file);
} else {
toast.error(`File not found.`);
toast.error($i18n.t('File not found.'));
}
}
};
......
......@@ -24,7 +24,7 @@
{
name: 'All Documents',
type: 'collection',
title: 'All Documents',
title: $i18n.t('All Documents'),
collection_names: $documents.map((doc) => doc.collection_name)
}
]
......
<script lang="ts">
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 suggestionPrompts = [];
......@@ -33,7 +35,7 @@
{#if prompts.length > 0}
<div class="mb-2 flex gap-1 text-sm font-medium items-center text-gray-400 dark:text-gray-600">
<Bolt />
Suggested
{$i18n.t('Suggested')}
</div>
{/if}
......@@ -71,7 +73,7 @@
<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"
>
Prompt
{$i18n.t('Prompt')}
</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 @@
submitHandler();
}}
>
Submit
{$i18n.t('Submit')}
</button>
</div>
</div>
......@@ -23,15 +23,16 @@
revertSanitizedResponseContent,
sanitizeResponseContent
} from '$lib/utils';
import { WEBUI_BASE_URL } from '$lib/constants';
import Name from './Name.svelte';
import ProfileImage from './ProfileImage.svelte';
import Skeleton from './Skeleton.svelte';
import CodeBlock from './CodeBlock.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 RateComment from './RateComment.svelte';
import CitationsModal from '$lib/components/chat/Messages/CitationsModal.svelte';
export let modelfiles = [];
export let message;
......@@ -65,6 +66,9 @@
let showRateComment = false;
let showCitationModal = false;
let selectedCitation = null;
$: tokens = marked.lexer(sanitizeResponseContent(message.content));
const renderer = new marked.Renderer();
......@@ -324,6 +328,8 @@
});
</script>
<CitationsModal bind:show={showCitationModal} citation={selectedCitation} />
{#key message.id}
<div class=" flex w-full message-{message.id}" id="message-{message.id}">
<ProfileImage
......@@ -346,9 +352,6 @@
{/if}
</Name>
{#if message.content === ''}
<Skeleton />
{:else}
{#if message.files}
<div class="my-2.5 w-full flex overflow-x-auto gap-2 flex-wrap">
{#each message.files as file}
......@@ -423,6 +426,8 @@
{message.content}
</div>
</div>
{:else if message.content === ''}
<Skeleton />
{:else}
{#each tokens as token}
{#if token.type === 'code'}
......@@ -442,7 +447,45 @@
<!-- {@html marked(message.content.replaceAll('\\', '\\\\'))} -->
{/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
class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
>
......@@ -494,6 +537,7 @@
</div>
{/if}
{#if message.done}
{#if !readOnly}
<Tooltip content={$i18n.t('Edit')} placement="bottom">
<button
......@@ -854,6 +898,7 @@
</button>
</Tooltip>
{/if}
{/if}
</div>
{/if}
......@@ -871,7 +916,6 @@
{/if}
</div>
</div>
{/if}
</div>
</div>
{/key}
......
<div class="w-full mt-3">
<div class="w-full mt-3 mb-4">
<div class="animate-pulse flex w-full">
<div class="space-y-2 w-full">
<div class="h-2 bg-gray-200 dark:bg-gray-600 rounded mr-14" />
......
......@@ -82,7 +82,7 @@
</div>
{:else}
<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
{disabled}
on:click={() => {
......
......@@ -151,7 +151,7 @@
models.set(await getModels(localStorage.token));
} else {
toast.error('Download canceled');
toast.error($i18n.t('Download canceled'));
}
delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag];
......@@ -305,7 +305,7 @@
{:else}
<div>
<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>
{/each}
......@@ -317,7 +317,7 @@
pullModelHandler();
}}
>
Pull "{searchValue}" from Ollama.com
{$i18n.t(`Pull "{{searchValue}}" from Ollama.com`, { searchValue: searchValue })}
</button>
{/if}
......@@ -368,7 +368,7 @@
</div>
<div class="mr-2 translate-y-0.5">
<Tooltip content="Cancel">
<Tooltip content={$i18n.t('Cancel')}>
<button
class="text-gray-800 dark:text-gray-100"
on:click={() => {
......
......@@ -447,7 +447,7 @@
{/if}
</button>
<Tooltip content="Create new key">
<Tooltip content={$i18n.t('Create new key')}>
<button
class=" px-1.5 py-1 dark:hover:bg-gray-850transition rounded-lg"
on:click={() => {
......@@ -479,7 +479,7 @@
>
<Plus strokeWidth="2" className=" size-3.5" />
Create new secret key</button
{$i18n.t('Create new secret key')}</button
>
{/if}
</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