Repository: gradio-app/gradio
Pull request: Chatbot code syntax highlighting
# Description
Add code syntax highlighting to chatbot.

Opening this PR for review, but there's still some minor css styling to fix, and probably some bugs.

![Screenshot 2023-05-03 at 2 03 55 AM](https://user-images.githubusercontent.com/12725292/235842450-7d354476-3677-413c-97bb-23064d2e0c49.png)


Please include: 
* relevant motivation
* a summary of the change 
* which issue is fixed. 
* any additional dependencies that are required for this change.

Closes: #3831

# Checklist:

- [ ] I have performed a self-review of my own code
- [ ] I have added a short summary of my change to the CHANGELOG.md
- [ ] My code follows the style guidelines of this project
- [ ] I have commented my code in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes


# A note about the CHANGELOG

Hello 👋 and thank you for contributing to Gradio!

All pull requests must update the change log located in CHANGELOG.md, unless the pull request is labeled with the "no-changelog-update" label.

Please add a brief summary of the change to the Upcoming Release > Full Changelog section of the CHANGELOG.md file and include
a link to the PR (formatted in markdown) and a link to your github profile (if you like). For example, "* Added a cool new feature by `[@myusername](link-to-your-github-profile)` in `[PR 11111](https://github.com/gradio-app/gradio/pull/11111)`".

If you would like to elaborate on your change further, feel free to include a longer explanation in the other sections.
If you would like an image/gif/video showcasing your feature, it may be best to edit the CHANGELOG file using the 
GitHub web UI since that lets you upload files directly via drag-and-drop.

Problem statement:
Better code blocks support in chatbot component
- [x] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**  
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**  
- Use `gr.Code()` component within chatbot component:
 - include syntax highlighting
 - add frame around code block
 - add copy button

**Additional context**  
Add any other context or screenshots about the feature request here.

Hints from issue discussion:
I think if we can find a way to dynamically load the code component that would be best. Rather than re-implementing a worse version of the code component specifically for the chatbot. Plus there seems to be a lot of chatbot demos outputting code blocks.

Commit message: version

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1f2ace789..2c5422ebac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
 
 ## Bug Fixes:
 
diff --git a/gradio/components.py b/gradio/components.py
index 0b5c70a138..a77b6a8ca3 100644
--- a/gradio/components.py
+++ b/gradio/components.py
@@ -4590,12 +4590,8 @@ def _postprocess_chat_messages(
                 "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)
+            return self.md.render(chat_message)
         else:
             raise ValueError(f"Invalid message for Chatbot component: {chat_message}")
 
diff --git a/gradio/utils.py b/gradio/utils.py
index ac9400cbe8..8a6f49bf51 100644
--- a/gradio/utils.py
+++ b/gradio/utils.py
@@ -41,6 +41,9 @@
 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
@@ -994,6 +997,19 @@ def get_class_that_defined_method(meth: Callable):
         return cls.__name__
 
 
+def highlight_code(code, name, attrs):
+    if attrs:
+        pass
+
+    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(
@@ -1002,7 +1018,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)
diff --git a/js/chatbot/src/ChatBot.svelte b/js/chatbot/src/ChatBot.svelte
index 71c617f613..d2fcbd2113 100644
--- a/js/chatbot/src/ChatBot.svelte
+++ b/js/chatbot/src/ChatBot.svelte
@@ -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]
@@ -56,6 +57,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}
@@ -217,6 +219,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(--background-fill-primary);
+		padding: var(--size-1);
+	}
 
 	/* Small screen */
 	@media (max-width: 480px) {
diff --git a/js/chatbot/src/manni.css b/js/chatbot/src/manni.css
new file mode 100644
index 0000000000..26739ff2b7
--- /dev/null
+++ b/js/chatbot/src/manni.css
@@ -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 */
diff --git a/requirements.txt b/requirements.txt
index bdf75ec5e9..4f1439c402 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -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


Code review comments:
diff_hunk: @@ -994,6 +997,19 @@ def get_class_that_defined_method(meth: Callable):
         return cls.__name__
 
 
+def highlight_code(code, name, attrs):
+    if attrs:
+        pass
+
path: gradio/utils.py
text: @user1:
Not needed?
```suggestion
```