Unverified Commit 13b0e7d6 authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge pull request #4434 from open-webui/dev

0.3.13
parents 8d257ed5 c8badfe2
...@@ -739,7 +739,7 @@ ...@@ -739,7 +739,7 @@
? await getAndUpdateUserLocation(localStorage.token) ? await getAndUpdateUserLocation(localStorage.token)
: undefined : undefined
)}${ )}${
responseMessage?.userContext ?? null (responseMessage?.userContext ?? null)
? `\n\nUser Context:\n${responseMessage?.userContext ?? ''}` ? `\n\nUser Context:\n${responseMessage?.userContext ?? ''}`
: '' : ''
}` }`
...@@ -811,7 +811,7 @@ ...@@ -811,7 +811,7 @@
options: { options: {
...(params ?? $settings.params ?? {}), ...(params ?? $settings.params ?? {}),
stop: stop:
params?.stop ?? $settings?.params?.stop ?? undefined (params?.stop ?? $settings?.params?.stop ?? undefined)
? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map( ? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(
(str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"')) (str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
) )
...@@ -877,6 +877,10 @@ ...@@ -877,6 +877,10 @@
} else { } else {
responseMessage.content += data.message.content; responseMessage.content += data.message.content;
if (navigator.vibrate && ($settings?.hapticFeedback ?? false)) {
navigator.vibrate(5);
}
const sentences = extractSentencesForAudio(responseMessage.content); const sentences = extractSentencesForAudio(responseMessage.content);
sentences.pop(); sentences.pop();
...@@ -1056,7 +1060,7 @@ ...@@ -1056,7 +1060,7 @@
stream: true, stream: true,
model: model.id, model: model.id,
stream_options: stream_options:
model.info?.meta?.capabilities?.usage ?? false (model.info?.meta?.capabilities?.usage ?? false)
? { ? {
include_usage: true include_usage: true
} }
...@@ -1072,7 +1076,7 @@ ...@@ -1072,7 +1076,7 @@
? await getAndUpdateUserLocation(localStorage.token) ? await getAndUpdateUserLocation(localStorage.token)
: undefined : undefined
)}${ )}${
responseMessage?.userContext ?? null (responseMessage?.userContext ?? null)
? `\n\nUser Context:\n${responseMessage?.userContext ?? ''}` ? `\n\nUser Context:\n${responseMessage?.userContext ?? ''}`
: '' : ''
}` }`
...@@ -1092,7 +1096,7 @@ ...@@ -1092,7 +1096,7 @@
text: text:
arr.length - 1 !== idx arr.length - 1 !== idx
? message.content ? message.content
: message?.raContent ?? message.content : (message?.raContent ?? message.content)
}, },
...message.files ...message.files
.filter((file) => file.type === 'image') .filter((file) => file.type === 'image')
...@@ -1108,12 +1112,12 @@ ...@@ -1108,12 +1112,12 @@
content: content:
arr.length - 1 !== idx arr.length - 1 !== idx
? message.content ? message.content
: message?.raContent ?? message.content : (message?.raContent ?? message.content)
}) })
})), })),
seed: params?.seed ?? $settings?.params?.seed ?? undefined, seed: params?.seed ?? $settings?.params?.seed ?? undefined,
stop: stop:
params?.stop ?? $settings?.params?.stop ?? undefined (params?.stop ?? $settings?.params?.stop ?? undefined)
? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map( ? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(
(str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"')) (str) => decodeURIComponent(JSON.parse('"' + str.replace(/\"/g, '\\"') + '"'))
) )
...@@ -1177,6 +1181,10 @@ ...@@ -1177,6 +1181,10 @@
} else { } else {
responseMessage.content += value; responseMessage.content += value;
if (navigator.vibrate && ($settings?.hapticFeedback ?? false)) {
navigator.vibrate(5);
}
const sentences = extractSentencesForAudio(responseMessage.content); const sentences = extractSentencesForAudio(responseMessage.content);
sentences.pop(); sentences.pop();
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
import FileItem from '$lib/components/common/FileItem.svelte'; import FileItem from '$lib/components/common/FileItem.svelte';
import Collapsible from '$lib/components/common/Collapsible.svelte'; import Collapsible from '$lib/components/common/Collapsible.svelte';
import { user } from '$lib/stores';
export let models = []; export let models = [];
export let chatFiles = []; export let chatFiles = [];
...@@ -78,7 +80,7 @@ ...@@ -78,7 +80,7 @@
<Collapsible title={$i18n.t('Advanced Params')} open={true}> <Collapsible title={$i18n.t('Advanced Params')} open={true}>
<div class="text-sm mt-1.5" slot="content"> <div class="text-sm mt-1.5" slot="content">
<div> <div>
<AdvancedParams bind:params /> <AdvancedParams admin={$user?.role === 'admin'} bind:params />
</div> </div>
</div> </div>
</Collapsible> </Collapsible>
......
...@@ -305,7 +305,7 @@ ...@@ -305,7 +305,7 @@
{#each messages as message, messageIdx} {#each messages as message, messageIdx}
<div class=" w-full {messageIdx === messages.length - 1 ? ' pb-12' : ''}"> <div class=" w-full {messageIdx === messages.length - 1 ? ' pb-12' : ''}">
<div <div
class="flex flex-col justify-between px-5 mb-3 {$settings?.widescreenMode ?? null class="flex flex-col justify-between px-5 mb-3 {($settings?.widescreenMode ?? null)
? 'max-w-full' ? 'max-w-full'
: 'max-w-5xl'} mx-auto rounded-lg group" : 'max-w-5xl'} mx-auto rounded-lg group"
> >
...@@ -317,10 +317,10 @@ ...@@ -317,10 +317,10 @@
{message} {message}
isFirstMessage={messageIdx === 0} isFirstMessage={messageIdx === 0}
siblings={message.parentId !== null siblings={message.parentId !== null
? history.messages[message.parentId]?.childrenIds ?? [] ? (history.messages[message.parentId]?.childrenIds ?? [])
: Object.values(history.messages) : (Object.values(history.messages)
.filter((message) => message.parentId === null) .filter((message) => message.parentId === null)
.map((message) => message.id) ?? []} .map((message) => message.id) ?? [])}
{confirmEditMessage} {confirmEditMessage}
{showPreviousMessage} {showPreviousMessage}
{showNextMessage} {showNextMessage}
......
<script lang="ts"> <script lang="ts">
import Spinner from '$lib/components/common/Spinner.svelte';
import { copyToClipboard } from '$lib/utils';
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import 'highlight.js/styles/github-dark.min.css';
import { loadPyodide } from 'pyodide'; import { loadPyodide } from 'pyodide';
import { onMount, tick } from 'svelte'; import mermaid from 'mermaid';
import { getContext, getAllContexts, onMount } from 'svelte';
import { copyToClipboard } from '$lib/utils';
import 'highlight.js/styles/github-dark.min.css';
import PyodideWorker from '$lib/workers/pyodide.worker?worker'; import PyodideWorker from '$lib/workers/pyodide.worker?worker';
const i18n = getContext('i18n');
export let id = ''; export let id = '';
export let token;
export let lang = ''; export let lang = '';
export let code = ''; export let code = '';
let mermaidHtml = null;
let highlightedCode = null; let highlightedCode = null;
let executing = false; let executing = false;
...@@ -204,7 +212,18 @@ __builtins__.input = input`); ...@@ -204,7 +212,18 @@ __builtins__.input = input`);
}; };
let debounceTimeout; let debounceTimeout;
$: if (code) { $: if (code) {
if (lang === 'mermaid' && (token?.raw ?? '').endsWith('```')) {
(async () => {
try {
const { svg } = await mermaid.render(`mermaid-${id}`, code);
mermaidHtml = svg;
} catch (error) {
console.error('Error:', error);
}
})();
} else {
// Function to perform the code highlighting // Function to perform the code highlighting
const highlightCode = () => { const highlightCode = () => {
highlightedCode = hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value || code; highlightedCode = hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value || code;
...@@ -212,13 +231,33 @@ __builtins__.input = input`); ...@@ -212,13 +231,33 @@ __builtins__.input = input`);
// Clear the previous timeout if it exists // Clear the previous timeout if it exists
clearTimeout(debounceTimeout); clearTimeout(debounceTimeout);
// Set a new timeout to debounce the code highlighting // Set a new timeout to debounce the code highlighting
debounceTimeout = setTimeout(highlightCode, 10); debounceTimeout = setTimeout(highlightCode, 10);
} }
}
onMount(async () => {
await mermaid.initialize({ startOnLoad: true });
if (lang === 'mermaid' && (token?.raw ?? '').endsWith('```')) {
try {
const { svg } = await mermaid.render(`mermaid-${id}`, code);
mermaidHtml = svg;
} catch (error) {
console.error('Error:', error);
}
}
});
</script> </script>
<div class="my-2" dir="ltr"> <div class="my-2" dir="ltr">
{#if lang === 'mermaid'}
{#if mermaidHtml}
{@html mermaidHtml}
{:else}
<pre class=" mermaid-{id}">{code}</pre>
{/if}
{:else}
<div <div
class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto" class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
> >
...@@ -233,12 +272,12 @@ __builtins__.input = input`); ...@@ -233,12 +272,12 @@ __builtins__.input = input`);
class="copy-code-button bg-none border-none p-1" class="copy-code-button bg-none border-none p-1"
on:click={() => { on:click={() => {
executePython(code); executePython(code);
}}>Run</button }}>{$i18n.t('Run')}</button
> >
{/if} {/if}
{/if} {/if}
<button class="copy-code-button bg-none border-none p-1" on:click={copyCode} <button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
>{copied ? 'Copied' : 'Copy Code'}</button >{copied ? $i18n.t('Copied') : $i18n.t('Copy Code')}</button
> >
</div> </div>
</div> </div>
...@@ -270,4 +309,5 @@ __builtins__.input = input`); ...@@ -270,4 +309,5 @@ __builtins__.input = input`);
<div class="text-sm">{stdout || stderr || result}</div> <div class="text-sm">{stdout || stderr || result}</div>
</div> </div>
{/if} {/if}
{/if}
</div> </div>
<script lang="ts">
import katex from 'katex';
import 'katex/contrib/mhchem';
export let content: string;
export let displayMode: boolean = false;
</script>
{@html katex.renderToString(content, { displayMode, throwOnError: false })}
<script lang="ts"> <script lang="ts">
import type { Token } from 'marked'; import type { Token } from 'marked';
import { unescapeHtml } from '$lib/utils'; import { revertSanitizedResponseContent, unescapeHtml } from '$lib/utils';
import { onMount } from 'svelte';
import Image from '$lib/components/common/Image.svelte'; import Image from '$lib/components/common/Image.svelte';
import KatexRenderer from './KatexRenderer.svelte';
export let id: string; export let id: string;
export let tokens: Token[]; export let tokens: Token[];
</script> </script>
...@@ -25,14 +28,18 @@ ...@@ -25,14 +28,18 @@
<svelte:self id={`${id}-em`} tokens={token.tokens} /> <svelte:self id={`${id}-em`} tokens={token.tokens} />
</em> </em>
{:else if token.type === 'codespan'} {:else if token.type === 'codespan'}
<code class="codespan">{unescapeHtml(token.text.replaceAll('&amp;', '&'))}</code> <code class="codespan">{revertSanitizedResponseContent(token.raw)}</code>
{:else if token.type === 'br'} {:else if token.type === 'br'}
<br /> <br />
{:else if token.type === 'del'} {:else if token.type === 'del'}
<del> <del>
<svelte:self id={`${id}-del`} tokens={token.tokens} /> <svelte:self id={`${id}-del`} tokens={token.tokens} />
</del> </del>
{:else if token.type === 'inlineKatex'}
{#if token.text}
<KatexRenderer content={revertSanitizedResponseContent(token.text)} displayMode={false} />
{/if}
{:else if token.type === 'text'} {:else if token.type === 'text'}
{unescapeHtml(token.text)} {token.raw}
{/if} {/if}
{/each} {/each}
...@@ -62,8 +62,8 @@ ...@@ -62,8 +62,8 @@
{#if !($settings?.chatBubble ?? true)} {#if !($settings?.chatBubble ?? true)}
<ProfileImage <ProfileImage
src={message.user src={message.user
? $models.find((m) => m.id === message.user)?.info?.meta?.profile_image_url ?? '/user.png' ? ($models.find((m) => m.id === message.user)?.info?.meta?.profile_image_url ?? '/user.png')
: user?.profile_image_url ?? '/user.png'} : (user?.profile_image_url ?? '/user.png')}
/> />
{/if} {/if}
<div class="w-full overflow-hidden pl-1"> <div class="w-full overflow-hidden pl-1">
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
{#if message.files} {#if message.files}
<div class="mt-2.5 mb-1 w-full flex flex-col justify-end overflow-x-auto gap-1 flex-wrap"> <div class="mt-2.5 mb-1 w-full flex flex-col justify-end overflow-x-auto gap-1 flex-wrap">
{#each message.files as file} {#each message.files as file}
<div class={$settings?.chatBubble ?? true ? 'self-end' : ''}> <div class={($settings?.chatBubble ?? true) ? 'self-end' : ''}>
{#if file.type === 'image'} {#if file.type === 'image'}
<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" /> <img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
{:else} {:else}
...@@ -162,9 +162,9 @@ ...@@ -162,9 +162,9 @@
</div> </div>
{:else} {:else}
<div class="w-full"> <div class="w-full">
<div class="flex {$settings?.chatBubble ?? true ? 'justify-end' : ''} mb-2"> <div class="flex {($settings?.chatBubble ?? true) ? 'justify-end' : ''} mb-2">
<div <div
class="rounded-3xl {$settings?.chatBubble ?? true class="rounded-3xl {($settings?.chatBubble ?? true)
? `max-w-[90%] px-5 py-2 bg-gray-50 dark:bg-gray-850 ${ ? `max-w-[90%] px-5 py-2 bg-gray-50 dark:bg-gray-850 ${
message.files ? 'rounded-tr-lg' : '' message.files ? 'rounded-tr-lg' : ''
}` }`
...@@ -175,7 +175,7 @@ ...@@ -175,7 +175,7 @@
</div> </div>
<div <div
class=" flex {$settings?.chatBubble ?? true class=" flex {($settings?.chatBubble ?? true)
? 'justify-end' ? 'justify-end'
: ''} text-gray-600 dark:text-gray-500" : ''} text-gray-600 dark:text-gray-500"
> >
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
on:click={() => { on:click={() => {
valves[property] = valves[property] =
(valves[property] ?? null) === null (valves[property] ?? null) === null
? valvesSpec.properties[property]?.default ?? '' ? (valvesSpec.properties[property]?.default ?? '')
: null; : null;
dispatch('change'); dispatch('change');
......
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