forked from open-webui/open-webui
Merge pull request #543 from ollama-webui/message-refac
refac: response message
This commit is contained in:
commit
92d85ae2e6
2 changed files with 371 additions and 372 deletions
38
src/lib/components/chat/Messages/CodeBlock.svelte
Normal file
38
src/lib/components/chat/Messages/CodeBlock.svelte
Normal file
|
@ -0,0 +1,38 @@
|
|||
<script lang="ts">
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/github-dark.min.css';
|
||||
|
||||
export let lang = '';
|
||||
export let code = '';
|
||||
|
||||
let copied = false;
|
||||
|
||||
const copyCode = async () => {
|
||||
copied = true;
|
||||
await copyToClipboard(code);
|
||||
|
||||
setTimeout(() => {
|
||||
copied = false;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
$: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
|
||||
</script>
|
||||
|
||||
{#if code}
|
||||
<div class="mb-4">
|
||||
<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>
|
||||
|
||||
<pre class=" rounded-b-lg hljs p-4 px-5 overflow-x-auto rounded-t-none"><code
|
||||
class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
|
||||
></pre>
|
||||
</div>
|
||||
{/if}
|
|
@ -1,17 +1,16 @@
|
|||
<script lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
import { marked } from 'marked';
|
||||
|
||||
import tippy from 'tippy.js';
|
||||
import hljs from 'highlight.js';
|
||||
import 'highlight.js/styles/github-dark.min.css';
|
||||
import auto_render from 'katex/dist/contrib/auto-render.mjs';
|
||||
import 'katex/dist/katex.min.css';
|
||||
|
||||
import { onMount, tick } from 'svelte';
|
||||
|
||||
import Name from './Name.svelte';
|
||||
import ProfileImage from './ProfileImage.svelte';
|
||||
import Skeleton from './Skeleton.svelte';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import CodeBlock from './CodeBlock.svelte';
|
||||
|
||||
export let modelfiles = [];
|
||||
export let message;
|
||||
|
@ -33,6 +32,20 @@
|
|||
let tooltipInstance = null;
|
||||
let speaking = null;
|
||||
|
||||
$: tokens = marked.lexer(message.content);
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
// For code blocks with simple backticks
|
||||
renderer.codespan = (code) => {
|
||||
return `<code>${code.replaceAll('&', '&')}</code>`;
|
||||
};
|
||||
|
||||
const { extensions, ...defaults } = marked.getDefaults() as marked.MarkedOptions & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
extensions: any;
|
||||
};
|
||||
|
||||
$: if (message) {
|
||||
renderStyling();
|
||||
}
|
||||
|
@ -45,8 +58,6 @@
|
|||
}
|
||||
|
||||
renderLatex();
|
||||
hljs.highlightAll();
|
||||
createCopyCodeBlockButton();
|
||||
|
||||
if (message.info) {
|
||||
tooltipInstance = tippy(`#info-${message.id}`, {
|
||||
|
@ -78,71 +89,6 @@
|
|||
}
|
||||
};
|
||||
|
||||
const createCopyCodeBlockButton = () => {
|
||||
// use a class selector if available
|
||||
let blocks = document.querySelectorAll('pre');
|
||||
|
||||
blocks.forEach((block) => {
|
||||
// only add button if browser supports Clipboard API
|
||||
|
||||
if (block.childNodes.length < 2 && block.id !== 'user-message') {
|
||||
let code = block.querySelector('code');
|
||||
code.style.borderTopRightRadius = 0;
|
||||
code.style.borderTopLeftRadius = 0;
|
||||
code.style.whiteSpace = 'pre';
|
||||
|
||||
let topBarDiv = document.createElement('div');
|
||||
topBarDiv.style.backgroundColor = '#202123';
|
||||
topBarDiv.style.overflowX = 'auto';
|
||||
topBarDiv.style.display = 'flex';
|
||||
topBarDiv.style.justifyContent = 'space-between';
|
||||
topBarDiv.style.padding = '0 1rem';
|
||||
topBarDiv.style.paddingTop = '4px';
|
||||
topBarDiv.style.borderTopRightRadius = '8px';
|
||||
topBarDiv.style.borderTopLeftRadius = '8px';
|
||||
|
||||
let langDiv = document.createElement('div');
|
||||
|
||||
let codeClassNames = code?.className.split(' ');
|
||||
langDiv.textContent =
|
||||
codeClassNames[0] === 'hljs' ? codeClassNames[1].slice(9) : codeClassNames[0].slice(9);
|
||||
langDiv.style.color = 'white';
|
||||
langDiv.style.margin = '4px';
|
||||
langDiv.style.fontSize = '0.75rem';
|
||||
|
||||
let button = document.createElement('button');
|
||||
button.className = 'copy-code-button';
|
||||
button.textContent = 'Copy Code';
|
||||
button.style.background = 'none';
|
||||
button.style.fontSize = '0.75rem';
|
||||
button.style.border = 'none';
|
||||
button.style.margin = '4px';
|
||||
button.style.cursor = 'pointer';
|
||||
button.style.color = '#ddd';
|
||||
button.addEventListener('click', () => copyCode(block, button));
|
||||
|
||||
topBarDiv.appendChild(langDiv);
|
||||
topBarDiv.appendChild(button);
|
||||
|
||||
block.prepend(topBarDiv);
|
||||
}
|
||||
});
|
||||
|
||||
async function copyCode(block, button) {
|
||||
let code = block.querySelector('code');
|
||||
let text = code.innerText;
|
||||
|
||||
await copyToClipboard(text);
|
||||
|
||||
// visual feedback that task is completed
|
||||
button.innerText = 'Copied!';
|
||||
|
||||
setTimeout(() => {
|
||||
button.innerText = 'Copy Code';
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const renderLatex = () => {
|
||||
let chatMessageElements = document.getElementsByClassName('chat-assistant');
|
||||
// let lastChatMessageElement = chatMessageElements[chatMessageElements.length - 1];
|
||||
|
@ -208,7 +154,8 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div class=" flex w-full message-{message.id}">
|
||||
{#key message.id}
|
||||
<div class=" flex w-full message-{message.id}">
|
||||
<ProfileImage src={modelfiles[message.model]?.imageUrl ?? '/favicon.png'} />
|
||||
|
||||
<div class="w-full overflow-hidden">
|
||||
|
@ -292,7 +239,20 @@
|
|||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{@html marked(message.content.replaceAll('\\', '\\\\'))}
|
||||
{#each tokens as token}
|
||||
{#if token.type === 'code'}
|
||||
<!-- {token.text} -->
|
||||
<CodeBlock lang={token.lang} code={token.text} />
|
||||
{:else}
|
||||
{@html marked.parse(token.raw, {
|
||||
...defaults,
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
renderer
|
||||
})}
|
||||
{/if}
|
||||
{/each}
|
||||
<!-- {@html marked(message.content.replaceAll('\\', '\\\\'))} -->
|
||||
{/if}
|
||||
|
||||
{#if message.done}
|
||||
|
@ -542,4 +502,5 @@
|
|||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/key}
|
||||
|
|
Loading…
Reference in a new issue