Merge pull request #1561 from ItzNotABug/fix-ai-rendering

Fix: markdown and styles in ai assistant
This commit is contained in:
Ernst Mulders
2024-12-16 15:52:38 +01:00
committed by GitHub
3 changed files with 83 additions and 6 deletions
+2
View File
@@ -36,6 +36,7 @@
"plausible-tracker": "^0.3.9",
"pretty-bytes": "^6.1.1",
"prismjs": "^1.29.0",
"remarkable": "^2.0.1",
"svelte-confetti": "^1.4.0",
"tippy.js": "^6.3.7"
},
@@ -52,6 +53,7 @@
"@testing-library/user-event": "^14.5.2",
"@types/deep-equal": "^1.0.4",
"@types/prismjs": "^1.26.5",
"@types/remarkable": "^2.0.8",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitest/ui": "^1.6.0",
+28
View File
@@ -59,6 +59,9 @@ importers:
prismjs:
specifier: ^1.29.0
version: 1.29.0
remarkable:
specifier: ^2.0.1
version: 2.0.1
svelte-confetti:
specifier: ^1.4.0
version: 1.4.0(svelte@4.2.19)
@@ -102,6 +105,9 @@ importers:
'@types/prismjs':
specifier: ^1.26.5
version: 1.26.5
'@types/remarkable':
specifier: ^2.0.8
version: 2.0.8
'@typescript-eslint/eslint-plugin':
specifier: ^7.18.0
version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)
@@ -1327,6 +1333,9 @@ packages:
'@types/pug@2.0.10':
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
'@types/remarkable@2.0.8':
resolution: {integrity: sha512-eKXqPZfpQl1kOADjdKchHrp2gwn9qMnGXhH/AtZe0UrklzhGJkawJo/Y/D0AlWcdWoWamFNIum8+/nkAISQVGg==}
'@types/shimmer@1.2.0':
resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==}
@@ -1559,6 +1568,9 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
autolinker@3.16.2:
resolution: {integrity: sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==}
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
@@ -3062,6 +3074,11 @@ packages:
resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==}
engines: {node: '>= 0.4'}
remarkable@2.0.1:
resolution: {integrity: sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==}
engines: {node: '>= 6.0.0'}
hasBin: true
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@@ -5143,6 +5160,8 @@ snapshots:
'@types/pug@2.0.10': {}
'@types/remarkable@2.0.8': {}
'@types/shimmer@1.2.0': {}
'@types/stack-utils@2.0.3': {}
@@ -5435,6 +5454,10 @@ snapshots:
asynckit@0.4.0: {}
autolinker@3.16.2:
dependencies:
tslib: 2.8.1
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.0.0
@@ -7144,6 +7167,11 @@ snapshots:
es-errors: 1.3.0
set-function-name: 2.0.2
remarkable@2.0.1:
dependencies:
argparse: 1.0.10
autolinker: 3.16.2
require-directory@2.1.1: {}
require-in-the-middle@7.4.0:
+53 -6
View File
@@ -1,6 +1,9 @@
<script lang="ts">
import { Remarkable } from 'remarkable';
import Template from './template.svelte';
const markdownInstance = new Remarkable();
import { Alert, AvatarInitials, Code, LoadingDots, SvgIcon } from '$lib/components';
import { user } from '$lib/stores/user';
import { useCompletion } from 'ai/svelte';
@@ -19,8 +22,6 @@
credentials: 'include'
});
let question = $input;
const examples = [
'How to add platform in the console?',
'How can I manage users, permissions, and access control in Appwrite?',
@@ -87,10 +88,47 @@
$: answer = parseCompletion($completion);
function renderMarkdown(answer: string): string {
const trimmedAnswer = answer
.trim()
.replace(/[ \t]+/g, ' ')
.replace(/\n[ \t]+/g, '\n')
.replace(/\n+/g, '\n');
// targeting links in plain text.
const processedAnswer = trimmedAnswer
.replace(/(\[(.*?)]\((.*?)\))|https?:\/\/\S+/g, (match, fullMarkdownLink, _, __) =>
fullMarkdownLink ? match : `[${match}](${match})`
)
.replace(/https?:\/\/\S+##/g, (url) => url.replace(/##/, '#'));
const formattedAnswer = processedAnswer.replace(
/(^|\n)Sources:/g,
(_, prefix) => `${prefix}\nSources:`
);
let renderedHTML = markdownInstance.render(formattedAnswer);
// add target blank to open links in a new tab.
renderedHTML = renderedHTML.replace(/<a\s+href="([^"]+)"/g, '<a href="$1" target="_blank"');
return renderedHTML;
}
function getInitials(name: string) {
const [first, last] = name.split(' ');
return `${first?.[0] ?? ''}${last?.[0] ?? ''}`;
}
let previousQuestion = '';
$: if ($input) {
previousQuestion = $input;
}
$: if (!$isLoading && answer) {
// reset input if answer received.
$input = '';
}
</script>
<Template
@@ -142,7 +180,7 @@
<div class="content">
<div class="u-flex u-gap-8 u-cross-center">
<div class="avatar is-size-x-small">{getInitials($user.name)}</div>
<p class="u-opacity-75">{question}</p>
<p class="u-opacity-75">{previousQuestion}</p>
</div>
<div class="u-flex u-gap-8 u-margin-block-start-24">
<div class="logo">
@@ -154,7 +192,7 @@
{:else}
{#each answer as part}
{#if part.type === 'text'}
<p>{part.value.trimStart()}</p>
<p>{@html renderMarkdown(part.value.trim())}</p>
{:else if part.type === 'code'}
{#key part.value}
<div
@@ -196,7 +234,6 @@
class="input-text-wrapper u-width-full-line"
style="--amount-of-buttons: 1;"
on:submit|preventDefault={(e) => {
question = $input;
handleSubmit(e);
}}>
<!-- svelte-ignore a11y-autofocus -->
@@ -269,10 +306,20 @@
.answer {
overflow: hidden;
p {
p:first-of-type {
white-space: pre-wrap;
}
}
:global(.answer ul),
:global(.answer ol) {
gap: 1rem;
display: grid;
}
:global(.answer a) {
text-decoration: underline;
}
}
.footer {