Spaces:
Running
Running
Richard
commited on
Commit
·
a43df2a
1
Parent(s):
56c5f7a
Minor improvements
Browse files- Improve snackbar with pointer_events css and async action
- Use markedjs instead of me.markdown
- .gitignore +3 -0
- components/snackbar.py +2 -0
- eval_table.py +3 -2
- main.py +32 -5
- requirements.txt +1 -1
- state.py +5 -1
- tool_sidebar.py +1 -5
- web_components/__init__.py +15 -0
- web_components/async_action/async_action_component.js +49 -0
- web_components/async_action/async_action_component.py +43 -0
- web_components/copy_to_clipboard/BUILD +14 -0
- web_components/copy_to_clipboard/copy_to_clipboard_component.js +31 -0
- web_components/copy_to_clipboard/copy_to_clipboard_component.py +16 -0
- web_components/markedjs/BUILD +14 -0
- web_components/markedjs/markedjs_component.js +76 -0
- web_components/markedjs/markedjs_component.py +19 -0
.gitignore
CHANGED
@@ -8,3 +8,6 @@ __pycache__
|
|
8 |
|
9 |
# Prompt Tuner App
|
10 |
saved_prompts
|
|
|
|
|
|
|
|
8 |
|
9 |
# Prompt Tuner App
|
10 |
saved_prompts
|
11 |
+
|
12 |
+
# System
|
13 |
+
.DS_Store
|
components/snackbar.py
CHANGED
@@ -38,6 +38,7 @@ def snackbar(
|
|
38 |
height="100%",
|
39 |
overflow_x="auto",
|
40 |
overflow_y="auto",
|
|
|
41 |
position="fixed",
|
42 |
width="100%",
|
43 |
z_index=1000,
|
@@ -64,6 +65,7 @@ def snackbar(
|
|
64 |
padding=me.Padding(top=5, bottom=5, right=5, left=15)
|
65 |
if action_label
|
66 |
else me.Padding.all(15),
|
|
|
67 |
width=300,
|
68 |
)
|
69 |
):
|
|
|
38 |
height="100%",
|
39 |
overflow_x="auto",
|
40 |
overflow_y="auto",
|
41 |
+
pointer_events="none",
|
42 |
position="fixed",
|
43 |
width="100%",
|
44 |
z_index=1000,
|
|
|
65 |
padding=me.Padding(top=5, bottom=5, right=5, left=15)
|
66 |
if action_label
|
67 |
else me.Padding.all(15),
|
68 |
+
pointer_events="auto",
|
69 |
width=300,
|
70 |
)
|
71 |
):
|
eval_table.py
CHANGED
@@ -4,6 +4,7 @@ import hashlib
|
|
4 |
import mesop as me
|
5 |
|
6 |
from state import Prompt
|
|
|
7 |
|
8 |
|
9 |
@me.component
|
@@ -59,12 +60,12 @@ def prompt_eval_table(
|
|
59 |
me.text(str(row_index))
|
60 |
elif row["type"] == "variable":
|
61 |
with me.box(style=_MARKDOWN_BOX_STYLE):
|
62 |
-
|
63 |
elif row["type"] == "model_response":
|
64 |
with me.box(style=_MARKDOWN_BOX_STYLE):
|
65 |
prompt_response = response_map[row["prompt"].version].get(response_key)
|
66 |
if prompt_response and prompt_response[0]["output"]:
|
67 |
-
|
68 |
else:
|
69 |
with me.box(
|
70 |
style=me.Style(
|
|
|
4 |
import mesop as me
|
5 |
|
6 |
from state import Prompt
|
7 |
+
from web_components import markedjs_component
|
8 |
|
9 |
|
10 |
@me.component
|
|
|
60 |
me.text(str(row_index))
|
61 |
elif row["type"] == "variable":
|
62 |
with me.box(style=_MARKDOWN_BOX_STYLE):
|
63 |
+
markedjs_component(example["variables"][row["variable_name"]])
|
64 |
elif row["type"] == "model_response":
|
65 |
with me.box(style=_MARKDOWN_BOX_STYLE):
|
66 |
prompt_response = response_map[row["prompt"].version].get(response_key)
|
67 |
if prompt_response and prompt_response[0]["output"]:
|
68 |
+
markedjs_component(prompt_response[0]["output"])
|
69 |
else:
|
70 |
with me.box(
|
71 |
style=me.Style(
|
main.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import copy
|
2 |
|
3 |
import mesop as me
|
|
|
4 |
|
5 |
import components as mex
|
6 |
import dialogs
|
@@ -10,6 +11,9 @@ from eval_table import prompt_eval_table
|
|
10 |
from tool_sidebar import tool_sidebar
|
11 |
from helpers import find_prompt, parse_variables
|
12 |
from state import State, Prompt
|
|
|
|
|
|
|
13 |
|
14 |
_INSTRUCTIONS = """
|
15 |
- Write your prompt.
|
@@ -23,14 +27,30 @@ _INSTRUCTIONS = """
|
|
23 |
|
24 |
|
25 |
@me.page(
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
)
|
28 |
def app():
|
29 |
state = me.state(State)
|
30 |
|
31 |
-
|
32 |
-
|
|
|
|
|
33 |
)
|
|
|
|
|
|
|
34 |
|
35 |
dialogs.update_title()
|
36 |
dialogs.model_settings()
|
@@ -122,10 +142,10 @@ def app():
|
|
122 |
with me.box(style=me.Style(padding=me.Padding.all(15), overflow_y="scroll")):
|
123 |
if state.response:
|
124 |
with mex.card(title="Response", style=me.Style(overflow_y="hidden")):
|
125 |
-
|
126 |
else:
|
127 |
with mex.card(title="Prompt Tuner Instructions"):
|
128 |
-
|
129 |
else:
|
130 |
# Render eval page
|
131 |
with me.box(style=me.Style(grid_column="1 / -2", overflow_y="scroll")):
|
@@ -269,6 +289,13 @@ def on_select_rating(e: me.SelectSelectionChangeEvent):
|
|
269 |
prompt.responses[int(response_index)]["rating"] = e.value
|
270 |
|
271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
# Style helpers
|
273 |
|
274 |
_STYLE_INVISIBLE_TEXTAREA = me.Style(
|
|
|
1 |
import copy
|
2 |
|
3 |
import mesop as me
|
4 |
+
import mesop.labs as mel
|
5 |
|
6 |
import components as mex
|
7 |
import dialogs
|
|
|
11 |
from tool_sidebar import tool_sidebar
|
12 |
from helpers import find_prompt, parse_variables
|
13 |
from state import State, Prompt
|
14 |
+
from web_components import AsyncAction
|
15 |
+
from web_components import async_action_component
|
16 |
+
from web_components import markedjs_component
|
17 |
|
18 |
_INSTRUCTIONS = """
|
19 |
- Write your prompt.
|
|
|
27 |
|
28 |
|
29 |
@me.page(
|
30 |
+
stylesheets=[
|
31 |
+
# Other themes here: https://www.jsdelivr.com/package/npm/highlight.js?tab=files&path=styles
|
32 |
+
"https://cdn.jsdelivr.net/npm/[email protected]/styles/github-dark.min.css",
|
33 |
+
"https://cdn.jsdelivr.net/npm/[email protected]/styles/github.min.css",
|
34 |
+
],
|
35 |
+
security_policy=me.SecurityPolicy(
|
36 |
+
allowed_script_srcs=[
|
37 |
+
"https://cdn.jsdelivr.net",
|
38 |
+
],
|
39 |
+
dangerously_disable_trusted_types=True,
|
40 |
+
allowed_iframe_parents=["https://huggingface.co"],
|
41 |
+
),
|
42 |
)
|
43 |
def app():
|
44 |
state = me.state(State)
|
45 |
|
46 |
+
action = (
|
47 |
+
AsyncAction(value=state.async_action_name, duration_seconds=state.async_action_duration)
|
48 |
+
if state.async_action_name
|
49 |
+
else None
|
50 |
)
|
51 |
+
async_action_component(action=action, on_finished=on_async_action_finished)
|
52 |
+
|
53 |
+
mex.snackbar(is_visible=state.show_snackbar, label=state.snackbar_message)
|
54 |
|
55 |
dialogs.update_title()
|
56 |
dialogs.model_settings()
|
|
|
142 |
with me.box(style=me.Style(padding=me.Padding.all(15), overflow_y="scroll")):
|
143 |
if state.response:
|
144 |
with mex.card(title="Response", style=me.Style(overflow_y="hidden")):
|
145 |
+
markedjs_component(state.response)
|
146 |
else:
|
147 |
with mex.card(title="Prompt Tuner Instructions"):
|
148 |
+
markedjs_component(_INSTRUCTIONS)
|
149 |
else:
|
150 |
# Render eval page
|
151 |
with me.box(style=me.Style(grid_column="1 / -2", overflow_y="scroll")):
|
|
|
289 |
prompt.responses[int(response_index)]["rating"] = e.value
|
290 |
|
291 |
|
292 |
+
def on_async_action_finished(e: mel.WebEvent):
|
293 |
+
state = me.state(State)
|
294 |
+
state.async_action_name = ""
|
295 |
+
state.snackbar_message = ""
|
296 |
+
state.show_snackbar = False
|
297 |
+
|
298 |
+
|
299 |
# Style helpers
|
300 |
|
301 |
_STYLE_INVISIBLE_TEXTAREA = me.Style(
|
requirements.txt
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
gunicorn
|
2 |
-
mesop==0.
|
3 |
google-generativeai
|
|
|
1 |
gunicorn
|
2 |
+
mesop==0.11.1
|
3 |
google-generativeai
|
state.py
CHANGED
@@ -59,6 +59,10 @@ class State:
|
|
59 |
# Eval comparisons
|
60 |
comparisons: list[int]
|
61 |
|
|
|
62 |
show_snackbar: bool = False
|
63 |
snackbar_message: str = ""
|
64 |
-
|
|
|
|
|
|
|
|
59 |
# Eval comparisons
|
60 |
comparisons: list[int]
|
61 |
|
62 |
+
# Snackbar
|
63 |
show_snackbar: bool = False
|
64 |
snackbar_message: str = ""
|
65 |
+
|
66 |
+
# Async action (for snackbar)
|
67 |
+
async_action_name: str
|
68 |
+
async_action_duration: int = 4
|
tool_sidebar.py
CHANGED
@@ -96,11 +96,7 @@ def on_click_download(e: me.ClickEvent):
|
|
96 |
|
97 |
state.snackbar_message = f"Prompt exported as {filename}."
|
98 |
state.show_snackbar = True
|
99 |
-
|
100 |
-
time.sleep(state.snackbar_duration)
|
101 |
-
yield
|
102 |
-
state.show_snackbar = False
|
103 |
-
yield
|
104 |
|
105 |
|
106 |
def _clean_title(title: str) -> str:
|
|
|
96 |
|
97 |
state.snackbar_message = f"Prompt exported as {filename}."
|
98 |
state.show_snackbar = True
|
99 |
+
state.async_action_name = "hide_snackbar"
|
|
|
|
|
|
|
|
|
100 |
|
101 |
|
102 |
def _clean_title(title: str) -> str:
|
web_components/__init__.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from web_components.async_action.async_action_component import (
|
2 |
+
async_action_component as async_action_component,
|
3 |
+
)
|
4 |
+
|
5 |
+
from web_components.async_action.async_action_component import (
|
6 |
+
AsyncAction as AsyncAction,
|
7 |
+
)
|
8 |
+
|
9 |
+
from web_components.copy_to_clipboard.copy_to_clipboard_component import (
|
10 |
+
copy_to_clipboard_component as copy_to_clipboard_component,
|
11 |
+
)
|
12 |
+
|
13 |
+
from web_components.markedjs.markedjs_component import (
|
14 |
+
markedjs_component as markedjs_component,
|
15 |
+
)
|
web_components/async_action/async_action_component.js
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
LitElement,
|
3 |
+
html,
|
4 |
+
} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
|
5 |
+
|
6 |
+
class AsyncAction extends LitElement {
|
7 |
+
static properties = {
|
8 |
+
startedEvent: {type: String},
|
9 |
+
finishedEvent: {type: String},
|
10 |
+
// Storing as string due to https://github.com/google/mesop/issues/730
|
11 |
+
// Format: {action: String, duration_seconds: Number}
|
12 |
+
action: {type: String},
|
13 |
+
isRunning: {type: Boolean},
|
14 |
+
};
|
15 |
+
|
16 |
+
render() {
|
17 |
+
return html`<div></div>`;
|
18 |
+
}
|
19 |
+
|
20 |
+
firstUpdated() {
|
21 |
+
if (this.action) {
|
22 |
+
this.runTimeout(this.action);
|
23 |
+
}
|
24 |
+
}
|
25 |
+
|
26 |
+
updated(changedProperties) {
|
27 |
+
if (changedProperties.has('action') && this.action) {
|
28 |
+
this.runTimeout(this.action);
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
runTimeout(actionJson) {
|
33 |
+
const action = JSON.parse(actionJson);
|
34 |
+
this.dispatchEvent(
|
35 |
+
new MesopEvent(this.startedEvent, {
|
36 |
+
action: action,
|
37 |
+
}),
|
38 |
+
);
|
39 |
+
setTimeout(() => {
|
40 |
+
this.dispatchEvent(
|
41 |
+
new MesopEvent(this.finishedEvent, {
|
42 |
+
action: action.value,
|
43 |
+
}),
|
44 |
+
);
|
45 |
+
}, action.duration_seconds * 1000);
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
customElements.define('async-action-component', AsyncAction);
|
web_components/async_action/async_action_component.py
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from dataclasses import asdict, dataclass
|
3 |
+
from typing import Any, Callable
|
4 |
+
|
5 |
+
import mesop.labs as mel
|
6 |
+
|
7 |
+
|
8 |
+
@dataclass
|
9 |
+
class AsyncAction:
|
10 |
+
value: str
|
11 |
+
duration_seconds: int
|
12 |
+
|
13 |
+
|
14 |
+
@mel.web_component(path="./async_action_component.js")
|
15 |
+
def async_action_component(
|
16 |
+
*,
|
17 |
+
action: AsyncAction | None = None,
|
18 |
+
on_started: Callable[[mel.WebEvent], Any] | None = None,
|
19 |
+
on_finished: Callable[[mel.WebEvent], Any] | None = None,
|
20 |
+
key: str | None = None,
|
21 |
+
):
|
22 |
+
"""Creates an invisibe component that will delay state changes asynchronously.
|
23 |
+
|
24 |
+
Right now this implementation is limited since we basically just pass the key around.
|
25 |
+
But ideally we also pass in some kind of value to update when the time out expires.
|
26 |
+
|
27 |
+
The main benefit of this component is for cases, such as status messages that may
|
28 |
+
appear and disappear after some duration. The primary example here is the example
|
29 |
+
snackbar widget, which right now blocks the UI when using the sleep yield approach.
|
30 |
+
|
31 |
+
The other benefit of this component is that it works generically (rather than say
|
32 |
+
implementing a custom snackbar widget as a web component).
|
33 |
+
"""
|
34 |
+
events = {
|
35 |
+
"startedEvent": on_started,
|
36 |
+
"finishedEvent": on_finished,
|
37 |
+
}
|
38 |
+
return mel.insert_web_component(
|
39 |
+
name="async-action-component",
|
40 |
+
key=key,
|
41 |
+
events={key: value for key, value in events.items() if value is not None},
|
42 |
+
properties={"action": json.dumps(asdict(action)) if action else ""},
|
43 |
+
)
|
web_components/copy_to_clipboard/BUILD
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
load("//build_defs:defaults.bzl", "py_library")
|
2 |
+
|
3 |
+
package(
|
4 |
+
default_visibility = ["//build_defs:mesop_examples"],
|
5 |
+
)
|
6 |
+
|
7 |
+
py_library(
|
8 |
+
name = "copy_to_clipboard",
|
9 |
+
srcs = glob(["*.py"]),
|
10 |
+
data = glob(["*.js"]),
|
11 |
+
deps = [
|
12 |
+
"//mesop",
|
13 |
+
],
|
14 |
+
)
|
web_components/copy_to_clipboard/copy_to_clipboard_component.js
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
LitElement,
|
3 |
+
html,
|
4 |
+
} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
|
5 |
+
|
6 |
+
class CopyToClipboard extends LitElement {
|
7 |
+
static properties = {
|
8 |
+
text: {type: String},
|
9 |
+
};
|
10 |
+
|
11 |
+
constructor() {
|
12 |
+
super();
|
13 |
+
this.text = '';
|
14 |
+
}
|
15 |
+
|
16 |
+
render() {
|
17 |
+
return html`
|
18 |
+
<div @click="${this._onClick}">
|
19 |
+
<slot></slot>
|
20 |
+
</div>
|
21 |
+
`;
|
22 |
+
}
|
23 |
+
|
24 |
+
_onClick() {
|
25 |
+
navigator.clipboard
|
26 |
+
.writeText(this.text)
|
27 |
+
.catch((err) => console.error('Failed to copy text: ', err));
|
28 |
+
}
|
29 |
+
}
|
30 |
+
|
31 |
+
customElements.define('copy-to-clipboard-component', CopyToClipboard);
|
web_components/copy_to_clipboard/copy_to_clipboard_component.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import mesop.labs as mel
|
2 |
+
|
3 |
+
|
4 |
+
@mel.web_component(path="./copy_to_clipboard_component.js")
|
5 |
+
def copy_to_clipboard_component(
|
6 |
+
*,
|
7 |
+
text: str = "",
|
8 |
+
key: str | None = None,
|
9 |
+
):
|
10 |
+
return mel.insert_web_component(
|
11 |
+
name="copy-to-clipboard-component",
|
12 |
+
key=key,
|
13 |
+
properties={
|
14 |
+
"text": text,
|
15 |
+
},
|
16 |
+
)
|
web_components/markedjs/BUILD
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
load("//build_defs:defaults.bzl", "py_library")
|
2 |
+
|
3 |
+
package(
|
4 |
+
default_visibility = ["//build_defs:mesop_examples"],
|
5 |
+
)
|
6 |
+
|
7 |
+
py_library(
|
8 |
+
name = "markedjs",
|
9 |
+
srcs = glob(["*.py"]),
|
10 |
+
data = glob(["*.js"]),
|
11 |
+
deps = [
|
12 |
+
"//mesop",
|
13 |
+
],
|
14 |
+
)
|
web_components/markedjs/markedjs_component.js
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { html, LitElement } from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
|
2 |
+
import { unsafeHTML } from "https://cdn.jsdelivr.net/npm/[email protected]/directives/unsafe-html.js/+esm";
|
3 |
+
import dompurify from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
|
4 |
+
import { Marked } from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
|
5 |
+
import { markedHighlight } from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
|
6 |
+
import highlightJs from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
|
7 |
+
import mermaid from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Markdown component using MarkedJS
|
11 |
+
*
|
12 |
+
* - HighlightJS used for code highlighting
|
13 |
+
* - Mermaid JS integration for diagrams
|
14 |
+
*/
|
15 |
+
class MarkedJsComponent extends LitElement {
|
16 |
+
static properties = {
|
17 |
+
markdown: { type: String },
|
18 |
+
themeBrightness: { type: String },
|
19 |
+
darkModeTheme: { type: String },
|
20 |
+
lightModeTheme: { type: String },
|
21 |
+
};
|
22 |
+
|
23 |
+
constructor() {
|
24 |
+
super();
|
25 |
+
mermaid.initialize({ startOnLoad: true });
|
26 |
+
this.marked = new Marked(
|
27 |
+
markedHighlight({
|
28 |
+
langPrefix: "hljs language-",
|
29 |
+
highlight(code, lang, info) {
|
30 |
+
if (lang === "mermaid") {
|
31 |
+
return `<div class="mermaid">${code}</div>`;
|
32 |
+
}
|
33 |
+
const language = highlightJs.getLanguage(lang) ? lang : "plaintext";
|
34 |
+
return highlightJs.highlight(code, { language }).value;
|
35 |
+
},
|
36 |
+
})
|
37 |
+
);
|
38 |
+
}
|
39 |
+
|
40 |
+
createRenderRoot() {
|
41 |
+
return this;
|
42 |
+
}
|
43 |
+
|
44 |
+
firstUpdated() {
|
45 |
+
this.darkModeUpdated();
|
46 |
+
}
|
47 |
+
|
48 |
+
updated(changedProperties) {
|
49 |
+
this.darkModeUpdated();
|
50 |
+
}
|
51 |
+
|
52 |
+
darkModeUpdated() {
|
53 |
+
if (this.themeBrightness === "dark") {
|
54 |
+
document
|
55 |
+
.querySelector(`link[href="${this.darkModeTheme}"]`)
|
56 |
+
.removeAttribute("disabled");
|
57 |
+
document
|
58 |
+
.querySelector(`link[href="${this.lightModeTheme}"]`)
|
59 |
+
.setAttribute("disabled", "disabled");
|
60 |
+
} else {
|
61 |
+
document
|
62 |
+
.querySelector(`link[href="${this.darkModeTheme}"]`)
|
63 |
+
.setAttribute("disabled", "disabled");
|
64 |
+
document
|
65 |
+
.querySelector(`link[href="${this.lightModeTheme}"]`)
|
66 |
+
.removeAttribute("disabled");
|
67 |
+
}
|
68 |
+
}
|
69 |
+
render() {
|
70 |
+
return html`${unsafeHTML(
|
71 |
+
dompurify.sanitize(this.marked.parse(this.markdown))
|
72 |
+
)}`;
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
customElements.define("markedjs-component", MarkedJsComponent);
|
web_components/markedjs/markedjs_component.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import mesop as me
|
2 |
+
import mesop.labs as mel
|
3 |
+
|
4 |
+
|
5 |
+
@mel.web_component(path="./markedjs_component.js")
|
6 |
+
def markedjs_component(
|
7 |
+
markdown: str,
|
8 |
+
light_mode_theme: str = "https://cdn.jsdelivr.net/npm/[email protected]/styles/github.min.css",
|
9 |
+
dark_mode_theme: str = "https://cdn.jsdelivr.net/npm/[email protected]/styles/github-dark.min.css",
|
10 |
+
):
|
11 |
+
return mel.insert_web_component(
|
12 |
+
name="markedjs-component",
|
13 |
+
properties={
|
14 |
+
"markdown": markdown,
|
15 |
+
"themeBrightness": me.theme_brightness(),
|
16 |
+
"lightModeTheme": light_mode_theme,
|
17 |
+
"darkModeTheme": dark_mode_theme,
|
18 |
+
},
|
19 |
+
)
|