Просмотр исходного кода

Merge pull request #11699 from shaun-gallagher-octane/markdown-alert-renderer

feat: Markdown alert renderer
Timothy Jaeryang Baek 1 месяц назад
Родитель
Сommit
f1a84f45c8

+ 116 - 0
src/lib/components/chat/Messages/Markdown/AlertRenderer.svelte

@@ -0,0 +1,116 @@
+<script lang="ts" context="module">
+	import { marked, type Token } from 'marked';
+
+    type AlertType = "NOTE" | "TIP" | "IMPORTANT" | "WARNING" | "CAUTION";
+
+    interface AlertTheme {
+        border: string;
+        text: string;
+        icon: ComponentType;
+    }
+
+    export interface AlertData {
+        type: AlertType;
+        text: string;
+        tokens: Token[];
+    }
+
+    const alertStyles: Record<AlertType, AlertTheme> = {
+        "NOTE": {
+            border: "border-sky-500",
+            text: "text-sky-500",
+            icon: Info
+        },
+        "TIP": {
+            border: "border-emerald-500",
+            text: "text-emerald-500",
+            icon: LightBlub
+        },
+        "IMPORTANT": {
+            border: "border-purple-500",
+            text: "text-purple-500",
+            icon: Star
+        },
+        "WARNING": {
+            border: "border-yellow-500",
+            text: "text-yellow-500",
+            icon: ArrowRightCircle
+        },
+        "CAUTION": {
+            border: "border-rose-500",
+            text: "text-rose-500",
+            icon: Bolt
+        }
+    };
+
+    export function alertComponent(token: Token): AlertData | false {
+        const regExpStr = `^(?:\\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\])\\s*?\n*`;
+        const regExp = new RegExp(regExpStr);
+        const matches = token.text?.match(regExp);
+
+        if (matches && matches.length) {
+            const alertType = matches[1] as AlertType;
+            const newText = token.text.replace(regExp, '');
+            const newTokens = marked.lexer(newText);
+            return {
+                type: alertType,
+                text: newText,
+                tokens: newTokens
+            };
+        }
+        return false;
+    }
+</script>
+
+<script lang="ts">
+    import Info from '$lib/components/icons/Info.svelte';
+    import Star from '$lib/components/icons/Star.svelte';
+    import LightBlub from '$lib/components/icons/LightBlub.svelte';
+    import Bolt from '$lib/components/icons/Bolt.svelte';
+    import ArrowRightCircle from '$lib/components/icons/ArrowRightCircle.svelte';
+    import MarkdownTokens from './MarkdownTokens.svelte';
+    import type { ComponentType } from 'svelte';
+
+    export let token: Token;
+    export let alert: AlertData;
+    export let id = '';
+    export let tokenIdx = 0;
+    export let onTaskClick: ((event: MouseEvent) => void) | undefined = undefined;
+    export let onSourceClick: ((event: MouseEvent) => void) | undefined = undefined;
+</script>
+
+<!--
+
+Renders the following Markdown as alerts:
+
+> [!NOTE]
+> Example note
+
+> [!TIP]
+> Example tip
+
+> [!IMPORTANT]
+> Example important
+
+> [!CAUTION]
+> Example caution
+
+> [!WARNING]
+> Example warning
+
+-->
+<div class={`border-l-2 pl-2 ${alertStyles[alert.type].border}`}>
+    <p class={alertStyles[alert.type].text}>
+        <svelte:component
+            this={alertStyles[alert.type].icon}
+            className="inline-block size-4"
+        />
+        <b>{alert.type}</b>
+    </p>
+    <MarkdownTokens
+        id={`${id}-${tokenIdx}`}
+        tokens={alert.tokens}
+        {onTaskClick}
+        {onSourceClick}
+    />
+</div>

+ 10 - 3
src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte

@@ -14,9 +14,11 @@
 	import CodeBlock from '$lib/components/chat/Messages/CodeBlock.svelte';
 	import MarkdownInlineTokens from '$lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte';
 	import KatexRenderer from './KatexRenderer.svelte';
+	import AlertRenderer, { alertComponent } from './AlertRenderer.svelte';
 	import Collapsible from '$lib/components/common/Collapsible.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
+
 	import Source from './Source.svelte';
 	import { settings } from '$lib/stores';
 
@@ -172,9 +174,14 @@
 			</div>
 		</div>
 	{:else if token.type === 'blockquote'}
-		<blockquote dir="auto">
-			<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} {onTaskClick} {onSourceClick} />
-		</blockquote>
+		{@const alert = alertComponent(token)}
+		{#if alert}
+			<AlertRenderer token={token} alert={alert} />
+		{:else}
+			<blockquote dir="auto">
+				<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} {onTaskClick} {onSourceClick} />
+			</blockquote>
+		{/if}
 	{:else if token.type === 'list'}
 		{#if token.ordered}
 			<ol start={token.start || 1}>