Chatbot code syntax highlighting (#4048)

* first pass

* fixes

* more fixes

* remove breaks

* format

* version

* pr fixes

* changelog

* test fix

* background color

* format

* revert test fix

* changes

* changes

* test

* changes

* changes

* changes

* changes

* changes

---------

Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
Co-authored-by: Ali Abid <aabid94@gmail.com>
This commit is contained in:
Dawood Khan 2023-05-04 01:28:39 -07:00 committed by GitHub
parent ff21ecbc25
commit c06901ed05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 305 additions and 11 deletions

View File

@ -7,6 +7,7 @@
## New Features:
- Add support for `visual-question-answering`, `document-question-answering`, and `image-to-text` using `gr.Interface.load("models/...")` and `gr.Interface.from_pipeline` by [@osanseviero](https://github.com/osanseviero) in [PR 3887](https://github.com/gradio-app/gradio/pull/3887)
- Add code block support in `gr.Chatbot()`, by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4048](https://github.com/gradio-app/gradio/pull/4048)
- Adds the ability to blocklist filepaths (and also improves the allowlist mechanism) by [@abidlabs](https://github.com/abidlabs) in [PR 4047](https://github.com/gradio-app/gradio/pull/4047).
- Adds the ability to specify the upload directory via an environment variable by [@abidlabs](https://github.com/abidlabs) in [PR 4047](https://github.com/gradio-app/gradio/pull/4047).
@ -27,6 +28,7 @@ No changes to highlight.
## Breaking Changes:
- `gr.HuggingFaceDatasetSaver` behavior changed internally. The `flagging/` folder is not a `.git/` folder anymore when using it. `organization` parameter is now ignored in favor of passing a full dataset id as `dataset_name` (e.g. `"username/my-dataset"`).
- New lines (`\n`) are not automatically converted to `<br>` in `gr.Markdown()` or `gr.Chatbot()`. For multiple new lines, a developer must add multiple `<br>` tags.
## Full Changelog:

View File

@ -20,7 +20,7 @@ from copy import deepcopy
from enum import Enum
from pathlib import Path
from types import ModuleType
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Set, Tuple, Type
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Set, Tuple, Type, cast
import aiofiles
import altair as alt
@ -4593,12 +4593,11 @@ class Chatbot(Changeable, Selectable, IOComponent, JSONSerializable):
"is_file": True,
}
elif isinstance(chat_message, str):
children = self.md.parseInline(chat_message)[0].children
if children and any("code" in child.tag for child in children):
return self.md.render(chat_message)
else:
chat_message = chat_message.replace("\n", "<br>")
return self.md.renderInline(chat_message)
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}")

View File

@ -568,6 +568,8 @@ class Base(ThemeClass):
section_header_text_size=None,
section_header_text_weight=None,
# Component Atoms: These set the style for elements within components.
chatbot_code_background_color=None,
chatbot_code_background_color_dark=None,
checkbox_background_color=None,
checkbox_background_color_dark=None,
checkbox_background_color_focus=None,
@ -800,6 +802,8 @@ class Base(ThemeClass):
panel_border_width_dark: The border width of a panel in dark mode.
section_header_text_size: The text size of a section header (e.g. tab name).
section_header_text_weight: The text weight of a section header (e.g. tab name).
chatbot_code_background_color: The background color of code blocks in the chatbot.
chatbot_code_background_color_dark: The background color of code blocks in the chatbot in dark mode.
checkbox_background_color: The background of a checkbox square or radio circle.
checkbox_background_color_dark: The background of a checkbox square or radio circle in dark mode.
checkbox_background_color_focus: The background of a checkbox square or radio circle when focused.
@ -1201,6 +1205,13 @@ class Base(ThemeClass):
self, "section_header_text_weight", "400"
)
# Component Atoms
self.chatbot_code_background_color = chatbot_code_background_color or getattr(
self, "chatbot_code_background_color", "*neutral_100"
)
self.chatbot_code_background_color_dark = (
chatbot_code_background_color_dark
or getattr(self, "chatbot_code_background_color_dark", "*neutral_800")
)
self.checkbox_background_color = checkbox_background_color or getattr(
self, "checkbox_background_color", "*background_fill_primary"
)

View File

@ -41,6 +41,9 @@ import httpx
import matplotlib
import requests
from markdown_it import MarkdownIt
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name
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
@ -1007,6 +1010,16 @@ def get_serializer_name(block: Block) -> str | None:
return cls.__name__
def highlight_code(code, name, attrs):
try:
lexer = get_lexer_by_name(name)
except:
lexer = get_lexer_by_name("text")
formatter = HtmlFormatter()
return highlight(code, lexer, formatter)
def get_markdown_parser() -> MarkdownIt:
md = (
MarkdownIt(
@ -1015,7 +1028,7 @@ def get_markdown_parser() -> MarkdownIt:
"linkify": True,
"typographer": True,
"html": True,
"breaks": True,
"highlight": highlight_code,
},
)
.use(dollarmath_plugin, renderer=tex2svg, allow_digits=False)

View File

@ -1 +1 @@
3.28.1
3.28.2

View File

@ -2,6 +2,7 @@
import { beforeUpdate, afterUpdate, createEventDispatcher } from "svelte";
import type { Styles, SelectData } from "@gradio/utils";
import type { FileData } from "@gradio/upload";
import "./manni.css";
export let value: Array<
[string | FileData | null, string | FileData | null]
@ -36,6 +37,31 @@
});
});
}
div.querySelectorAll("pre > code").forEach((n) => {
let code_node = n as HTMLElement;
let node = n.parentElement as HTMLElement;
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);
});
});
$: {
@ -56,6 +82,7 @@
{#if value !== null}
{#each value as message_pair, i}
{#each message_pair as message, j}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
data-testid={j == 0 ? "user" : "bot"}
class:latest={i === value.length - 1}
@ -63,7 +90,10 @@
class:hide={message === null}
class:selectable
on:click={() =>
dispatch("select", { index: [i, j], value: message })}
dispatch("select", {
index: [i, j],
value: message
})}
>
{#if typeof message === "string"}
{@html message}
@ -217,6 +247,13 @@
.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) {

231
js/chatbot/src/manni.css Normal file
View File

@ -0,0 +1,231 @@
.highlight .hll {
background-color: #ffffcc;
}
.highlight .c {
color: #0099ff;
font-style: italic;
} /* Comment */
.highlight .err {
background-color: #ffaaaa;
color: #aa0000;
} /* Error */
.highlight .k {
color: #006699;
font-weight: bold;
} /* Keyword */
.highlight .o {
color: #555555;
} /* Operator */
.highlight .ch {
color: #0099ff;
font-style: italic;
} /* Comment.Hashbang */
.highlight .cm {
color: #0099ff;
font-style: italic;
} /* Comment.Multiline */
.highlight .cp {
color: #009999;
} /* Comment.Preproc */
.highlight .cpf {
color: #0099ff;
font-style: italic;
} /* Comment.PreprocFile */
.highlight .c1 {
color: #0099ff;
font-style: italic;
} /* Comment.Single */
.highlight .cs {
color: #0099ff;
font-style: italic;
font-weight: bold;
} /* Comment.Special */
.highlight .gd {
border: 1px solid #cc0000;
background-color: #ffcccc;
} /* Generic.Deleted */
.highlight .ge {
font-style: italic;
} /* Generic.Emph */
.highlight .gr {
color: #ff0000;
} /* Generic.Error */
.highlight .gh {
color: #003300;
font-weight: bold;
} /* Generic.Heading */
.highlight .gi {
border: 1px solid #00cc00;
background-color: #ccffcc;
} /* Generic.Inserted */
.highlight .go {
color: #aaaaaa;
} /* Generic.Output */
.highlight .gp {
color: #000099;
font-weight: bold;
} /* Generic.Prompt */
.highlight .gs {
font-weight: bold;
} /* Generic.Strong */
.highlight .gu {
color: #003300;
font-weight: bold;
} /* Generic.Subheading */
.highlight .gt {
color: #99cc66;
} /* Generic.Traceback */
.highlight .kc {
color: #006699;
font-weight: bold;
} /* Keyword.Constant */
.highlight .kd {
color: #006699;
font-weight: bold;
} /* Keyword.Declaration */
.highlight .kn {
color: #006699;
font-weight: bold;
} /* Keyword.Namespace */
.highlight .kp {
color: #006699;
} /* Keyword.Pseudo */
.highlight .kr {
color: #006699;
font-weight: bold;
} /* Keyword.Reserved */
.highlight .kt {
color: #007788;
font-weight: bold;
} /* Keyword.Type */
.highlight .m {
color: #ff6600;
} /* Literal.Number */
.highlight .s {
color: #cc3300;
} /* Literal.String */
.highlight .na {
color: #330099;
} /* Name.Attribute */
.highlight .nb {
color: #336666;
} /* Name.Builtin */
.highlight .nc {
color: #00aa88;
font-weight: bold;
} /* Name.Class */
.highlight .no {
color: #336600;
} /* Name.Constant */
.highlight .nd {
color: #9999ff;
} /* Name.Decorator */
.highlight .ni {
color: #999999;
font-weight: bold;
} /* Name.Entity */
.highlight .ne {
color: #cc0000;
font-weight: bold;
} /* Name.Exception */
.highlight .nf {
color: #cc00ff;
} /* Name.Function */
.highlight .nl {
color: #9999ff;
} /* Name.Label */
.highlight .nn {
color: #00ccff;
font-weight: bold;
} /* Name.Namespace */
.highlight .nt {
color: #330099;
font-weight: bold;
} /* Name.Tag */
.highlight .nv {
color: #003333;
} /* Name.Variable */
.highlight .ow {
color: #000000;
font-weight: bold;
} /* Operator.Word */
.highlight .w {
color: #bbbbbb;
} /* Text.Whitespace */
.highlight .mb {
color: #ff6600;
} /* Literal.Number.Bin */
.highlight .mf {
color: #ff6600;
} /* Literal.Number.Float */
.highlight .mh {
color: #ff6600;
} /* Literal.Number.Hex */
.highlight .mi {
color: #ff6600;
} /* Literal.Number.Integer */
.highlight .mo {
color: #ff6600;
} /* Literal.Number.Oct */
.highlight .sa {
color: #cc3300;
} /* Literal.String.Affix */
.highlight .sb {
color: #cc3300;
} /* Literal.String.Backtick */
.highlight .sc {
color: #cc3300;
} /* Literal.String.Char */
.highlight .dl {
color: #cc3300;
} /* Literal.String.Delimiter */
.highlight .sd {
color: #cc3300;
font-style: italic;
} /* Literal.String.Doc */
.highlight .s2 {
color: #cc3300;
} /* Literal.String.Double */
.highlight .se {
color: #cc3300;
font-weight: bold;
} /* Literal.String.Escape */
.highlight .sh {
color: #cc3300;
} /* Literal.String.Heredoc */
.highlight .si {
color: #aa0000;
} /* Literal.String.Interpol */
.highlight .sx {
color: #cc3300;
} /* Literal.String.Other */
.highlight .sr {
color: #33aaaa;
} /* Literal.String.Regex */
.highlight .s1 {
color: #cc3300;
} /* Literal.String.Single */
.highlight .ss {
color: #ffcc33;
} /* Literal.String.Symbol */
.highlight .bp {
color: #336666;
} /* Name.Builtin.Pseudo */
.highlight .fm {
color: #cc00ff;
} /* Name.Function.Magic */
.highlight .vc {
color: #003333;
} /* Name.Variable.Class */
.highlight .vg {
color: #003333;
} /* Name.Variable.Global */
.highlight .vi {
color: #003333;
} /* Name.Variable.Instance */
.highlight .vm {
color: #003333;
} /* Name.Variable.Magic */
.highlight .il {
color: #ff6600;
} /* Literal.Number.Integer.Long */

View File

@ -8,6 +8,7 @@ httpx
huggingface_hub>=0.13.0
Jinja2
markdown-it-py[linkify]>=2.0.0
pygments>=2.12.0
mdit-py-plugins<=0.3.3
markupsafe
matplotlib

View File

@ -1775,7 +1775,7 @@ class TestChatbot:
"""
chatbot = gr.Chatbot()
assert chatbot.postprocess([["You are **cool**\nand fun", "so are *you*"]]) == [
["You are <strong>cool</strong><br>and fun", "so are <em>you</em>"]
["You are <strong>cool</strong>\nand fun", "so are <em>you</em>"]
]
multimodal_msg = [