Skip to content

Commit e4bc396

Browse files
author
Mishig
authored
[widgets] Conversation widget render markdown (#657)
### Description Make conversational widget render markdown. Pretty much cop paste of [chat-ui implementation here](https://github.com/huggingface/chat-ui/blob/4805941429bb3dccbf95944b469c733cfe81f708/src/lib/components/chat/ChatMessage.svelte#L221) with a difference: 1. rendering code in `<div class="font-mono whitespace-pre-wrap">` rather than specific CodeBlock.svelte (that has copy-code feature) 2. No support for latex in this PR. Should I add it? | main | this PR | | -------- | ------- | |<img src="https://github.com/huggingface/huggingface.js/assets/11827707/af35cc4e-a75c-426b-8425-c7afde3ef692"> | <img src="https://github.com/huggingface/huggingface.js/assets/11827707/da1ca4d9-4ef3-4da8-9112-16013d7cbdad"> | ### testing locally `pnpm install` to get `marked` (new dep; fyi, we also use marked for chat-ui & moon)
1 parent ab84639 commit e4bc396

File tree

5 files changed

+70
-8
lines changed

5 files changed

+70
-8
lines changed

packages/widgets/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@
4545
"static/audioProcessor.js"
4646
],
4747
"dependencies": {
48-
"@huggingface/tasks": "workspace:^",
48+
"@huggingface/inference": "workspace:^",
4949
"@huggingface/jinja": "workspace:^",
50-
"@huggingface/inference": "workspace:^"
50+
"@huggingface/tasks": "workspace:^",
51+
"marked": "^12.0.2"
5152
},
5253
"peerDependencies": {
5354
"svelte": "^3.59.2"

packages/widgets/pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,64 @@
11
<script lang="ts">
2+
import type { SpecialTokensMap } from "@huggingface/tasks";
3+
import { marked, type MarkedOptions } from "marked";
4+
25
export let position: "left" | "right" | "center";
36
export let text: string;
7+
export let specialTokensMap: SpecialTokensMap | undefined = undefined;
8+
9+
$: tokens = marked.lexer(sanitizeMd(text));
410
511
const CLASSES: Record<typeof position, string> = {
612
left: "rounded-2xl mr-7 place-self-start bg-gray-50 dark:bg-gray-850 dark:text-gray-200",
713
right: "rounded-2xl ml-7 bg-blue-600 text-white",
814
center: "self-center justify-center border-b text-center pb-4",
915
};
16+
17+
function sanitizeMd(md: string) {
18+
const PUBLIC_SEP_TOKEN = "</s>";
19+
let ret = md
20+
.replace(/<\|[a-z]*$/, "")
21+
.replace(/<\|[a-z]+\|$/, "")
22+
.replace(/<$/, "")
23+
.replaceAll(PUBLIC_SEP_TOKEN, " ")
24+
.replaceAll(/<\|[a-z]+\|>/g, " ")
25+
.replaceAll(/<br\s?\/?>/gi, "\n")
26+
.replaceAll("<", "&lt;")
27+
.trim();
28+
29+
const stopTokens = ["<|endoftext|>"];
30+
if (typeof specialTokensMap?.eos_token === "string") {
31+
stopTokens.push(specialTokensMap.eos_token);
32+
}
33+
for (const stop of stopTokens) {
34+
if (ret.endsWith(stop)) {
35+
ret = ret.slice(0, -stop.length).trim();
36+
}
37+
}
38+
39+
return ret;
40+
}
41+
42+
const renderer = new marked.Renderer();
43+
renderer.codespan = (code) => `<div class="font-mono">${code.replaceAll("&amp;", "&")}</div>`;
44+
renderer.code = (code) => `<div class="font-mono whitespace-pre-wrap">${code.replaceAll("&amp;", "&")}</div>`;
45+
46+
renderer.link = (href, title, text) =>
47+
`<a href="${href?.replace(/>$/, "")}" target="_blank" rel="noreferrer">${text}</a>`;
48+
49+
const { extensions, ...defaults } = marked.getDefaults() as MarkedOptions & {
50+
extensions: any;
51+
};
52+
const options: MarkedOptions = {
53+
...defaults,
54+
gfm: true,
55+
breaks: true,
56+
renderer,
57+
};
1058
</script>
1159

1260
<div class="px-3 py-2 text-smd {CLASSES[position]}">
13-
{text}
61+
{#each tokens as token}
62+
{@html marked.parse(token.raw, options)}
63+
{/each}
1464
</div>

packages/widgets/src/lib/components/InferenceWidget/shared/WidgetOutputConvo/WidgetOutputConvo.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
44
import { isFullyScrolled, scrollToMax } from "../../../../utils/ViewUtils.js";
55
import WidgetOutputConvoBubble from "../WidgetOuputConvoBubble/WidgetOutputConvoBubble.svelte";
6-
import type { ChatMessage } from "@huggingface/tasks";
6+
import type { ChatMessage, SpecialTokensMap } from "@huggingface/tasks";
77
import { widgetStates } from "../../stores.js";
88
99
export let modelId: string;
1010
export let messages: ChatMessage[];
11+
export let specialTokensMap: SpecialTokensMap | undefined = undefined;
1112
1213
let wrapperEl: HTMLElement;
1314
$: isMaximized = $widgetStates?.[modelId]?.isMaximized;
@@ -30,7 +31,7 @@
3031
<div class="flex flex-col items-end space-y-4 p-3">
3132
{#each messages as message}
3233
{@const position = message.role === "user" ? "right" : message.role === "assistant" ? "left" : "center"}
33-
<WidgetOutputConvoBubble {position} text={message.content} />
34+
<WidgetOutputConvoBubble {position} {specialTokensMap} text={message.content} />
3435
{/each}
3536
</div>
3637
</div>

packages/widgets/src/lib/components/InferenceWidget/widgets/ConversationalWidget/ConversationalWidget.svelte

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
4949
let compiledTemplate: Template;
5050
let tokenizerConfig: TokenizerConfig;
51+
let specialTokensMap: SpecialTokensMap | undefined = undefined;
5152
let inferenceClient: HfInference | undefined = undefined;
5253
let abort: AbortController | undefined = undefined;
5354
@@ -132,14 +133,14 @@
132133
return;
133134
}
134135
// Render chat template
135-
const special_tokens_map = extractSpecialTokensMap(tokenizerConfig);
136+
specialTokensMap = extractSpecialTokensMap(tokenizerConfig);
136137
137138
let chatText;
138139
try {
139140
chatText = compiledTemplate.render({
140141
messages,
141142
add_generation_prompt: true,
142-
...special_tokens_map,
143+
...specialTokensMap,
143144
});
144145
} catch (e) {
145146
error = `An error occurred while rendering the chat template: "${(e as Error).message}"`;
@@ -273,7 +274,7 @@
273274
on:reset={clearConversation}
274275
showReset={!!messages.length}
275276
/>
276-
<WidgetOutputConvo modelId={model.id} {messages} />
277+
<WidgetOutputConvo modelId={model.id} {messages} {specialTokensMap} />
277278

278279
<WidgetQuickInput
279280
bind:value={text}

0 commit comments

Comments
 (0)