Merge remote-tracking branch 'origin/dev' into feat/backend-web-search
This commit is contained in:
@@ -1,11 +1,23 @@
|
||||
<script lang="ts">
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/github-dark.min.css';
|
||||
import { loadPyodide } from 'pyodide';
|
||||
import { tick } from 'svelte';
|
||||
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
|
||||
|
||||
export let id = '';
|
||||
|
||||
export let lang = '';
|
||||
export let code = '';
|
||||
|
||||
let executing = false;
|
||||
|
||||
let stdout = null;
|
||||
let stderr = null;
|
||||
let result = null;
|
||||
|
||||
let copied = false;
|
||||
|
||||
const copyCode = async () => {
|
||||
@@ -17,24 +29,233 @@
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const checkPythonCode = (str) => {
|
||||
// Check if the string contains typical Python syntax characters
|
||||
const pythonSyntax = [
|
||||
'def ',
|
||||
'else:',
|
||||
'elif ',
|
||||
'try:',
|
||||
'except:',
|
||||
'finally:',
|
||||
'yield ',
|
||||
'lambda ',
|
||||
'assert ',
|
||||
'nonlocal ',
|
||||
'del ',
|
||||
'True',
|
||||
'False',
|
||||
'None',
|
||||
' and ',
|
||||
' or ',
|
||||
' not ',
|
||||
' in ',
|
||||
' is ',
|
||||
' with '
|
||||
];
|
||||
|
||||
for (let syntax of pythonSyntax) {
|
||||
if (str.includes(syntax)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the above conditions met, it's probably not Python code
|
||||
return false;
|
||||
};
|
||||
|
||||
const executePython = async (code) => {
|
||||
if (!code.includes('input') && !code.includes('matplotlib')) {
|
||||
executePythonAsWorker(code);
|
||||
} else {
|
||||
result = null;
|
||||
stdout = null;
|
||||
stderr = null;
|
||||
|
||||
executing = true;
|
||||
|
||||
document.pyodideMplTarget = document.getElementById(`plt-canvas-${id}`);
|
||||
|
||||
let pyodide = await loadPyodide({
|
||||
indexURL: '/pyodide/',
|
||||
stdout: (text) => {
|
||||
console.log('Python output:', text);
|
||||
|
||||
if (stdout) {
|
||||
stdout += `${text}\n`;
|
||||
} else {
|
||||
stdout = `${text}\n`;
|
||||
}
|
||||
},
|
||||
stderr: (text) => {
|
||||
console.log('An error occured:', text);
|
||||
if (stderr) {
|
||||
stderr += `${text}\n`;
|
||||
} else {
|
||||
stderr = `${text}\n`;
|
||||
}
|
||||
},
|
||||
packages: ['micropip']
|
||||
});
|
||||
|
||||
try {
|
||||
const micropip = pyodide.pyimport('micropip');
|
||||
|
||||
await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
|
||||
|
||||
let packages = [
|
||||
code.includes('requests') ? 'requests' : null,
|
||||
code.includes('bs4') ? 'beautifulsoup4' : null,
|
||||
code.includes('numpy') ? 'numpy' : null,
|
||||
code.includes('pandas') ? 'pandas' : null,
|
||||
code.includes('matplotlib') ? 'matplotlib' : null,
|
||||
code.includes('sklearn') ? 'scikit-learn' : null,
|
||||
code.includes('scipy') ? 'scipy' : null,
|
||||
code.includes('re') ? 'regex' : null,
|
||||
code.includes('seaborn') ? 'seaborn' : null
|
||||
].filter(Boolean);
|
||||
|
||||
console.log(packages);
|
||||
await micropip.install(packages);
|
||||
|
||||
result = await pyodide.runPythonAsync(`from js import prompt
|
||||
def input(p):
|
||||
return prompt(p)
|
||||
__builtins__.input = input`);
|
||||
|
||||
result = await pyodide.runPython(code);
|
||||
|
||||
if (!result) {
|
||||
result = '[NO OUTPUT]';
|
||||
}
|
||||
|
||||
console.log(result);
|
||||
console.log(stdout);
|
||||
console.log(stderr);
|
||||
|
||||
const pltCanvasElement = document.getElementById(`plt-canvas-${id}`);
|
||||
|
||||
if (pltCanvasElement?.innerHTML !== '') {
|
||||
pltCanvasElement.classList.add('pt-4');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
stderr = error;
|
||||
}
|
||||
|
||||
executing = false;
|
||||
}
|
||||
};
|
||||
|
||||
const executePythonAsWorker = async (code) => {
|
||||
result = null;
|
||||
stdout = null;
|
||||
stderr = null;
|
||||
|
||||
executing = true;
|
||||
|
||||
let packages = [
|
||||
code.includes('requests') ? 'requests' : null,
|
||||
code.includes('bs4') ? 'beautifulsoup4' : null,
|
||||
code.includes('numpy') ? 'numpy' : null,
|
||||
code.includes('pandas') ? 'pandas' : null,
|
||||
code.includes('sklearn') ? 'scikit-learn' : null,
|
||||
code.includes('scipy') ? 'scipy' : null,
|
||||
code.includes('re') ? 'regex' : null,
|
||||
code.includes('seaborn') ? 'seaborn' : null
|
||||
].filter(Boolean);
|
||||
|
||||
console.log(packages);
|
||||
|
||||
const pyodideWorker = new PyodideWorker();
|
||||
|
||||
pyodideWorker.postMessage({
|
||||
id: id,
|
||||
code: code,
|
||||
packages: packages
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (executing) {
|
||||
executing = false;
|
||||
stderr = 'Execution Time Limit Exceeded';
|
||||
pyodideWorker.terminate();
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
pyodideWorker.onmessage = (event) => {
|
||||
console.log('pyodideWorker.onmessage', event);
|
||||
const { id, ...data } = event.data;
|
||||
|
||||
console.log(id, data);
|
||||
|
||||
data['stdout'] && (stdout = data['stdout']);
|
||||
data['stderr'] && (stderr = data['stderr']);
|
||||
data['result'] && (result = data['result']);
|
||||
|
||||
executing = false;
|
||||
};
|
||||
|
||||
pyodideWorker.onerror = (event) => {
|
||||
console.log('pyodideWorker.onerror', event);
|
||||
executing = false;
|
||||
};
|
||||
};
|
||||
|
||||
$: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
|
||||
</script>
|
||||
|
||||
{#if code}
|
||||
<div class="mb-4">
|
||||
<div class="mb-4" dir="ltr">
|
||||
<div
|
||||
class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
|
||||
>
|
||||
<div class="p-1">{@html lang}</div>
|
||||
<button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
|
||||
>{copied ? 'Copied' : 'Copy Code'}</button
|
||||
>
|
||||
|
||||
<div class="flex items-center">
|
||||
{#if ['', 'python'].includes(lang) && (lang === 'python' || checkPythonCode(code))}
|
||||
{#if executing}
|
||||
<div class="copy-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
|
||||
{:else}
|
||||
<button
|
||||
class="copy-code-button bg-none border-none p-1"
|
||||
on:click={() => {
|
||||
executePython(code);
|
||||
}}>Run</button
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
<button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
|
||||
>{copied ? 'Copied' : 'Copy Code'}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre
|
||||
class=" hljs p-4 px-5 overflow-x-auto"
|
||||
style="border-top-left-radius: 0px; border-top-right-radius: 0px;"><code
|
||||
style="border-top-left-radius: 0px; border-top-right-radius: 0px; {(executing ||
|
||||
stdout ||
|
||||
stderr ||
|
||||
result) &&
|
||||
'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code
|
||||
class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
|
||||
></pre>
|
||||
|
||||
<div
|
||||
id="plt-canvas-{id}"
|
||||
class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
|
||||
/>
|
||||
|
||||
{#if executing}
|
||||
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
|
||||
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
|
||||
<div class="text-sm">Running...</div>
|
||||
</div>
|
||||
{:else if stdout || stderr || result}
|
||||
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
|
||||
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
|
||||
<div class="text-sm">{stdout || stderr || result}</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import { updateChatById } from '$lib/apis/chats';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import ResponseMessage from './ResponseMessage.svelte';
|
||||
|
||||
export let chatId;
|
||||
|
||||
export let history;
|
||||
export let messages = [];
|
||||
export let messageIdx;
|
||||
|
||||
export let parentMessage;
|
||||
|
||||
export let selectedModelfiles;
|
||||
|
||||
export let updateChatMessages: Function;
|
||||
export let confirmEditResponseMessage: Function;
|
||||
export let rateMessage: Function;
|
||||
|
||||
export let copyToClipboard: Function;
|
||||
export let continueGeneration: Function;
|
||||
export let regenerateResponse: Function;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let currentMessageId;
|
||||
|
||||
let groupedMessagesIdx = {};
|
||||
let groupedMessages = {};
|
||||
|
||||
$: groupedMessages = parentMessage?.models.reduce((a, model) => {
|
||||
const modelMessages = parentMessage?.childrenIds
|
||||
.map((id) => history.messages[id])
|
||||
.filter((m) => m.model === model);
|
||||
|
||||
return {
|
||||
...a,
|
||||
[model]: { messages: modelMessages }
|
||||
};
|
||||
}, {});
|
||||
|
||||
onMount(async () => {
|
||||
await tick();
|
||||
currentMessageId = messages[messageIdx].id;
|
||||
|
||||
for (const model of parentMessage?.models) {
|
||||
const idx = groupedMessages[model].messages.findIndex((m) => m.id === currentMessageId);
|
||||
|
||||
if (idx !== -1) {
|
||||
groupedMessagesIdx[model] = idx;
|
||||
} else {
|
||||
groupedMessagesIdx[model] = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
|
||||
id="responses-container-{parentMessage.id}"
|
||||
>
|
||||
{#each Object.keys(groupedMessages) as model}
|
||||
{#if groupedMessagesIdx[model] !== undefined && groupedMessages[model].messages.length > 0}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
|
||||
<div
|
||||
class=" snap-center min-w-80 w-full max-w-full m-1 border {history.messages[
|
||||
currentMessageId
|
||||
].model === model
|
||||
? 'border-gray-100 dark:border-gray-700 border-[1.5px]'
|
||||
: 'border-gray-50 dark:border-gray-850 '} transition p-5 rounded-3xl"
|
||||
on:click={() => {
|
||||
currentMessageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
|
||||
|
||||
let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
|
||||
|
||||
console.log(messageId);
|
||||
let messageChildrenIds = history.messages[messageId].childrenIds;
|
||||
|
||||
while (messageChildrenIds.length !== 0) {
|
||||
messageId = messageChildrenIds.at(-1);
|
||||
messageChildrenIds = history.messages[messageId].childrenIds;
|
||||
}
|
||||
|
||||
history.currentId = messageId;
|
||||
dispatch('change');
|
||||
}}
|
||||
>
|
||||
<ResponseMessage
|
||||
message={groupedMessages[model].messages[groupedMessagesIdx[model]]}
|
||||
modelfiles={selectedModelfiles}
|
||||
siblings={groupedMessages[model].messages.map((m) => m.id)}
|
||||
isLastMessage={true}
|
||||
{updateChatMessages}
|
||||
{confirmEditResponseMessage}
|
||||
showPreviousMessage={() => {
|
||||
groupedMessagesIdx[model] = Math.max(0, groupedMessagesIdx[model] - 1);
|
||||
let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
|
||||
|
||||
console.log(messageId);
|
||||
let messageChildrenIds = history.messages[messageId].childrenIds;
|
||||
|
||||
while (messageChildrenIds.length !== 0) {
|
||||
messageId = messageChildrenIds.at(-1);
|
||||
messageChildrenIds = history.messages[messageId].childrenIds;
|
||||
}
|
||||
|
||||
history.currentId = messageId;
|
||||
|
||||
dispatch('change');
|
||||
}}
|
||||
showNextMessage={() => {
|
||||
groupedMessagesIdx[model] = Math.min(
|
||||
groupedMessages[model].messages.length - 1,
|
||||
groupedMessagesIdx[model] + 1
|
||||
);
|
||||
|
||||
let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
|
||||
console.log(messageId);
|
||||
|
||||
let messageChildrenIds = history.messages[messageId].childrenIds;
|
||||
|
||||
while (messageChildrenIds.length !== 0) {
|
||||
messageId = messageChildrenIds.at(-1);
|
||||
messageChildrenIds = history.messages[messageId].childrenIds;
|
||||
}
|
||||
|
||||
history.currentId = messageId;
|
||||
|
||||
dispatch('change');
|
||||
}}
|
||||
{rateMessage}
|
||||
{copyToClipboard}
|
||||
{continueGeneration}
|
||||
regenerateResponse={async (message) => {
|
||||
regenerateResponse(message);
|
||||
await tick();
|
||||
groupedMessagesIdx[model] = groupedMessages[model].messages.length - 1;
|
||||
}}
|
||||
on:save={async (e) => {
|
||||
console.log('save', e);
|
||||
|
||||
const message = e.detail;
|
||||
history.messages[message.id] = message;
|
||||
await updateChatById(localStorage.token, chatId, {
|
||||
messages: messages,
|
||||
history: history
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,3 +1,3 @@
|
||||
<div class=" self-center font-bold mb-0.5 line-clamp-1">
|
||||
<div class=" self-center font-bold mb-0.5 line-clamp-1 contents">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
>
|
||||
{#if model in modelfiles}
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
|
||||
alt="modelfile"
|
||||
class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
|
||||
@@ -50,6 +51,7 @@
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
src={$i18n.language === 'dg-DG'
|
||||
? `/doge.png`
|
||||
: `${WEBUI_BASE_URL}/static/favicon.png`}
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { settings } from '$lib/stores';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
export let src = '/user.png';
|
||||
</script>
|
||||
|
||||
<div class=" mr-3">
|
||||
<img {src} class=" w-8 object-cover rounded-full" alt="profile" draggable="false" />
|
||||
<div class={($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}>
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
src={src.startsWith(WEBUI_BASE_URL) ||
|
||||
src.startsWith('https://www.gravatar.com/avatar/') ||
|
||||
src.startsWith('data:')
|
||||
? src
|
||||
: `/user.png`}
|
||||
class=" w-8 object-cover rounded-full"
|
||||
alt="profile"
|
||||
draggable="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
let selectedCitation = null;
|
||||
|
||||
$: tokens = marked.lexer(sanitizeResponseContent(message.content));
|
||||
$: tokens = marked.lexer(sanitizeResponseContent(message?.content));
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
@@ -332,7 +332,11 @@
|
||||
<CitationsModal bind:show={showCitationModal} citation={selectedCitation} />
|
||||
|
||||
{#key message.id}
|
||||
<div class=" flex w-full message-{message.id}" id="message-{message.id}">
|
||||
<div
|
||||
class=" flex w-full message-{message.id}"
|
||||
id="message-{message.id}"
|
||||
dir={$settings.chatDirection}
|
||||
>
|
||||
<ProfileImage
|
||||
src={modelfiles[message.model]?.imageUrl ??
|
||||
($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
|
||||
@@ -533,9 +537,10 @@
|
||||
{:else if message.content === ''}
|
||||
<Skeleton />
|
||||
{:else}
|
||||
{#each tokens as token}
|
||||
{#each tokens as token, tokenIdx}
|
||||
{#if token.type === 'code'}
|
||||
<CodeBlock
|
||||
id={`${message.id}-${tokenIdx}`}
|
||||
lang={token.lang}
|
||||
code={revertSanitizedResponseContent(token.text)}
|
||||
/>
|
||||
@@ -595,10 +600,10 @@
|
||||
|
||||
{#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"
|
||||
class=" flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500"
|
||||
>
|
||||
{#if siblings.length > 1}
|
||||
<div class="flex self-center">
|
||||
<div class="flex self-center min-w-fit" dir="ltr">
|
||||
<button
|
||||
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
|
||||
on:click={() => {
|
||||
@@ -622,7 +627,7 @@
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100"
|
||||
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100 min-w-fit"
|
||||
>
|
||||
{siblings.indexOf(message.id) + 1}/{siblings.length}
|
||||
</div>
|
||||
@@ -657,7 +662,7 @@
|
||||
<button
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||
: '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"
|
||||
on:click={() => {
|
||||
editMessageHandler();
|
||||
}}
|
||||
@@ -666,7 +671,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
@@ -684,7 +689,7 @@
|
||||
<button
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition copy-response-button"
|
||||
: '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 copy-response-button"
|
||||
on:click={() => {
|
||||
copyToClipboard(message.content);
|
||||
}}
|
||||
@@ -693,7 +698,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
@@ -706,83 +711,12 @@
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
{#if !readOnly}
|
||||
<Tooltip content={$i18n.t('Good Response')} placement="bottom">
|
||||
<button
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded {message?.annotation
|
||||
?.rating === 1
|
||||
? 'bg-gray-100 dark:bg-gray-800'
|
||||
: ''} dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
rateMessage(message.id, 1);
|
||||
showRateComment = true;
|
||||
|
||||
window.setTimeout(() => {
|
||||
document
|
||||
.getElementById(`message-feedback-${message.id}`)
|
||||
?.scrollIntoView();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="w-4 h-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={$i18n.t('Bad Response')} placement="bottom">
|
||||
<button
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded {message?.annotation
|
||||
?.rating === -1
|
||||
? 'bg-gray-100 dark:bg-gray-800'
|
||||
: ''} dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
rateMessage(message.id, -1);
|
||||
showRateComment = true;
|
||||
window.setTimeout(() => {
|
||||
document
|
||||
.getElementById(`message-feedback-${message.id}`)
|
||||
?.scrollIntoView();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="w-4 h-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
<Tooltip content={$i18n.t('Read Aloud')} placement="bottom">
|
||||
<button
|
||||
id="speak-button-{message.id}"
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||
: '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"
|
||||
on:click={() => {
|
||||
if (!loadingSpeech) {
|
||||
toggleSpeakMessage(message);
|
||||
@@ -829,7 +763,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
@@ -844,7 +778,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
@@ -863,7 +797,7 @@
|
||||
<button
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||
: '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"
|
||||
on:click={() => {
|
||||
if (!generatingImage) {
|
||||
generateImage(message);
|
||||
@@ -910,7 +844,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
@@ -930,7 +864,7 @@
|
||||
<button
|
||||
class=" {isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
|
||||
: '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 whitespace-pre-wrap"
|
||||
on:click={() => {
|
||||
console.log(message);
|
||||
}}
|
||||
@@ -940,7 +874,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
@@ -954,13 +888,84 @@
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#if !readOnly}
|
||||
<Tooltip content={$i18n.t('Good Response')} placement="bottom">
|
||||
<button
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {message
|
||||
?.annotation?.rating === 1
|
||||
? 'bg-gray-100 dark:bg-gray-800'
|
||||
: ''} dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
rateMessage(message.id, 1);
|
||||
showRateComment = true;
|
||||
|
||||
window.setTimeout(() => {
|
||||
document
|
||||
.getElementById(`message-feedback-${message.id}`)
|
||||
?.scrollIntoView();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-width="2.3"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="w-4 h-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={$i18n.t('Bad Response')} placement="bottom">
|
||||
<button
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {message
|
||||
?.annotation?.rating === -1
|
||||
? 'bg-gray-100 dark:bg-gray-800'
|
||||
: ''} dark:hover:text-white hover:text-black transition"
|
||||
on:click={() => {
|
||||
rateMessage(message.id, -1);
|
||||
showRateComment = true;
|
||||
window.setTimeout(() => {
|
||||
document
|
||||
.getElementById(`message-feedback-${message.id}`)
|
||||
?.scrollIntoView();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-width="2.3"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="w-4 h-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><path
|
||||
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#if isLastMessage && !readOnly}
|
||||
<Tooltip content={$i18n.t('Continue Response')} placement="bottom">
|
||||
<button
|
||||
type="button"
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
|
||||
: '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={() => {
|
||||
continueGeneration();
|
||||
}}
|
||||
@@ -969,7 +974,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
@@ -992,14 +997,16 @@
|
||||
type="button"
|
||||
class="{isLastMessage
|
||||
? 'visible'
|
||||
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
|
||||
on:click={regenerateResponse}
|
||||
: '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={() => {
|
||||
regenerateResponse(message);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
import { modelfiles, settings } from '$lib/stores';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
import { user as _user } from '$lib/stores';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -54,7 +56,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class=" flex w-full user-message">
|
||||
<div class=" flex w-full user-message" dir={$settings.chatDirection}>
|
||||
{#if !($settings?.chatBubble ?? true)}
|
||||
<ProfileImage
|
||||
src={message.user
|
||||
@@ -63,7 +65,7 @@
|
||||
: user?.profile_image_url ?? '/user.png'}
|
||||
/>
|
||||
{/if}
|
||||
<div class="w-full overflow-hidden">
|
||||
<div class="w-full overflow-hidden pl-1">
|
||||
{#if !($settings?.chatBubble ?? true)}
|
||||
<div>
|
||||
<Name>
|
||||
@@ -74,7 +76,7 @@
|
||||
{$i18n.t('You')}
|
||||
<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
|
||||
{/if}
|
||||
{:else if $settings.showUsername}
|
||||
{:else if $settings.showUsername || $_user.name !== user.name}
|
||||
{user.name}
|
||||
{:else}
|
||||
{$i18n.t('You')}
|
||||
@@ -235,11 +237,11 @@
|
||||
<div
|
||||
class=" flex {$settings?.chatBubble ?? true
|
||||
? 'justify-end'
|
||||
: ''} space-x-1 text-gray-700 dark:text-gray-500"
|
||||
: ''} text-gray-600 dark:text-gray-500"
|
||||
>
|
||||
{#if !($settings?.chatBubble ?? true)}
|
||||
{#if siblings.length > 1}
|
||||
<div class="flex self-center">
|
||||
<div class="flex self-center" dir="ltr">
|
||||
<button
|
||||
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
|
||||
on:click={() => {
|
||||
@@ -293,7 +295,7 @@
|
||||
{#if !readOnly}
|
||||
<Tooltip content={$i18n.t('Edit')} placement="bottom">
|
||||
<button
|
||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
|
||||
class="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 edit-user-message-button"
|
||||
on:click={() => {
|
||||
editMessageHandler();
|
||||
}}
|
||||
@@ -302,7 +304,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
@@ -318,7 +320,7 @@
|
||||
|
||||
<Tooltip content={$i18n.t('Copy')} placement="bottom">
|
||||
<button
|
||||
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
|
||||
class="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"
|
||||
on:click={() => {
|
||||
copyToClipboard(message.content);
|
||||
}}
|
||||
@@ -327,7 +329,7 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-width="2.3"
|
||||
stroke="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
@@ -368,7 +370,7 @@
|
||||
|
||||
{#if $settings?.chatBubble ?? true}
|
||||
{#if siblings.length > 1}
|
||||
<div class="flex self-center">
|
||||
<div class="flex self-center" dir="ltr">
|
||||
<button
|
||||
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
|
||||
on:click={() => {
|
||||
|
||||
Reference in New Issue
Block a user