Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using marked for chatbot markdown parsing #4150

Merged
merged 15 commits into from
May 18, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

## Other Changes:

- Change `gr.Chatbot()` markdown parsing to frontend using `marked` library and `prism` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4150](https://github.com/gradio-app/gradio/pull/4150)
- Update the js client by [@pngwn](https://github.com/pngwn) in [PR 3899](https://github.com/gradio-app/gradio/pull/3899)
- Fix documentation for the shape of the numpy array produced by the `Image` component by [@der3318](https://github.com/der3318) in [PR 4204](https://github.com/gradio-app/gradio/pull/4204).
- Updates the timeout for websocket messaging from 1 second to 5 seconds by [@abidlabs](https://github.com/abidlabs) in [PR 4235](https://github.com/gradio-app/gradio/pull/4235)
Expand Down
4 changes: 0 additions & 4 deletions gradio/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -4526,7 +4526,6 @@ def __init__(
warnings.warn(
"The 'color_map' parameter has been deprecated.",
)
self.md = utils.get_markdown_parser()
self.select: EventListenerMethod
"""
Event listener for when the user selects message from Chatbot.
Expand Down Expand Up @@ -4628,9 +4627,6 @@ def _postprocess_chat_messages(
}
elif isinstance(chat_message, str):
chat_message = inspect.cleandoc(chat_message)
chat_message = cast(str, self.md.render(chat_message))
if chat_message.startswith("<p>") and chat_message.endswith("</p>\n"):
chat_message = chat_message[3:-5]
return chat_message
else:
raise ValueError(f"Invalid message for Chatbot component: {chat_message}")
Expand Down
14 changes: 0 additions & 14 deletions gradio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@
from mdit_py_plugins.dollarmath.index import dollarmath_plugin
from mdit_py_plugins.footnote.index import footnote_plugin
from pydantic import BaseModel, parse_obj_as
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name

import gradio
from gradio.context import Context
Expand Down Expand Up @@ -890,16 +887,6 @@ def get_class_that_defined_method(meth: Callable):
return cls.__name__


def highlight_code(code, name, attrs):
try:
lexer = get_lexer_by_name(name)
except Exception:
lexer = get_lexer_by_name("text")
formatter = HtmlFormatter()

return highlight(code, lexer, formatter)


def get_markdown_parser() -> MarkdownIt:
md = (
MarkdownIt(
Expand All @@ -908,7 +895,6 @@ def get_markdown_parser() -> MarkdownIt:
"linkify": True,
"typographer": True,
"html": True,
"highlight": highlight_code,
},
)
.use(dollarmath_plugin, renderer=tex2svg, allow_digits=False)
Expand Down
3 changes: 3 additions & 0 deletions js/app/src/components/Chatbot/Chatbot.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Block, BlockLabel } from "@gradio/atoms";
import type { LoadingStatus } from "../StatusTracker/types";
import type { Styles } from "@gradio/utils";
import type { ThemeMode } from "js/app/src/components/types";
import { Chat } from "@gradio/icons";
import type { FileData } from "@gradio/upload";
import { normalise_file } from "@gradio/upload";
Expand All @@ -20,6 +21,7 @@
export let root: string;
export let root_url: null | string;
export let selectable: boolean = false;
export let theme_mode: ThemeMode;

const redirect_src_url = (src: string) =>
src.replace('src="/file', `src="${root}file`);
Expand Down Expand Up @@ -50,6 +52,7 @@
<ChatBot
{style}
{selectable}
{theme_mode}
value={_value}
pending_message={loading_status?.status === "pending"}
on:change
Expand Down
7 changes: 6 additions & 1 deletion js/chatbot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
"dependencies": {
"@gradio/theme": "workspace:^0.0.1",
"@gradio/utils": "workspace:^0.0.1",
"@gradio/upload": "workspace:^0.0.1"
"@gradio/upload": "workspace:^0.0.1",
"@gradio/icons": "workspace:^0.0.1",
"marked": "^5.0.1",
"@types/marked": "^4.3.0",
"prismjs": "1.29.0",
"@types/prismjs": "1.26.0"
}
}
115 changes: 85 additions & 30 deletions js/chatbot/src/ChatBot.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
<script lang="ts">
import { marked } from "marked";
import Prism from "prismjs";
import "prismjs/components/prism-python";
dawoodkhan82 marked this conversation as resolved.
Show resolved Hide resolved
import { beforeUpdate, afterUpdate, createEventDispatcher } from "svelte";
import type { Styles, SelectData } from "@gradio/utils";
import type { ThemeMode } from "js/app/src/components/types";
import type { FileData } from "@gradio/upload";
import "./manni.css";
import Copy from "./Copy.svelte";

const code_highlight_css = {
light: () => import("prismjs/themes/prism.css"),
dark: () => import("prismjs/themes/prism-dark.css")
};

export let value: Array<
[string | FileData | null, string | FileData | null]
Expand All @@ -14,6 +23,32 @@
export let feedback: Array<string> | null = null;
export let style: Styles = {};
export let selectable: boolean = false;
export let theme_mode: ThemeMode;

$: if (theme_mode == "dark") {
code_highlight_css.dark();
} else {
code_highlight_css.light();
}
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
breaks: true,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});

marked.setOptions({
highlight: (code: string, lang: string) => {
if (Prism.languages[lang]) {
return Prism.highlight(code, Prism.languages[lang], lang);
} else {
return code;
}
}
});

let div: HTMLDivElement;
let autoscroll: Boolean;
Expand All @@ -39,28 +74,22 @@
}
div.querySelectorAll("pre > code").forEach((n) => {
let code_node = n as HTMLElement;
const copy_div = document.createElement("div");
new Copy({
target: copy_div,
props: {
value: code_node.innerText.trimEnd()
}
});
let node = n.parentElement as HTMLElement;
copy_div.style.position = "absolute";
copy_div.style.right = "0";
copy_div.style.top = "0";
copy_div.style.zIndex = "1";
copy_div.style.padding = "var(--spacing-md)";
copy_div.style.borderBottomLeftRadius = "var(--radius-sm)";
node.style.position = "relative";
const button = document.createElement("button");
button.className = "copy-button";
button.innerHTML = "Copy";
button.style.position = "absolute";
button.style.right = "0";
button.style.top = "0";
button.style.zIndex = "1";
button.style.padding = "var(--spacing-md)";
button.style.marginTop = "12px";
button.style.fontSize = "var(--text-sm)";
button.style.borderBottomLeftRadius = "var(--radius-sm)";
button.style.backgroundColor = "var(--block-label-background-fill)";
button.addEventListener("click", () => {
navigator.clipboard.writeText(code_node.innerText.trimEnd());
button.innerHTML = "Copied!";
setTimeout(() => {
button.innerHTML = "Copy";
}, 1000);
});
node.appendChild(button);
node.appendChild(copy_div);
});
});

Expand Down Expand Up @@ -96,7 +125,7 @@
})}
>
{#if typeof message === "string"}
{@html message}
{@html marked.parse(message)}
{#if feedback && j == 1}
<div class="feedback">
{#each feedback as f}
Expand Down Expand Up @@ -184,6 +213,7 @@
.user {
align-self: flex-end;
border-bottom-right-radius: 0;
padding-left: calc(2 * var(--spacing-xxl));
}
.bot {
border-bottom-left-radius: 0;
Expand Down Expand Up @@ -247,13 +277,6 @@
.dot-flashing:nth-child(3) {
animation-delay: 0.66s;
}
.message-wrap > div :global(.highlight) {
margin-top: var(--spacing-xs);
margin-bottom: var(--spacing-xs);
border-radius: var(--radius-md);
background: var(--chatbot-code-background-color);
padding-left: var(--spacing-xxl);
}

/* Small screen */
@media (max-width: 480px) {
Expand Down Expand Up @@ -290,7 +313,39 @@
display: none;
}

/* Code blocks */
.message-wrap :global(pre[class*="language-"]),
.message-wrap :global(pre) {
padding: var(--spacing-xl) 0px;
margin-top: var(--spacing-sm);
margin-bottom: var(--spacing-sm);
box-shadow: none;
border: none;
border-radius: var(--radius-md);
background-color: var(--chatbot-code-background-color);
padding: var(--spacing-xl) 10px;
}

/* Tables */
.message-wrap :global(table),
.message-wrap :global(tr),
.message-wrap :global(td),
.message-wrap :global(th) {
margin-top: var(--spacing-sm);
margin-bottom: var(--spacing-sm);
padding: var(--spacing-xl);
}

.message-wrap .bot :global(table),
.message-wrap .bot :global(tr),
.message-wrap .bot :global(td),
.message-wrap .bot :global(th) {
border: 1px solid var(--border-color-primary);
}

.message-wrap .user :global(table),
.message-wrap .user :global(tr),
.message-wrap .user :global(td),
.message-wrap .user :global(th) {
border: 1px solid var(--border-color-accent);
}
</style>
59 changes: 59 additions & 0 deletions js/chatbot/src/Copy.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script lang="ts">
import { onDestroy } from "svelte";
import { fade } from "svelte/transition";
import { Copy, Check } from "@gradio/icons";

let copied = false;
export let value: string;
let timer: NodeJS.Timeout;

function copy_feedback() {
copied = true;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
copied = false;
}, 2000);
}

async function handle_copy() {
if ("clipboard" in navigator) {
await navigator.clipboard.writeText(value);
copy_feedback();
}
}

onDestroy(() => {
if (timer) clearTimeout(timer);
});
</script>

<button on:click={handle_copy} title="copy">
<!-- {#if !copied} -->
<span class="copy-text" class:copied><Copy /> </span>
<!-- {/if} -->
{#if copied}
<span class="check" transition:fade><Check /></span>
{/if}
</button>

<style>
button {
position: relative;
cursor: pointer;
padding: 5px;
width: 22px;
height: 22px;
}

.check {
position: absolute;
top: 0;
right: 0;
z-index: var(--layer-top);
background: var(--background-fill-primary);
padding: var(--size-1);
width: 100%;
height: 100%;
color: var(--body-text-color);
}
</style>
Loading