Merge remote-tracking branch 'origin' into logit_bias

This commit is contained in:
dannyl1u
2025-02-27 23:48:22 -08:00
181 changed files with 10428 additions and 5218 deletions
+21 -4
View File
@@ -187,15 +187,20 @@
setToolIds();
}
$: if (atSelectedModel || selectedModels) {
setToolIds();
}
const setToolIds = async () => {
if (!$tools) {
tools.set(await getTools(localStorage.token));
}
if (selectedModels.length !== 1) {
if (selectedModels.length !== 1 && !atSelectedModel) {
return;
}
const model = $models.find((m) => m.id === selectedModels[0]);
const model = atSelectedModel ?? $models.find((m) => m.id === selectedModels[0]);
if (model) {
selectedToolIds = (model?.info?.meta?.toolIds ?? []).filter((id) =>
$tools.find((t) => t.id === id)
@@ -836,6 +841,7 @@
content: m.content,
info: m.info ? m.info : undefined,
timestamp: m.timestamp,
...(m.usage ? { usage: m.usage } : {}),
...(m.sources ? { sources: m.sources } : {})
})),
model_item: $models.find((m) => m.id === modelId),
@@ -1273,7 +1279,9 @@
const chatInputElement = document.getElementById('chat-input');
if (chatInputElement) {
await tick();
chatInputElement.style.height = '';
chatInputElement.style.height = Math.min(chatInputElement.scrollHeight, 320) + 'px';
}
const _files = JSON.parse(JSON.stringify(files));
@@ -1488,7 +1496,10 @@
params?.system ?? $settings?.system ?? '',
$user.name,
$settings?.userLocation
? await getAndUpdateUserLocation(localStorage.token)
? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
console.error(err);
return undefined;
})
: undefined
)}${
(responseMessage?.userContext ?? null)
@@ -1573,7 +1584,12 @@
variables: {
...getPromptVariables(
$user.name,
$settings?.userLocation ? await getAndUpdateUserLocation(localStorage.token) : undefined
$settings?.userLocation
? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
console.error(err);
return undefined;
})
: undefined
)
},
model_item: $models.find((m) => m.id === model.id),
@@ -1965,6 +1981,7 @@
bind:autoScroll
bind:prompt
{selectedModels}
{atSelectedModel}
{sendPrompt}
{showMessage}
{submitMessage}
@@ -16,6 +16,7 @@
export let modelIds = [];
export let models = [];
export let atSelectedModel;
export let submitPrompt;
@@ -126,7 +127,8 @@
<div class=" w-full font-primary" in:fade={{ duration: 200, delay: 300 }}>
<Suggestions
className="grid grid-cols-2"
suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
suggestionPrompts={atSelectedModel?.info?.meta?.suggestion_prompts ??
models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
$config?.default_prompt_suggestions ??
[]}
on:select={(e) => {
+40 -27
View File
@@ -2,6 +2,7 @@
import { toast } from 'svelte-sonner';
import { v4 as uuidv4 } from 'uuid';
import { createPicker, getAuthToken } from '$lib/utils/google-drive-picker';
import { pickAndDownloadFile } from '$lib/utils/onedrive-file-picker';
import { onMount, tick, getContext, createEventDispatcher, onDestroy } from 'svelte';
const dispatch = createEventDispatcher();
@@ -173,22 +174,6 @@
}
files = [...files, fileItem];
// Check if the file is an audio file and transcribe/convert it to text file
if (['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/x-m4a'].includes(file['type'])) {
const res = await transcribeAudio(localStorage.token, file).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
console.log(res);
const blob = new Blob([res.text], { type: 'text/plain' });
file = blobToFile(blob, `${file.name}.txt`);
fileItem.name = file.name;
fileItem.size = file.size;
}
}
try {
// During the file upload, file content is automatically extracted.
@@ -249,7 +234,9 @@
return;
}
if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
if (
['image/gif', 'image/webp', 'image/jpeg', 'image/png', 'image/avif'].includes(file['type'])
) {
if (visionCapableModels.length === 0) {
toast.error($i18n.t('Selected model(s) do not support image inputs'));
return;
@@ -428,7 +415,7 @@
</div>
{/if}
{#if webSearchEnabled || ($settings?.webSearch ?? false) === 'always'}
{#if webSearchEnabled || ($config?.features?.enable_web_search && ($settings?.webSearch ?? false)) === 'always'}
<div class="flex items-center justify-between w-full">
<div class="flex items-center gap-2.5 text-sm dark:text-gray-500">
<div class="pl-1">
@@ -825,7 +812,11 @@
}
// Submit the prompt when Enter key is pressed
if (prompt !== '' && e.keyCode === 13 && !e.shiftKey) {
if (
(prompt !== '' || files.length > 0) &&
e.keyCode === 13 &&
!e.shiftKey
) {
dispatch('submit', prompt);
}
}
@@ -904,7 +895,11 @@
}
// Submit the prompt when Enter key is pressed
if (prompt !== '' && e.key === 'Enter' && !e.shiftKey) {
if (
(prompt !== '' || files.length > 0) &&
e.key === 'Enter' &&
!e.shiftKey
) {
dispatch('submit', prompt);
}
}
@@ -1106,6 +1101,21 @@
);
}
}}
uploadOneDriveHandler={async () => {
try {
const fileData = await pickAndDownloadFile();
if (fileData) {
const file = new File([fileData.blob], fileData.name, {
type: fileData.blob.type || 'application/octet-stream'
});
await uploadFileHandler(file);
} else {
console.log('No file was selected from OneDrive');
}
} catch (error) {
console.error('OneDrive Error:', error);
}
}}
onClose={async () => {
await tick();
@@ -1283,14 +1293,17 @@
stream = null;
if (!$TTSWorker) {
await TTSWorker.set(
new KokoroWorker({
dtype: $settings.audio?.tts?.engineConfig?.dtype ?? 'fp32'
})
);
if ($settings.audio?.tts?.engine === 'browser-kokoro') {
// If the user has not initialized the TTS worker, initialize it
if (!$TTSWorker) {
await TTSWorker.set(
new KokoroWorker({
dtype: $settings.audio?.tts?.engineConfig?.dtype ?? 'fp32'
})
);
await $TTSWorker.init();
await $TTSWorker.init();
}
}
showCallOverlay.set(true);
@@ -74,7 +74,13 @@
}
if (command.content.includes('{{USER_LOCATION}}')) {
const location = await getUserPosition();
let location;
try {
location = await getUserPosition();
} catch (error) {
toast.error($i18n.t('Location access not allowed'));
location = 'LOCATION_UNKNOWN';
}
text = text.replaceAll('{{USER_LOCATION}}', String(location));
}
@@ -5,6 +5,7 @@
import { config, user, tools as _tools, mobile } from '$lib/stores';
import { createPicker } from '$lib/utils/google-drive-picker';
import { getTools } from '$lib/apis/tools';
import Dropdown from '$lib/components/common/Dropdown.svelte';
@@ -24,6 +25,7 @@
export let inputFilesHandler: Function;
export let uploadGoogleDriveHandler: Function;
export let uploadOneDriveHandler: Function;
export let selectedToolIds: string[] = [];
@@ -225,6 +227,97 @@
<div class="line-clamp-1">{$i18n.t('Google Drive')}</div>
</DropdownMenu.Item>
{/if}
{#if $config?.features?.enable_onedrive_integration}
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
on:click={() => {
uploadOneDriveHandler();
}}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="w-5 h-5" fill="none">
<mask
id="mask0_87_7796"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="0"
y="6"
width="32"
height="20"
>
<path
d="M7.82979 26C3.50549 26 0 22.5675 0 18.3333C0 14.1921 3.35322 10.8179 7.54613 10.6716C9.27535 7.87166 12.4144 6 16 6C20.6308 6 24.5169 9.12183 25.5829 13.3335C29.1316 13.3603 32 16.1855 32 19.6667C32 23.0527 29 26 25.8723 25.9914L7.82979 26Z"
fill="#C4C4C4"
/>
</mask>
<g mask="url(#mask0_87_7796)">
<path
d="M7.83017 26.0001C5.37824 26.0001 3.18957 24.8966 1.75391 23.1691L18.0429 16.3335L30.7089 23.4647C29.5926 24.9211 27.9066 26.0001 26.0004 25.9915C23.1254 26.0001 12.0629 26.0001 7.83017 26.0001Z"
fill="url(#paint0_linear_87_7796)"
/>
<path
d="M25.5785 13.3149L18.043 16.3334L30.709 23.4647C31.5199 22.4065 32.0004 21.0916 32.0004 19.6669C32.0004 16.1857 29.1321 13.3605 25.5833 13.3337C25.5817 13.3274 25.5801 13.3212 25.5785 13.3149Z"
fill="url(#paint1_linear_87_7796)"
/>
<path
d="M7.06445 10.7028L18.0423 16.3333L25.5779 13.3148C24.5051 9.11261 20.6237 6 15.9997 6C12.4141 6 9.27508 7.87166 7.54586 10.6716C7.3841 10.6773 7.22358 10.6877 7.06445 10.7028Z"
fill="url(#paint2_linear_87_7796)"
/>
<path
d="M1.7535 23.1687L18.0425 16.3331L7.06471 10.7026C3.09947 11.0792 0 14.3517 0 18.3331C0 20.1665 0.657197 21.8495 1.7535 23.1687Z"
fill="url(#paint3_linear_87_7796)"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_87_7796"
x1="4.42591"
y1="24.6668"
x2="27.2309"
y2="23.2764"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#2086B8" />
<stop offset="1" stop-color="#46D3F6" />
</linearGradient>
<linearGradient
id="paint1_linear_87_7796"
x1="23.8302"
y1="19.6668"
x2="30.2108"
y2="15.2082"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#1694DB" />
<stop offset="1" stop-color="#62C3FE" />
</linearGradient>
<linearGradient
id="paint2_linear_87_7796"
x1="8.51037"
y1="7.33333"
x2="23.3335"
y2="15.9348"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#0D3D78" />
<stop offset="1" stop-color="#063B83" />
</linearGradient>
<linearGradient
id="paint3_linear_87_7796"
x1="-0.340429"
y1="19.9998"
x2="14.5634"
y2="14.4649"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#16589B" />
<stop offset="1" stop-color="#1464B7" />
</linearGradient>
</defs>
</svg>
<div class="line-clamp-1">{$i18n.t('OneDrive')}</div>
</DropdownMenu.Item>
{/if}
</DropdownMenu.Content>
</div>
</Dropdown>
+21 -9
View File
@@ -1,6 +1,14 @@
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { chats, config, settings, user as _user, mobile, currentChatPage } from '$lib/stores';
import {
chats,
config,
settings,
user as _user,
mobile,
currentChatPage,
temporaryChatEnabled
} from '$lib/stores';
import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
@@ -24,6 +32,7 @@
export let prompt;
export let history = {};
export let selectedModels;
export let atSelectedModel;
let messages = [];
@@ -85,15 +94,17 @@
};
const updateChat = async () => {
history = history;
await tick();
await updateChatById(localStorage.token, chatId, {
history: history,
messages: messages
});
if (!$temporaryChatEnabled) {
history = history;
await tick();
await updateChatById(localStorage.token, chatId, {
history: history,
messages: messages
});
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
}
};
const showPreviousMessage = async (message) => {
@@ -339,6 +350,7 @@
{#if Object.keys(history?.messages ?? {}).length == 0}
<ChatPlaceholder
modelIds={selectedModels}
{atSelectedModel}
submitPrompt={async (p) => {
let text = p;
@@ -7,6 +7,7 @@
const i18n = getContext('i18n');
export let id = '';
export let sources = [];
let citations = [];
@@ -42,6 +43,7 @@
}
$: {
console.log('sources', sources);
citations = sources.reduce((acc, source) => {
if (Object.keys(source).length === 0) {
return acc;
@@ -52,7 +54,7 @@
const distance = source.distances?.[index];
// Within the same citation there could be multiple documents
const id = metadata?.source ?? 'N/A';
const id = metadata?.source ?? source?.source?.id ?? 'N/A';
let _source = source?.source;
if (metadata?.name) {
@@ -100,7 +102,7 @@
<div class="flex text-xs font-medium flex-wrap">
{#each citations as citation, idx}
<button
id={`source-${idx}`}
id={`source-${id}-${idx}`}
class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-white dark:bg-gray-900 rounded-xl max-w-96"
on:click={() => {
showCitationModal = true;
@@ -179,7 +181,7 @@
<div class="flex text-xs font-medium flex-wrap">
{#each citations as citation, idx}
<button
id={`source-${idx}`}
id={`source-${id}-${idx}`}
class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
on:click={() => {
showCitationModal = true;
+131 -25
View File
@@ -1,18 +1,9 @@
<script lang="ts">
import hljs from 'highlight.js';
import { loadPyodide } from 'pyodide';
import mermaid from 'mermaid';
import { v4 as uuidv4 } from 'uuid';
import {
getContext,
getAllContexts,
onMount,
tick,
createEventDispatcher,
onDestroy
} from 'svelte';
import { getContext, onMount, tick, onDestroy } from 'svelte';
import { copyToClipboard } from '$lib/utils';
import 'highlight.js/styles/github-dark.min.css';
@@ -20,12 +11,17 @@
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
import CodeEditor from '$lib/components/common/CodeEditor.svelte';
import SvgPanZoom from '$lib/components/common/SVGPanZoom.svelte';
import { config } from '$lib/stores';
import { executeCode } from '$lib/apis/utils';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let id = '';
export let onSave = (e) => {};
export let onCode = (e) => {};
export let save = false;
export let run = true;
@@ -68,7 +64,7 @@
saved = true;
code = _code;
dispatch('save', code);
onSave(code);
setTimeout(() => {
saved = false;
@@ -120,16 +116,87 @@
};
const executePython = async (code) => {
executePythonAsWorker(code);
};
const executePythonAsWorker = async (code) => {
result = null;
stdout = null;
stderr = null;
executing = true;
if ($config?.code?.engine === 'jupyter') {
const output = await executeCode(localStorage.token, code).catch((error) => {
toast.error(`${error}`);
return null;
});
if (output) {
if (output['stdout']) {
stdout = output['stdout'];
const stdoutLines = stdout.split('\n');
for (const [idx, line] of stdoutLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
if (stdout.startsWith(`${line}\n`)) {
stdout = stdout.replace(`${line}\n`, ``);
} else if (stdout.startsWith(`${line}`)) {
stdout = stdout.replace(`${line}`, ``);
}
}
}
}
if (output['result']) {
result = output['result'];
const resultLines = result.split('\n');
for (const [idx, line] of resultLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
if (result.startsWith(`${line}\n`)) {
result = result.replace(`${line}\n`, ``);
} else if (result.startsWith(`${line}`)) {
result = result.replace(`${line}`, ``);
}
}
}
}
output['stderr'] && (stderr = output['stderr']);
}
executing = false;
} else {
executePythonAsWorker(code);
}
};
const executePythonAsWorker = async (code) => {
let packages = [
code.includes('requests') ? 'requests' : null,
code.includes('bs4') ? 'beautifulsoup4' : null,
@@ -189,7 +256,40 @@
];
}
stdout = stdout.replace(`${line}\n`, ``);
if (stdout.startsWith(`${line}\n`)) {
stdout = stdout.replace(`${line}\n`, ``);
} else if (stdout.startsWith(`${line}`)) {
stdout = stdout.replace(`${line}`, ``);
}
}
}
}
if (data['result']) {
result = data['result'];
const resultLines = result.split('\n');
for (const [idx, line] of resultLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
if (result.startsWith(`${line}\n`)) {
result = result.replace(`${line}\n`, ``);
} else if (result.startsWith(`${line}`)) {
result = result.replace(`${line}`, ``);
}
}
}
}
@@ -237,7 +337,7 @@
render();
}
$: dispatch('code', { lang, code });
$: onCode({ lang, code });
$: if (attributes) {
onAttributesUpdate();
@@ -273,7 +373,7 @@
console.log('codeblock', lang, code);
if (lang) {
dispatch('code', { lang, code });
onCode({ lang, code });
}
if (document.documentElement.classList.contains('dark')) {
mermaid.initialize({
@@ -361,11 +461,11 @@
value={code}
{id}
{lang}
on:save={() => {
onSave={() => {
saveCode();
}}
on:change={(e) => {
_code = e.detail.value;
onChange={(value) => {
_code = value;
}}
/>
</div>
@@ -375,7 +475,7 @@
class="bg-gray-50 dark:bg-[#202123] dark:text-white max-w-full overflow-x-auto scrollbar-hidden"
/>
{#if executing || stdout || stderr || result}
{#if executing || stdout || stderr || result || files}
<div
class="bg-gray-50 dark:bg-[#202123] dark:text-white rounded-b-lg! py-4 px-4 flex flex-col gap-2"
>
@@ -388,7 +488,13 @@
{#if stdout || stderr}
<div class=" ">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">{stdout || stderr}</div>
<div
class="text-sm {stdout?.split('\n')?.length > 100
? `max-h-96`
: ''} overflow-y-auto"
>
{stdout || stderr}
</div>
</div>
{/if}
{#if result || files}
@@ -401,7 +507,7 @@
<div class="flex flex-col gap-2">
{#each files as file}
{#if file.type.startsWith('image')}
<img src={file.data} alt="Output" />
<img src={file.data} alt="Output" class=" w-full max-w-[36rem]" />
{/if}
{/each}
</div>
@@ -120,6 +120,11 @@
sourceIds={(sources ?? []).reduce((acc, s) => {
let ids = [];
s.document.forEach((document, index) => {
if (model?.info?.meta?.capabilities?.citations == false) {
ids.push('N/A');
return ids;
}
const metadata = s.metadata?.[index];
const id = metadata?.source ?? 'N/A';
@@ -29,7 +29,7 @@
{:else if token.text.includes(`<iframe src="${WEBUI_BASE_URL}/api/v1/files/`)}
{@html `${token.text}`}
{:else if token.text.includes(`<source_id`)}
<Source {token} onClick={onSourceClick} />
<Source {id} {token} onClick={onSourceClick} />
{:else}
{token.text}
{/if}
@@ -76,7 +76,7 @@
{#if token.type === 'hr'}
<hr class=" border-gray-100 dark:border-gray-850" />
{:else if token.type === 'heading'}
<svelte:element this={headerComponent(token.depth)}>
<svelte:element this={headerComponent(token.depth)} dir="auto">
<MarkdownInlineTokens id={`${id}-${tokenIdx}-h`} tokens={token.tokens} {onSourceClick} />
</svelte:element>
{:else if token.type === 'code'}
@@ -88,14 +88,14 @@
code={token?.text ?? ''}
{attributes}
{save}
on:code={(e) => {
dispatch('code', e.detail);
onCode={(value) => {
dispatch('code', value);
}}
on:save={(e) => {
onSave={(e) => {
dispatch('update', {
raw: token.raw,
oldContent: token.text,
newContent: e.detail
newContent: value
});
}}
/>
@@ -169,14 +169,14 @@
</div>
</div>
{:else if token.type === 'blockquote'}
<blockquote>
<blockquote dir="auto">
<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} {onTaskClick} {onSourceClick} />
</blockquote>
{:else if token.type === 'list'}
{#if token.ordered}
<ol start={token.start || 1}>
{#each token.items as item, itemIdx}
<li>
<li dir="auto" class="text-start">
{#if item?.task}
<input
class=" translate-y-[1px] -translate-x-1"
@@ -208,7 +208,7 @@
{:else}
<ul>
{#each token.items as item, itemIdx}
<li>
<li dir="auto" class="text-start">
{#if item?.task}
<input
class=" translate-y-[1px] -translate-x-1"
@@ -239,7 +239,12 @@
</ul>
{/if}
{:else if token.type === 'details'}
<Collapsible title={token.summary} attributes={token?.attributes} className="w-full space-y-1">
<Collapsible
title={token.summary}
attributes={token?.attributes}
className="w-full space-y-1"
dir="auto"
>
<div class=" mb-1.5" slot="content">
<svelte:self
id={`${id}-${tokenIdx}-d`}
@@ -268,7 +273,7 @@
onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';"
></iframe>
{:else if token.type === 'paragraph'}
<p>
<p dir="auto">
<MarkdownInlineTokens
id={`${id}-${tokenIdx}-p`}
tokens={token.tokens ?? []}
@@ -277,7 +282,7 @@
</p>
{:else if token.type === 'text'}
{#if top}
<p>
<p dir="auto">
{#if token.tokens}
<MarkdownInlineTokens id={`${id}-${tokenIdx}-t`} tokens={token.tokens} {onSourceClick} />
{:else}
@@ -1,8 +1,9 @@
<script lang="ts">
export let id;
export let token;
export let onClick: Function = () => {};
let attributes: Record<string, string> = {};
let attributes: Record<string, string | undefined> = {};
function extractAttributes(input: string): Record<string, string> {
const regex = /(\w+)="([^"]*)"/g;
@@ -35,13 +36,15 @@
$: attributes = extractAttributes(token.text);
</script>
<button
class="text-xs font-medium w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/60 dark:hover:text-white bg-gray-50 text-black/60 hover:text-black transition rounded-lg"
on:click={() => {
onClick(attributes.data);
}}
>
<span class="line-clamp-1">
{formattedTitle(attributes.title)}
</span>
</button>
{#if attributes.title !== 'N/A'}
<button
class="text-xs font-medium w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/60 dark:hover:text-white bg-gray-50 text-black/60 hover:text-black transition rounded-lg"
on:click={() => {
onClick(id, attributes.data);
}}
>
<span class="line-clamp-1">
{attributes.title ? formattedTitle(attributes.title) : ''}
</span>
</button>
{/if}
@@ -37,6 +37,9 @@
import Spinner from '$lib/components/common/Spinner.svelte';
import WebSearchResults from './ResponseMessage/WebSearchResults.svelte';
import Sparkles from '$lib/components/icons/Sparkles.svelte';
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import Error from './Error.svelte';
import Citations from './Citations.svelte';
import CodeExecutions from './CodeExecutions.svelte';
@@ -126,6 +129,9 @@
export let isLastMessage = true;
export let readOnly = false;
let buttonsContainerElement: HTMLDivElement;
let showDeleteConfirm = false;
let model = null;
$: model = $models.find((m) => m.id === message.model);
@@ -513,9 +519,29 @@
// console.log('ResponseMessage mounted');
await tick();
if (buttonsContainerElement) {
console.log(buttonsContainerElement);
buttonsContainerElement.addEventListener('wheel', function (event) {
// console.log(event.deltaY);
event.preventDefault();
if (event.deltaY !== 0) {
// Adjust horizontal scroll position based on vertical scroll
buttonsContainerElement.scrollLeft += event.deltaY;
}
});
}
});
</script>
<DeleteConfirmDialog
bind:show={showDeleteConfirm}
title={$i18n.t('Delete message?')}
on:confirm={() => {
deleteMessageHandler();
}}
/>
{#key message.id}
<div
class=" flex w-full message-{message.id}"
@@ -719,9 +745,9 @@
onTaskClick={async (e) => {
console.log(e);
}}
onSourceClick={async (e) => {
console.log(e);
let sourceButton = document.getElementById(`source-${e}`);
onSourceClick={async (id, idx) => {
console.log(id, idx);
let sourceButton = document.getElementById(`source-${message.id}-${idx}`);
const sourcesCollapsible = document.getElementById(`collapsible-sources`);
if (sourceButton) {
@@ -740,7 +766,7 @@
});
// Try clicking the source button again
sourceButton = document.getElementById(`source-${e}`);
sourceButton = document.getElementById(`source-${message.id}-${idx}`);
sourceButton && sourceButton.click();
}
}}
@@ -777,7 +803,7 @@
{/if}
{#if (message?.sources || message?.citations) && (model?.info?.meta?.capabilities?.citations ?? true)}
<Citations sources={message?.sources ?? message?.citations} />
<Citations id={message?.id} sources={message?.sources ?? message?.citations} />
{/if}
{#if message.code_executions}
@@ -789,10 +815,11 @@
</div>
{#if !edit}
{#if message.done || siblings.length > 1}
<div
class=" flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500 mt-0.5"
>
<div
bind:this={buttonsContainerElement}
class="flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500 mt-0.5"
>
{#if message.done || siblings.length > 1}
{#if siblings.length > 1}
<div class="flex self-center min-w-fit" dir="ltr">
<button
@@ -1247,7 +1274,7 @@
? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={() => {
deleteMessageHandler();
showDeleteConfirm = true;
}}
>
<svg
@@ -1300,8 +1327,8 @@
{/if}
{/if}
{/if}
</div>
{/if}
{/if}
</div>
{#if message.done && showRateComment}
<RateComment
@@ -5,22 +5,32 @@
<div class="w-full mt-2 mb-2">
<div class="animate-pulse flex w-full">
<div class="{size === 'md' ? 'space-y-2' : 'space-y-1.5'} w-full">
<div class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm mr-14" />
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm mr-14"
/>
<div class="grid grid-cols-3 gap-4">
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
class="{size === 'md'
? 'h-2'
: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
/>
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
class="{size === 'md'
? 'h-2'
: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
/>
</div>
<div class="grid grid-cols-4 gap-4">
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
class="{size === 'md'
? 'h-2'
: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
/>
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
class="{size === 'md'
? 'h-2'
: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
/>
<div
class="{size === 'md'
@@ -13,6 +13,8 @@
import FileItem from '$lib/components/common/FileItem.svelte';
import Markdown from './Markdown.svelte';
import Image from '$lib/components/common/Image.svelte';
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
const i18n = getContext('i18n');
@@ -34,6 +36,8 @@
export let isFirstMessage: boolean;
export let readOnly: boolean;
let showDeleteConfirm = false;
let edit = false;
let editedContent = '';
let messageEditTextAreaElement: HTMLTextAreaElement;
@@ -85,6 +89,14 @@
});
</script>
<DeleteConfirmDialog
bind:show={showDeleteConfirm}
title={$i18n.t('Delete message?')}
on:confirm={() => {
deleteMessageHandler();
}}
/>
<div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
{#if !($settings?.chatBubble ?? true)}
<div class={`shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
@@ -340,7 +352,7 @@
<button
class="invisible group-hover:visible p-1 rounded-sm dark:hover:text-white hover:text-black transition"
on:click={() => {
deleteMessageHandler();
showDeleteConfirm = true;
}}
>
<svg
+2 -1
View File
@@ -213,7 +213,8 @@
<div class="mx-auto max-w-2xl font-primary" in:fade={{ duration: 200, delay: 200 }}>
<div class="mx-5">
<Suggestions
suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
suggestionPrompts={atSelectedModel?.info?.meta?.suggestion_prompts ??
models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
$config?.default_prompt_suggestions ??
[]}
inputValue={prompt}
@@ -214,7 +214,7 @@
<button
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
on:click={async () => {
const url = await getGravatarUrl($user.email);
const url = await getGravatarUrl(localStorage.token, $user.email);
profileImageUrl = url;
}}>{$i18n.t('Use Gravatar')}</button
@@ -786,7 +786,6 @@
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
@@ -353,8 +353,10 @@
params.logit_bias !== null ? params.logit_bias : undefined,
frequency_penalty:
params.frequency_penalty !== null ? params.frequency_penalty : undefined,
presence_penalty: params.frequency_penalty !== null ? params.frequency_penalty : undefined,
repeat_penalty: params.frequency_penalty !== null ? params.frequency_penalty : undefined,
presence_penalty:
params.frequency_penalty !== null ? params.frequency_penalty : undefined,
repeat_penalty:
params.frequency_penalty !== null ? params.frequency_penalty : undefined,
repeat_last_n: params.repeat_last_n !== null ? params.repeat_last_n : undefined,
mirostat: params.mirostat !== null ? params.mirostat : undefined,
mirostat_eta: params.mirostat_eta !== null ? params.mirostat_eta : undefined,
@@ -12,6 +12,7 @@
import { error } from '@sveltejs/kit';
import EditMemoryModal from './EditMemoryModal.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
const i18n = getContext('i18n');
dayjs.extend(localizedFormat);
@@ -26,6 +27,21 @@
let selectedMemory = null;
let showClearConfirmDialog = false;
let onClearConfirmed = async () => {
const res = await deleteMemoriesByUserId(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res && memories.length > 0) {
toast.success($i18n.t('Memory cleared successfully'));
memories = [];
}
showClearConfirmDialog = false;
};
$: if (show && memories.length === 0 && loading) {
(async () => {
memories = await getMemories(localStorage.token);
@@ -175,15 +191,11 @@
>
<button
class=" px-3.5 py-1.5 font-medium text-red-500 hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-red-300 dark:outline-red-800 rounded-3xl"
on:click={async () => {
const res = await deleteMemoriesByUserId(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
toast.success($i18n.t('Memory cleared successfully'));
memories = [];
on:click={() => {
if (memories.length > 0) {
showClearConfirmDialog = true;
} else {
toast.error($i18n.t('No memories to clear'));
}
}}>{$i18n.t('Clear memory')}</button
>
@@ -192,6 +204,16 @@
</div>
</Modal>
<ConfirmDialog
title={$i18n.t('Clear Memory')}
message={$i18n.t('Are you sure you want to clear all memories? This action cannot be undone.')}
show={showClearConfirmDialog}
on:confirm={onClearConfirmed}
on:cancel={() => {
showClearConfirmDialog = false;
}}
/>
<AddMemoryModal
bind:show={showAddMemoryModal}
on:save={async () => {
+40 -2
View File
@@ -122,6 +122,30 @@
</div>
</div>
</div>
<div class="w-full flex justify-between items-center">
<div class=" text-sm">{$i18n.t('Generate prompt pair')}</div>
<div class="flex space-x-1 text-xs">
<div
class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
>
Ctrl/⌘
</div>
<div
class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
>
Shift
</div>
<div
class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
>
Enter
</div>
</div>
</div>
</div>
<div class="flex flex-col space-y-3 w-full self-start">
@@ -219,7 +243,7 @@
<div class="flex flex-col space-y-3 w-full self-start">
<div class="w-full flex justify-between items-center">
<div class=" text-sm">
{$i18n.t('Attach file')}
{$i18n.t('Attach file from knowledge')}
</div>
<div class="flex space-x-1 text-xs">
@@ -247,7 +271,7 @@
<div class="w-full flex justify-between items-center">
<div class=" text-sm">
{$i18n.t('Select model')}
{$i18n.t('Talk to model')}
</div>
<div class="flex space-x-1 text-xs">
@@ -258,6 +282,20 @@
</div>
</div>
</div>
<div class="w-full flex justify-between items-center">
<div class=" text-sm">
{$i18n.t('Accept autocomplete generation / Jump to prompt variable')}
</div>
<div class="flex space-x-1 text-xs">
<div
class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
>
TAB
</div>
</div>
</div>
</div>
</div>
</div>