Merge remote-tracking branch 'origin' into logit_bias
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user