mirror of
https://github.com/gradio-app/gradio.git
synced 2025-04-18 12:50:30 +08:00
delegate gradio events via a custom event dispatcher (#5279)
* delegate gradio events via a custom event dispatcher * improve md perf + share code * fix df markdown * prevent model3d from rerending too frequently * tweaks * fix more event bugs with video * add changeset * optimise handle mount * does this do anything * fix * remove old dispatches * fix dropdown position * oops * fixes * fix tests * fix types * format * fix markdown code * add changeset * fix typecheck * fix typecheck * fix demos * notebooks * fix tests * changer * maybe this * fixes * add changeset * fix chatbot alignment mobile * fix chantbot * add changeset * changeset * changeset * storybook --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
parent
cf167cd1dd
commit
fe057300f0
.changeset
demo
js
_website/src/lib/assets
annotatedimage/static
app
package.json
src
Blocks.svelteIndex.svelteLogin.svelteMountComponents.svelteRender.svelte
components/Dataset
css.tsgradio_helper.tstest
atoms/src
audio
button
chatbot
checkbox
checkboxgroup
code
colorpicker
dataframe
dropdown
file
gallery/static
highlightedtext
html
image
json
label
markdown
package.json
static
model3D
number
plot/static
radio
slider
tabitem/static
tabs/static
textbox
theme/src
timeseries/interactive
tootils/src
upload/src
56
.changeset/chilly-chefs-hug.md
Normal file
56
.changeset/chilly-chefs-hug.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
"@gradio/annotatedimage": patch
|
||||
"@gradio/app": patch
|
||||
"@gradio/atoms": patch
|
||||
"@gradio/audio": patch
|
||||
"@gradio/button": patch
|
||||
"@gradio/chatbot": patch
|
||||
"@gradio/checkbox": patch
|
||||
"@gradio/checkboxgroup": patch
|
||||
"@gradio/code": patch
|
||||
"@gradio/colorpicker": patch
|
||||
"@gradio/dataframe": patch
|
||||
"@gradio/dropdown": patch
|
||||
"@gradio/file": patch
|
||||
"@gradio/gallery": patch
|
||||
"@gradio/highlightedtext": patch
|
||||
"@gradio/html": patch
|
||||
"@gradio/image": patch
|
||||
"@gradio/json": patch
|
||||
"@gradio/label": patch
|
||||
"@gradio/markdown": patch
|
||||
"@gradio/model3d": patch
|
||||
"@gradio/number": patch
|
||||
"@gradio/plot": patch
|
||||
"@gradio/radio": patch
|
||||
"@gradio/slider": patch
|
||||
"@gradio/tabitem": patch
|
||||
"@gradio/tabs": patch
|
||||
"@gradio/textbox": patch
|
||||
"@gradio/theme": patch
|
||||
"@gradio/timeseries": patch
|
||||
"@gradio/tootils": patch
|
||||
"@gradio/upload": patch
|
||||
"@gradio/uploadbutton": patch
|
||||
"@gradio/utils": patch
|
||||
"@gradio/video": patch
|
||||
"gradio": patch
|
||||
"website": patch
|
||||
---
|
||||
|
||||
highlight:
|
||||
|
||||
#### Improve startup performance and markdown support
|
||||
|
||||
##### Improved markdown support
|
||||
|
||||
We now have better support for markdown in `gr.Markdown` and `gr.Dataframe`. Including syntax highlighting and Github Flavoured Markdown. We also have more consistent markdown behaviour and styling.
|
||||
|
||||
##### Various performance improvements
|
||||
|
||||
These improvements will be particularly beneficial to large applications.
|
||||
|
||||
- Rather than attaching events manually, they are now delegated, leading to a significant performance improvement and addressing a performance regression introduced in a recent version of Gradio. App startup for large applications is now around twice as fast.
|
||||
- Optimised the mounting of individual components, leading to a modest performance improvement during startup (~30%).
|
||||
- Corrected an issue that was causing markdown to re-render infinitely.
|
||||
- Ensured that the `gr.3DModel` does re-render prematurely.
|
File diff suppressed because one or more lines are too long
@ -69,9 +69,13 @@ with gr.Blocks() as demo:
|
||||
def video_stop():
|
||||
print("video_stop")
|
||||
|
||||
def video_end():
|
||||
print("video_end")
|
||||
|
||||
video1.play(fn=video_play)
|
||||
video1.pause(fn=video_pause)
|
||||
video1.stop(fn=video_stop)
|
||||
video1.end(fn=video_end)
|
||||
|
||||
radio1.change(fn=change_video, inputs=radio1, outputs=video1)
|
||||
video1.change(fn=alert_change, inputs=[gr.State("Video"), video1])
|
||||
|
@ -1 +1 @@
|
||||
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: latex"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " gr.Markdown(\n", " r\"\"\"\n", " # Hello World! $\\frac{\\sqrt{x + y}}{4}$ is today's lesson\n", "\n", " ## the $\\sqrt{x + y}$ is first\n", "\n", " Start with $\\frac{\\frac{x+1}{x+2}}{x+3}$ then we get $ 2+x $ and $3$.\n", " \n", " There are three formulas to know:\n", " \n", " the first is $\\gamma^2 + \\theta^2 = \\omega^2$\n", " \n", " $\\sqrt{x^2+1}\n", " $ is next\n", " \n", " Integral $\\int_{a}^{b} x^2 \\,dx$ is last\n", "\n", " Start typing below to see the output.\n", "\n", " I spent $5 at the grocery store. Then I bought a $2.50 ice cream cone.\n", " \"\"\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
||||
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: latex"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " gr.Markdown(\n", " r\"\"\"\n", " # Hello World! $\\frac{\\sqrt{x + y}}{4}$ is today's lesson\n", "\n", " ## the $\\sqrt{x + y}$ is first\n", "\n", " Start with $\\frac{\\frac{x+1}{x+2}}{x+3}$ then we get $ 2+x $ and $3$.\n", " \n", " There are three formulas to know:\n", " \n", " the first is $\\gamma^2 + \\theta^2 = \\omega^2$\n", " \n", " $\\sqrt{x^2+1}$ is next\n", " \n", " Integral $\\int_{a}^{b} x^2 \\,dx$ is last\n", "\n", " Start typing below to see the output.\n", "\n", " I spent $5 at the grocery store. Then I bought a $2.50 ice cream cone.\n", " \"\"\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
@ -13,8 +13,7 @@ with gr.Blocks() as demo:
|
||||
|
||||
the first is $\gamma^2 + \theta^2 = \omega^2$
|
||||
|
||||
$\sqrt{x^2+1}
|
||||
$ is next
|
||||
$\sqrt{x^2+1}$ is next
|
||||
|
||||
Integral $\int_{a}^{b} x^2 \,dx$ is last
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -168,6 +168,23 @@ your preferred browser.
|
||||
127.0.0.1:8000
|
||||
```
|
||||
|
||||
```python
|
||||
import gradio as gr
|
||||
|
||||
gr.Blocks() as demo:
|
||||
gr.Markdown(value=md)
|
||||
|
||||
demo.launch()
|
||||
```
|
||||
|
||||
```js
|
||||
function fancyAlert(arg) {
|
||||
if(arg) {
|
||||
$.facebox({div:'#foo'})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
@ -9,7 +9,6 @@ https://prismjs.com/download.html#themes=prism&languages=python */
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
word-wrap: normal;
|
||||
background: none;
|
||||
color: black;
|
||||
font-size: 1em;
|
||||
line-height: 1.5;
|
||||
|
@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
|
||||
import { Block, BlockLabel, Empty } from "@gradio/atoms";
|
||||
import { Image } from "@gradio/icons";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
import { type FileData, normalise_file } from "@gradio/upload";
|
||||
import type { SelectData } from "@gradio/utils";
|
||||
import { _ } from "svelte-i18n";
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
@ -26,24 +26,23 @@
|
||||
export let root_url: string;
|
||||
let active: string | null = null;
|
||||
export let loading_status: LoadingStatus;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
export let gradio: Gradio<{
|
||||
change: undefined;
|
||||
select: SelectData;
|
||||
}>();
|
||||
}>;
|
||||
|
||||
$: {
|
||||
if (value !== old_value) {
|
||||
old_value = value;
|
||||
dispatch("change");
|
||||
gradio.dispatch("change");
|
||||
}
|
||||
if (value) {
|
||||
_value = [
|
||||
normalise_file(value[0], root, root_url) as FileData,
|
||||
value[1].map(([file, _label]) => [
|
||||
normalise_file(file, root, root_url) as FileData,
|
||||
_label,
|
||||
]),
|
||||
_label
|
||||
])
|
||||
];
|
||||
} else {
|
||||
_value = null;
|
||||
@ -55,6 +54,13 @@
|
||||
function handle_mouseout(): void {
|
||||
active = null;
|
||||
}
|
||||
|
||||
function handle_click(i: number): void {
|
||||
gradio.dispatch("select", {
|
||||
value: label,
|
||||
index: i
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Block
|
||||
@ -115,7 +121,7 @@
|
||||
on:focus={() => handle_mouseover(label)}
|
||||
on:mouseout={() => handle_mouseout()}
|
||||
on:blur={() => handle_mouseout()}
|
||||
on:click={() => dispatch("select", { index: i, value: label })}
|
||||
on:click={() => handle_click(i)}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
|
@ -67,7 +67,6 @@
|
||||
"@gradio/utils": "workspace:^",
|
||||
"@gradio/video": "workspace:^",
|
||||
"@gradio/wasm": "workspace:^",
|
||||
"@playwright/test": "^1.35.1",
|
||||
"d3-dsv": "^3.0.1",
|
||||
"mime-types": "^2.1.34",
|
||||
"postcss": "^8.4.21",
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { tick } from "svelte";
|
||||
import { onMount, tick } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import type { client } from "@gradio/client";
|
||||
|
||||
@ -10,16 +10,15 @@
|
||||
import type {
|
||||
ComponentMeta,
|
||||
Dependency,
|
||||
LayoutNode,
|
||||
LayoutNode
|
||||
} from "./components/types";
|
||||
import { setupi18n } from "./i18n";
|
||||
import Render from "./Render.svelte";
|
||||
import { ApiDocs } from "./api_docs/";
|
||||
import type { ThemeMode } from "./components/types";
|
||||
import { Toast } from "@gradio/statustracker";
|
||||
import type { ToastMessage } from "@gradio/statustracker";
|
||||
import type { ShareData } from "@gradio/utils";
|
||||
import { dequal } from "dequal";
|
||||
import MountComponents from "./MountComponents.svelte";
|
||||
|
||||
import logo from "./images/logo.svg";
|
||||
import api_logo from "./api_docs/img/api-logo.svg";
|
||||
@ -42,12 +41,10 @@
|
||||
export let theme_mode: ThemeMode;
|
||||
export let app: Awaited<ReturnType<typeof client>>;
|
||||
export let space_id: string | null;
|
||||
export let version: string;
|
||||
|
||||
let loading_status = create_loading_status_store();
|
||||
|
||||
const walked_node_ids = new Set();
|
||||
const mounted_node_ids = new Set();
|
||||
|
||||
$: app_state.update((s) => ({ ...s, autoscroll }));
|
||||
|
||||
let rootNode: ComponentMeta = {
|
||||
@ -56,7 +53,7 @@
|
||||
props: { mode: "static" },
|
||||
has_modes: false,
|
||||
instance: {} as ComponentMeta["instance"],
|
||||
component: {} as ComponentMeta["component"],
|
||||
component: {} as ComponentMeta["component"]
|
||||
};
|
||||
|
||||
components.push(rootNode);
|
||||
@ -128,10 +125,13 @@
|
||||
);
|
||||
}
|
||||
|
||||
let instance_map = components.reduce((acc, next) => {
|
||||
acc[next.id] = next;
|
||||
return acc;
|
||||
}, {} as { [id: number]: ComponentMeta });
|
||||
let instance_map = components.reduce(
|
||||
(acc, next) => {
|
||||
acc[next.id] = next;
|
||||
return acc;
|
||||
},
|
||||
{} as { [id: number]: ComponentMeta }
|
||||
);
|
||||
|
||||
type LoadedComponent = {
|
||||
default: ComponentMeta["component"];
|
||||
@ -149,7 +149,7 @@
|
||||
const c = await component_map[name][mode]();
|
||||
return {
|
||||
name,
|
||||
component: c as LoadedComponent,
|
||||
component: c as LoadedComponent
|
||||
};
|
||||
} catch (e) {
|
||||
if (mode === "interactive") {
|
||||
@ -157,7 +157,7 @@
|
||||
const c = await component_map[name]["static"]();
|
||||
return {
|
||||
name,
|
||||
component: c as LoadedComponent,
|
||||
component: c as LoadedComponent
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(`failed to load: ${name}`);
|
||||
@ -184,7 +184,6 @@
|
||||
|
||||
async function walk_layout(node: LayoutNode): Promise<void> {
|
||||
let instance = instance_map[node.id];
|
||||
walked_node_ids.add(node.id);
|
||||
|
||||
const _component = (await _component_map.get(
|
||||
`${instance.type}_${_type_for_id.get(node.id) || "static"}`
|
||||
@ -308,7 +307,7 @@
|
||||
message,
|
||||
fn_index,
|
||||
type,
|
||||
id: ++_error_id,
|
||||
id: ++_error_id
|
||||
};
|
||||
}
|
||||
|
||||
@ -359,7 +358,7 @@
|
||||
let payload = {
|
||||
fn_index: dep_index,
|
||||
data: dep.inputs.map((id) => instance_map[id].props.value),
|
||||
event_data: dep.collects_event_data ? event_data : null,
|
||||
event_data: dep.collects_event_data ? event_data : null
|
||||
};
|
||||
|
||||
if (dep.frontend_fn) {
|
||||
@ -395,7 +394,7 @@
|
||||
...status,
|
||||
status: status.stage,
|
||||
progress: status.progress_data,
|
||||
fn_index,
|
||||
fn_index
|
||||
});
|
||||
if (
|
||||
!showed_duplicate_message &&
|
||||
@ -408,7 +407,7 @@
|
||||
showed_duplicate_message = true;
|
||||
messages = [
|
||||
new_message(DUPLICATE_MESSAGE, fn_index, "warning"),
|
||||
...messages,
|
||||
...messages
|
||||
];
|
||||
}
|
||||
if (
|
||||
@ -420,7 +419,7 @@
|
||||
showed_mobile_warning = true;
|
||||
messages = [
|
||||
new_message(MOBILE_QUEUE_WARNING, fn_index, "warning"),
|
||||
...messages,
|
||||
...messages
|
||||
];
|
||||
}
|
||||
|
||||
@ -437,7 +436,7 @@
|
||||
window.setTimeout(() => {
|
||||
messages = [
|
||||
new_message(MOBILE_RECONNECT_MESSAGE, fn_index, "error"),
|
||||
...messages,
|
||||
...messages
|
||||
];
|
||||
}, 0);
|
||||
trigger_api_call(dep_index, event_data);
|
||||
@ -450,7 +449,7 @@
|
||||
);
|
||||
messages = [
|
||||
new_message(_message, fn_index, "error"),
|
||||
...messages,
|
||||
...messages
|
||||
];
|
||||
}
|
||||
dependencies.map(async (dep, i) => {
|
||||
@ -495,11 +494,7 @@
|
||||
const is_external_url = (link: string | null): boolean =>
|
||||
!!(link && new URL(link, location.href).origin !== location.origin);
|
||||
|
||||
let attached_error_listeners: number[] = [];
|
||||
let shareable_components: number[] = [];
|
||||
async function handle_mount({ detail }: { detail: number }): Promise<void> {
|
||||
mounted_node_ids.add(detail);
|
||||
|
||||
async function handle_mount(): Promise<void> {
|
||||
await tick();
|
||||
|
||||
var a = target.getElementsByTagName("a");
|
||||
@ -513,68 +508,14 @@
|
||||
a[i].setAttribute("target", "_blank");
|
||||
}
|
||||
|
||||
// handle load triggers
|
||||
dependencies.forEach((dep, i) => {
|
||||
let { targets, trigger, inputs, outputs } = dep;
|
||||
const target_instances: [number, ComponentMeta][] = targets.map((t) => [
|
||||
t,
|
||||
instance_map[t],
|
||||
]);
|
||||
|
||||
// page events
|
||||
if (
|
||||
targets.length === 0 &&
|
||||
!handled_dependencies[i]?.includes(-1) &&
|
||||
trigger === "load" &&
|
||||
// check all input + output elements are on the page
|
||||
outputs.every((v) => instance_map?.[v].instance) &&
|
||||
inputs.every((v) => instance_map?.[v].instance)
|
||||
) {
|
||||
if (dep.targets.length === 0 && dep.trigger === "load") {
|
||||
trigger_api_call(i);
|
||||
handled_dependencies[i] = [-1];
|
||||
}
|
||||
|
||||
// component events
|
||||
target_instances
|
||||
.filter((v) => !!v && !!v[1])
|
||||
.forEach(([id, { instance }]: [number, ComponentMeta]) => {
|
||||
if (handled_dependencies[i]?.includes(id) || !instance) return;
|
||||
instance?.$on(trigger, (event_data: any) => {
|
||||
trigger_api_call(i, event_data.detail);
|
||||
});
|
||||
|
||||
if (!handled_dependencies[i]) handled_dependencies[i] = [];
|
||||
handled_dependencies[i].push(id);
|
||||
});
|
||||
});
|
||||
// share events
|
||||
components.forEach((c) => {
|
||||
if (
|
||||
c.props.show_share_button &&
|
||||
!shareable_components.includes(c.id) // only one share listener per component
|
||||
) {
|
||||
shareable_components.push(c.id);
|
||||
c.instance.$on("share", (event_data) => {
|
||||
const { title, description } = event_data.detail as ShareData;
|
||||
trigger_share(title, description);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
components.forEach((c) => {
|
||||
if (!attached_error_listeners.includes(c.id)) {
|
||||
if (c.instance) {
|
||||
attached_error_listeners.push(c.id);
|
||||
c.instance.$on("error", (event_data: any) => {
|
||||
messages = [
|
||||
new_message(event_data.detail, -1, "error"),
|
||||
...messages,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
checkRenderCompletion();
|
||||
render_complete = true;
|
||||
}
|
||||
|
||||
function handle_destroy(id: number): void {
|
||||
@ -604,11 +545,50 @@
|
||||
}
|
||||
}
|
||||
|
||||
function checkRenderCompletion(): void {
|
||||
if (dequal(walked_node_ids, mounted_node_ids)) {
|
||||
render_complete = true;
|
||||
}
|
||||
const target_map: Record<number, Record<string, number[]>> = {};
|
||||
|
||||
function isCustomEvent(event: Event): event is CustomEvent {
|
||||
return "detail" in event;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
dependencies.forEach((dep, i) => {
|
||||
let { targets, trigger, inputs, outputs } = dep;
|
||||
const target_instances: [number, ComponentMeta][] = targets.map((t) => [
|
||||
t,
|
||||
instance_map[t]
|
||||
]);
|
||||
|
||||
target_instances.forEach(([id]) => {
|
||||
if (!target_map[id]) {
|
||||
target_map[id] = {};
|
||||
}
|
||||
if (target_map[id]?.[trigger]) {
|
||||
target_map[id][trigger].push(i);
|
||||
} else {
|
||||
target_map[id][trigger] = [i];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
target.addEventListener("gradio", (e: Event) => {
|
||||
if (!isCustomEvent(e)) throw new Error("not a custom event");
|
||||
|
||||
const { id, event, data } = e.detail;
|
||||
|
||||
if (event === "share") {
|
||||
const { title, description } = data as ShareData;
|
||||
trigger_share(title, description);
|
||||
} else if (event === "error") {
|
||||
messages = [new_message(data, -1, "error"), ...messages];
|
||||
}
|
||||
|
||||
const deps = target_map[id]?.[event];
|
||||
deps?.forEach((dep_id) => {
|
||||
trigger_api_call(dep_id, data);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@ -635,11 +615,8 @@
|
||||
<div class="wrap" style:min-height={app_mode ? "100%" : "auto"}>
|
||||
<div class="contain" style:flex-grow={app_mode ? "1" : "auto"}>
|
||||
{#if ready}
|
||||
<Render
|
||||
component={rootNode.component}
|
||||
id={rootNode.id}
|
||||
props={rootNode.props}
|
||||
children={rootNode.children}
|
||||
<MountComponents
|
||||
{rootNode}
|
||||
{dynamic_ids}
|
||||
{instance_map}
|
||||
{root}
|
||||
@ -647,6 +624,7 @@
|
||||
{theme_mode}
|
||||
on:mount={handle_mount}
|
||||
on:destroy={({ detail }) => handle_destroy(detail)}
|
||||
{version}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -5,7 +5,7 @@
|
||||
import type {
|
||||
ComponentMeta,
|
||||
Dependency,
|
||||
LayoutNode,
|
||||
LayoutNode
|
||||
} from "./components/types";
|
||||
|
||||
declare let BUILD_MODE: string;
|
||||
@ -188,7 +188,7 @@
|
||||
message: "",
|
||||
load_status: "pending",
|
||||
status: "sleeping",
|
||||
detail: "SLEEPING",
|
||||
detail: "SLEEPING"
|
||||
};
|
||||
|
||||
let app: Awaited<ReturnType<typeof client>>;
|
||||
@ -208,7 +208,7 @@
|
||||
|
||||
app = await client(api_url, {
|
||||
status_callback: handle_status,
|
||||
normalise_files: false,
|
||||
normalise_files: false
|
||||
});
|
||||
config = app.config;
|
||||
window.__gradio_space__ = config.space_id;
|
||||
@ -217,7 +217,7 @@
|
||||
message: "",
|
||||
load_status: "complete",
|
||||
status: "running",
|
||||
detail: "RUNNING",
|
||||
detail: "RUNNING"
|
||||
};
|
||||
|
||||
await mount_custom_css(wrapper, config.css);
|
||||
@ -269,7 +269,7 @@
|
||||
CONFIG_ERROR: $_("errors.config_error"),
|
||||
BUILD_ERROR: $_("errors.build_error"),
|
||||
RUNTIME_ERROR: $_("errors.runtime_error"),
|
||||
PAUSED: $_("errors.space_paused"),
|
||||
PAUSED: $_("errors.space_paused")
|
||||
} as const,
|
||||
title(error: error_types): string {
|
||||
return encodeURIComponent($_("errors.space_not_working"));
|
||||
@ -280,7 +280,7 @@
|
||||
this.readable_error[error] || "an error"
|
||||
}.\n\nIt would be great if you could take a look at this because this space is being embedded on ${site}.\n\nThanks!`
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
@ -292,7 +292,7 @@
|
||||
new CustomEvent("render", {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
composed: true,
|
||||
composed: true
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -359,6 +359,7 @@
|
||||
bind:render_complete
|
||||
show_footer={!is_embed}
|
||||
{app_mode}
|
||||
{version}
|
||||
/>
|
||||
{/if}
|
||||
</Embed>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Form from "@gradio/form";
|
||||
import Textbox from "@gradio/textbox/interactive";
|
||||
import { BaseTextbox as Textbox } from "@gradio/textbox/interactive";
|
||||
import { BaseButton } from "@gradio/button/static";
|
||||
import Column from "@gradio/column";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
30
js/app/src/MountComponents.svelte
Normal file
30
js/app/src/MountComponents.svelte
Normal file
@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { onMount, createEventDispatcher } from "svelte";
|
||||
import Render from "./Render.svelte";
|
||||
|
||||
export let rootNode: any;
|
||||
export let dynamic_ids: any;
|
||||
export let instance_map: any;
|
||||
export let root: any;
|
||||
export let target: any;
|
||||
export let theme_mode: any;
|
||||
export let version: any;
|
||||
|
||||
const dispatch = createEventDispatcher<{ mount: never }>();
|
||||
onMount(() => {
|
||||
dispatch("mount");
|
||||
});
|
||||
</script>
|
||||
|
||||
<Render
|
||||
component={rootNode.component}
|
||||
id={rootNode.id}
|
||||
props={rootNode.props}
|
||||
children={rootNode.children}
|
||||
{dynamic_ids}
|
||||
{instance_map}
|
||||
{root}
|
||||
{target}
|
||||
{theme_mode}
|
||||
{version}
|
||||
/>
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Gradio } from "./gradio_helper";
|
||||
import { onMount, createEventDispatcher, setContext } from "svelte";
|
||||
import type { ComponentMeta } from "./components/types";
|
||||
import type { ThemeMode } from "./components/types";
|
||||
@ -15,6 +16,7 @@
|
||||
export let parent: string | null = null;
|
||||
export let target: HTMLElement;
|
||||
export let theme_mode: ThemeMode;
|
||||
export let version: string;
|
||||
|
||||
const dispatch = createEventDispatcher<{ mount: number; destroy: number }>();
|
||||
let filtered_children: ComponentMeta[] = [];
|
||||
@ -75,6 +77,7 @@
|
||||
{...props}
|
||||
{theme_mode}
|
||||
{root}
|
||||
gradio={new Gradio(id, target, theme_mode, version, root)}
|
||||
>
|
||||
{#if children && children.length}
|
||||
{#each children as { component, id: each_id, props, children: _children, has_modes } (each_id)}
|
||||
|
@ -1,11 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { SvelteComponent, ComponentType } from "svelte";
|
||||
import { component_map } from "./directory";
|
||||
import type { SelectData } from "@gradio/utils";
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import { get_fetchable_url_or_file } from "@gradio/upload";
|
||||
|
||||
export let components: (keyof typeof component_map)[];
|
||||
export let label = "Examples";
|
||||
export let headers: string[];
|
||||
@ -19,11 +17,10 @@
|
||||
export let samples_per_page = 10;
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
export let gradio: Gradio<{
|
||||
click: number;
|
||||
select: SelectData;
|
||||
}>();
|
||||
}>;
|
||||
|
||||
let samples_dir: string = get_fetchable_url_or_file(null, root, root_url);
|
||||
let page = 0;
|
||||
@ -112,8 +109,8 @@
|
||||
class="gallery-item"
|
||||
on:click={() => {
|
||||
value = i + page * samples_per_page;
|
||||
dispatch("click", value);
|
||||
dispatch("select", { index: value, value: sample_row });
|
||||
gradio.dispatch("click", value);
|
||||
gradio.dispatch("select", { index: value, value: sample_row });
|
||||
}}
|
||||
on:mouseenter={() => handle_mouseenter(i)}
|
||||
on:mouseleave={() => handle_mouseleave()}
|
||||
@ -149,7 +146,7 @@
|
||||
class="tr-body"
|
||||
on:click={() => {
|
||||
value = i + page * samples_per_page;
|
||||
dispatch("click", value);
|
||||
gradio.dispatch("click", value);
|
||||
}}
|
||||
on:mouseenter={() => handle_mouseenter(i)}
|
||||
on:mouseleave={() => handle_mouseleave()}
|
||||
|
@ -6,8 +6,6 @@ export function mount_css(url: string, target: HTMLElement): Promise<void> {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = url;
|
||||
// @ts-ignore
|
||||
target.appendChild(link);
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
link.addEventListener("load", () => res());
|
||||
@ -15,5 +13,6 @@ export function mount_css(url: string, target: HTMLElement): Promise<void> {
|
||||
console.error(`Unable to preload CSS for ${url}`);
|
||||
res();
|
||||
});
|
||||
target.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
33
js/app/src/gradio_helper.ts
Normal file
33
js/app/src/gradio_helper.ts
Normal file
@ -0,0 +1,33 @@
|
||||
// import { _ } from "svelte-i18n";
|
||||
|
||||
export class Gradio<T extends Record<string, any>> {
|
||||
#id: number;
|
||||
theme: string;
|
||||
version: string;
|
||||
// i18n: typeof _;
|
||||
#el: HTMLElement;
|
||||
root: string;
|
||||
|
||||
constructor(
|
||||
id: number,
|
||||
el: HTMLElement,
|
||||
theme: string,
|
||||
version: string,
|
||||
root: string
|
||||
) {
|
||||
this.#id = id;
|
||||
this.theme = theme;
|
||||
this.version = version;
|
||||
this.#el = el;
|
||||
// this.i18n = _;
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
dispatch<E extends keyof T>(event_name: E, data?: T[E]): void {
|
||||
const e = new CustomEvent("gradio", {
|
||||
bubbles: true,
|
||||
detail: { data, id: this.#id, event: event_name }
|
||||
});
|
||||
this.#el.dispatchEvent(e);
|
||||
}
|
||||
}
|
@ -240,11 +240,9 @@ describe("all components should have the appropriate label when set via the `lab
|
||||
});
|
||||
|
||||
describe("all components should hide their label when `show_label=false`", () => {
|
||||
components
|
||||
.filter(([name]) => name !== "Markdown" && name !== "HTML")
|
||||
.forEach(([name, component, props]) => {
|
||||
test.todo(name);
|
||||
});
|
||||
components.forEach(([name, component, props]) => {
|
||||
test.todo(name);
|
||||
});
|
||||
|
||||
["Button", "Code", "Image", "Plot"].forEach((name) => {
|
||||
test.todo(name);
|
||||
@ -252,11 +250,9 @@ describe("all components should hide their label when `show_label=false`", () =>
|
||||
});
|
||||
|
||||
describe("all components should show their label when `show_label=true`", () => {
|
||||
components
|
||||
.filter(([name]) => name !== "Markdown" && name !== "HTML")
|
||||
.forEach(([name, component, props]) => {
|
||||
test.todo(name);
|
||||
});
|
||||
components.forEach(([name, component, props]) => {
|
||||
test.todo(name);
|
||||
});
|
||||
|
||||
["Button", "Code", "Image", "Plot"].forEach((name) => {
|
||||
test.todo(name);
|
||||
@ -264,11 +260,9 @@ describe("all components should show their label when `show_label=true`", () =>
|
||||
});
|
||||
|
||||
describe("all components should hide their container when `container=false`", () => {
|
||||
components
|
||||
.filter(([name]) => name !== "Markdown" && name !== "HTML")
|
||||
.forEach(([name, component, props]) => {
|
||||
test.todo(name);
|
||||
});
|
||||
components.forEach(([name, component, props]) => {
|
||||
test.todo(name);
|
||||
});
|
||||
|
||||
["Button", "Code", "Image", "Plot"].forEach((name) => {
|
||||
test.todo(name);
|
||||
|
@ -25,7 +25,7 @@
|
||||
pending = true;
|
||||
const formatted = await formatter(value);
|
||||
dispatch("share", {
|
||||
description: formatted,
|
||||
description: formatted
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
@ -8,7 +8,7 @@
|
||||
video: "upload_text.drop_video",
|
||||
audio: "upload_text.drop_audio",
|
||||
file: "upload_text.drop_file",
|
||||
csv: "upload_text.drop_csv",
|
||||
csv: "upload_text.drop_csv"
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -53,7 +53,7 @@
|
||||
function get_modules(): void {
|
||||
module_promises = [
|
||||
import("extendable-media-recorder"),
|
||||
import("extendable-media-recorder-wav-encoder"),
|
||||
import("extendable-media-recorder-wav-encoder")
|
||||
];
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@
|
||||
let _audio_blob = new Blob(blobs, { type: "audio/wav" });
|
||||
value = {
|
||||
data: await blob_to_data_url(_audio_blob),
|
||||
name: "audio.wav",
|
||||
name: "audio.wav"
|
||||
};
|
||||
dispatch(event, value);
|
||||
};
|
||||
@ -202,7 +202,7 @@
|
||||
}
|
||||
|
||||
function handle_change({
|
||||
detail: { values },
|
||||
detail: { values }
|
||||
}: {
|
||||
detail: { values: [number, number] };
|
||||
}): void {
|
||||
@ -212,14 +212,14 @@
|
||||
data: value.data,
|
||||
name,
|
||||
crop_min: values[0],
|
||||
crop_max: values[1],
|
||||
crop_max: values[1]
|
||||
});
|
||||
|
||||
dispatch("edit");
|
||||
}
|
||||
|
||||
function handle_load({
|
||||
detail,
|
||||
detail
|
||||
}: {
|
||||
detail: {
|
||||
data: string;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { UploadText } from "@gradio/atoms";
|
||||
|
||||
@ -14,12 +14,6 @@
|
||||
|
||||
import { normalise_file } from "@gradio/upload";
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: typeof value;
|
||||
stream: typeof value;
|
||||
error: string;
|
||||
}>();
|
||||
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
export let visible = true;
|
||||
@ -38,6 +32,19 @@
|
||||
export let loading_status: LoadingStatus;
|
||||
export let autoplay = false;
|
||||
export let show_edit_button = true;
|
||||
export let gradio: Gradio<{
|
||||
change: typeof value;
|
||||
stream: typeof value;
|
||||
error: string;
|
||||
edit: never;
|
||||
play: never;
|
||||
pause: never;
|
||||
stop: never;
|
||||
end: never;
|
||||
start_recording: never;
|
||||
stop_recording: never;
|
||||
upload: never;
|
||||
}>;
|
||||
|
||||
let old_value: null | FileData | string = null;
|
||||
|
||||
@ -47,7 +54,7 @@
|
||||
$: {
|
||||
if (JSON.stringify(value) !== JSON.stringify(old_value)) {
|
||||
old_value = value;
|
||||
dispatch("change");
|
||||
gradio.dispatch("change");
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +80,7 @@
|
||||
on:change={({ detail }) => (value = detail)}
|
||||
on:stream={({ detail }) => {
|
||||
value = detail;
|
||||
dispatch("stream", value);
|
||||
gradio.dispatch("stream", value);
|
||||
}}
|
||||
on:drag={({ detail }) => (dragging = detail)}
|
||||
{name}
|
||||
@ -82,18 +89,18 @@
|
||||
{streaming}
|
||||
{autoplay}
|
||||
{show_edit_button}
|
||||
on:edit
|
||||
on:play
|
||||
on:pause
|
||||
on:stop
|
||||
on:end
|
||||
on:start_recording
|
||||
on:stop_recording
|
||||
on:upload
|
||||
on:edit={() => gradio.dispatch("edit")}
|
||||
on:play={() => gradio.dispatch("play")}
|
||||
on:pause={() => gradio.dispatch("pause")}
|
||||
on:stop={() => gradio.dispatch("stop")}
|
||||
on:end={() => gradio.dispatch("end")}
|
||||
on:start_recording={() => gradio.dispatch("start_recording")}
|
||||
on:stop_recording={() => gradio.dispatch("stop_recording")}
|
||||
on:upload={() => gradio.dispatch("upload")}
|
||||
on:error={({ detail }) => {
|
||||
loading_status = loading_status || {};
|
||||
loading_status.status = "error";
|
||||
dispatch("error", detail);
|
||||
gradio.dispatch("error", detail);
|
||||
}}
|
||||
>
|
||||
<UploadText type="audio" />
|
||||
|
@ -35,7 +35,7 @@
|
||||
$: value &&
|
||||
dispatch("change", {
|
||||
name: name,
|
||||
data: value?.data,
|
||||
data: value?.data
|
||||
});
|
||||
|
||||
function handle_ended(): void {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio, ShareData } from "@gradio/utils";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
import type { FileData } from "@gradio/upload";
|
||||
@ -13,12 +13,6 @@
|
||||
|
||||
import { normalise_file } from "@gradio/upload";
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: typeof value;
|
||||
stream: typeof value;
|
||||
error: string;
|
||||
}>();
|
||||
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
export let visible = true;
|
||||
@ -36,6 +30,11 @@
|
||||
export let autoplay = false;
|
||||
export let show_download_button = true;
|
||||
export let show_share_button = false;
|
||||
export let gradio: Gradio<{
|
||||
change: typeof value;
|
||||
share: ShareData;
|
||||
error: string;
|
||||
}>;
|
||||
|
||||
let old_value: null | FileData | string = null;
|
||||
|
||||
@ -45,7 +44,7 @@
|
||||
$: {
|
||||
if (JSON.stringify(value) !== JSON.stringify(old_value)) {
|
||||
old_value = value;
|
||||
dispatch("change");
|
||||
gradio.dispatch("change");
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +74,7 @@
|
||||
value={_value}
|
||||
name={_value?.name || "audio_file"}
|
||||
{label}
|
||||
on:share
|
||||
on:error
|
||||
on:share={(e) => gradio.dispatch("share", e.detail)}
|
||||
on:error={(e) => gradio.dispatch("error", e.detail)}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -11,43 +11,43 @@
|
||||
control: "text",
|
||||
description: "The text to display on the button",
|
||||
name: "label",
|
||||
value: "Gradio Button",
|
||||
value: "Gradio Button"
|
||||
},
|
||||
variant: {
|
||||
options: ["primary", "secondary", "stop"],
|
||||
description: "The variant of the button",
|
||||
control: { type: "select" },
|
||||
defaultValue: "primary",
|
||||
defaultValue: "primary"
|
||||
},
|
||||
size: {
|
||||
options: ["sm", "lg"],
|
||||
description: "The size of the button",
|
||||
control: { type: "select" },
|
||||
defaultValue: "lg",
|
||||
defaultValue: "lg"
|
||||
},
|
||||
visible: {
|
||||
options: [true, false],
|
||||
description: "Sets the visibility of the button",
|
||||
control: { type: "boolean" },
|
||||
defaultValue: true,
|
||||
defaultValue: true
|
||||
},
|
||||
interactive: {
|
||||
options: [true, false],
|
||||
description: "If false, the button will be in a disabled state",
|
||||
control: { type: "boolean" },
|
||||
defaultValue: true,
|
||||
defaultValue: true
|
||||
},
|
||||
disabled: {
|
||||
options: [true, false],
|
||||
control: { type: "boolean" },
|
||||
defaultValue: false,
|
||||
defaultValue: false
|
||||
},
|
||||
scale: {
|
||||
options: [null, 0.5, 1, 2],
|
||||
description:
|
||||
"relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.",
|
||||
control: { type: "select" },
|
||||
},
|
||||
control: { type: "select" }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -65,12 +65,12 @@
|
||||
<Story
|
||||
name="Button with external image icon"
|
||||
args={{
|
||||
icon: "https://huggingface.co/front/assets/huggingface_logo-noborder.svg",
|
||||
icon: "https://huggingface.co/front/assets/huggingface_logo-noborder.svg"
|
||||
}}
|
||||
/>
|
||||
<Story
|
||||
name="Button with visible equal to false"
|
||||
args={{
|
||||
visible: false,
|
||||
visible: false
|
||||
}}
|
||||
/>
|
||||
|
@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, ShareData } from "@gradio/utils";
|
||||
|
||||
import Button from "./Button.svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
@ -13,6 +15,9 @@
|
||||
export let icon: string | null = null;
|
||||
export let link: string | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let gradio: Gradio<{
|
||||
click: never;
|
||||
}>;
|
||||
export let root = "";
|
||||
export let root_url: null | string = null;
|
||||
</script>
|
||||
@ -31,7 +36,7 @@
|
||||
{root}
|
||||
{root_url}
|
||||
disabled={mode === "static"}
|
||||
on:click
|
||||
on:click={() => gradio.dispatch("click")}
|
||||
>
|
||||
{$_(value)}
|
||||
</Button>
|
||||
|
@ -10,18 +10,14 @@
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^",
|
||||
"@gradio/icons": "workspace:^",
|
||||
"@gradio/markdown": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/theme": "workspace:^",
|
||||
"@gradio/upload": "workspace:^",
|
||||
"@gradio/utils": "workspace:^",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/katex": "^0.16.0",
|
||||
"@types/prismjs": "1.26.0",
|
||||
"dompurify": "^3.0.3",
|
||||
"katex": "^0.16.7",
|
||||
"marked": "^7.0.0",
|
||||
"marked-highlight": "^2.0.1",
|
||||
"prismjs": "1.29.0"
|
||||
"@types/prismjs": "1.26.0"
|
||||
},
|
||||
"main_changeset": true,
|
||||
"exports": {
|
||||
|
@ -1,22 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { copy, format_chat_for_sharing } from "../utils";
|
||||
import "katex/dist/katex.min.css";
|
||||
import { format_chat_for_sharing } from "../utils";
|
||||
import { copy } from "@gradio/utils";
|
||||
|
||||
import { beforeUpdate, afterUpdate, createEventDispatcher } from "svelte";
|
||||
import { ShareButton } from "@gradio/atoms";
|
||||
import type { SelectData } from "@gradio/utils";
|
||||
import type { ThemeMode } from "js/app/src/components/types";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import {get_fetchable_url_or_file} from "@gradio/upload";
|
||||
import Markdown from "./MarkdownCode.svelte";
|
||||
import { MarkdownCode as Markdown } from "@gradio/markdown/static";
|
||||
import { get_fetchable_url_or_file } from "@gradio/upload";
|
||||
import Copy from "./Copy.svelte";
|
||||
|
||||
const code_highlight_css = {
|
||||
light: (): Promise<typeof import("prismjs/themes/prism.css")> =>
|
||||
import("prismjs/themes/prism.css"),
|
||||
dark: (): Promise<typeof import("prismjs/themes/prism.css")> =>
|
||||
import("prismjs/themes/prism-dark.css")
|
||||
};
|
||||
|
||||
export let value:
|
||||
| [string | FileData | null, string | FileData | null][]
|
||||
| null;
|
||||
@ -31,17 +25,11 @@
|
||||
export let feedback: string[] | null = null;
|
||||
export let selectable = false;
|
||||
export let show_share_button = false;
|
||||
export let theme_mode: ThemeMode;
|
||||
export let rtl = false;
|
||||
export let show_copy_button = false;
|
||||
export let avatar_images: [string | null, string | null] = [null, null];
|
||||
export let root: string;
|
||||
export let root_url: null | string;
|
||||
$: if (theme_mode == "dark") {
|
||||
code_highlight_css.dark();
|
||||
} else {
|
||||
code_highlight_css.light();
|
||||
}
|
||||
|
||||
let div: HTMLDivElement;
|
||||
let autoscroll: boolean;
|
||||
@ -107,11 +95,15 @@
|
||||
{#if value !== null}
|
||||
{#each value as message_pair, i}
|
||||
{#each message_pair as message, j}
|
||||
<div class="message-row">
|
||||
<div class="message-row {j == 0 ? 'user-row' : 'bot-row'}">
|
||||
{#if avatar_images[j] !== null}
|
||||
<img
|
||||
class="avatar-image-{j == 0 ? 'user' : 'bot'}"
|
||||
src={get_fetchable_url_or_file(avatar_images[j], root, root_url)}
|
||||
src={get_fetchable_url_or_file(
|
||||
avatar_images[j],
|
||||
root,
|
||||
root_url
|
||||
)}
|
||||
alt="avatar"
|
||||
/>
|
||||
{/if}
|
||||
@ -247,15 +239,7 @@
|
||||
}
|
||||
.bot {
|
||||
border-bottom-left-radius: 0;
|
||||
padding-left: calc(2 * var(--spacing-xxl));
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.message {
|
||||
width: auto;
|
||||
}
|
||||
.bot {
|
||||
padding-left: var(--spacing-xxl);
|
||||
}
|
||||
padding-left: var(--spacing-xxl);
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
@ -272,6 +256,22 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.user-row {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.bot-row {
|
||||
align-self: flex-start;
|
||||
}
|
||||
.message {
|
||||
width: auto;
|
||||
}
|
||||
.bot {
|
||||
padding-left: var(--spacing-xxl);
|
||||
}
|
||||
}
|
||||
.avatar-image-user,
|
||||
.avatar-image-bot {
|
||||
align-self: flex-end;
|
||||
@ -365,39 +365,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
.message-wrap :global(pre[class*="language-"]),
|
||||
.message-wrap :global(pre) {
|
||||
position: relative;
|
||||
direction: ltr;
|
||||
white-space: no-wrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.message-wrap :global(code) {
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.message-wrap :global(div[class*="code_wrap"]) {
|
||||
position: relative;
|
||||
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),
|
||||
|
@ -1,54 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { afterUpdate, tick, createEventDispatcher } from "svelte";
|
||||
import DOMPurify from "dompurify";
|
||||
import render_math_in_element from "katex/dist/contrib/auto-render.js";
|
||||
import { marked } from "../utils";
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let message: string;
|
||||
let old_message = "";
|
||||
export let latex_delimiters: {
|
||||
left: string;
|
||||
right: string;
|
||||
display: boolean;
|
||||
}[];
|
||||
|
||||
let el: HTMLSpanElement;
|
||||
let mounted = false;
|
||||
|
||||
DOMPurify.addHook("afterSanitizeAttributes", function (node) {
|
||||
if ("target" in node) {
|
||||
node.setAttribute("target", "_blank");
|
||||
node.setAttribute("rel", "noopener noreferrer");
|
||||
}
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
tick().then(() => {
|
||||
if (message !== old_message) {
|
||||
requestAnimationFrame(() => {
|
||||
el.innerHTML = DOMPurify.sanitize(marked.parse(message));
|
||||
mounted = true;
|
||||
old_message = message;
|
||||
dispatch("load");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$: mounted &&
|
||||
latex_delimiters.length > 0 &&
|
||||
render_math_in_element(el, {
|
||||
delimiters: latex_delimiters,
|
||||
throwOnError: false
|
||||
});
|
||||
</script>
|
||||
|
||||
<span bind:this={el} />
|
||||
|
||||
<style>
|
||||
span :global(code[class*="language-"]),
|
||||
span :global(pre[class*="language-"]) {
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
</style>
|
@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
|
||||
import ChatBot from "./ChatBot.svelte";
|
||||
import { Block, BlockLabel } from "@gradio/atoms";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
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";
|
||||
@ -19,7 +20,6 @@
|
||||
export let root: string;
|
||||
export let root_url: null | string;
|
||||
export let selectable = false;
|
||||
export let theme_mode: ThemeMode;
|
||||
export let show_share_button = false;
|
||||
export let rtl = false;
|
||||
export let show_copy_button = false;
|
||||
@ -28,6 +28,12 @@
|
||||
right: string;
|
||||
display: boolean;
|
||||
}[];
|
||||
export let gradio: Gradio<{
|
||||
change: typeof value;
|
||||
select: SelectData;
|
||||
share: ShareData;
|
||||
error: string;
|
||||
}>;
|
||||
export let avatar_images: [string | null, string | null] = [null, null];
|
||||
|
||||
let _value: [string | FileData | null, string | FileData | null][];
|
||||
@ -80,19 +86,18 @@
|
||||
<ChatBot
|
||||
{selectable}
|
||||
{show_share_button}
|
||||
{theme_mode}
|
||||
value={_value}
|
||||
{latex_delimiters}
|
||||
pending_message={loading_status?.status === "pending"}
|
||||
{rtl}
|
||||
{show_copy_button}
|
||||
on:change={() => gradio.dispatch("change", value)}
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
on:share={(e) => gradio.dispatch("share", e.detail)}
|
||||
on:error={(e) => gradio.dispatch("error", e.detail)}
|
||||
{avatar_images}
|
||||
{root_url}
|
||||
{root}
|
||||
on:change
|
||||
on:select
|
||||
on:share
|
||||
on:error
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
|
@ -1,194 +1,5 @@
|
||||
import { marked, type Renderer } from "marked";
|
||||
import { markedHighlight } from "marked-highlight";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/components/prism-python";
|
||||
import "prismjs/components/prism-latex";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { uploadToHuggingFace } from "@gradio/utils";
|
||||
import type { ActionReturn } from "svelte/action";
|
||||
|
||||
const COPY_ICON_CODE = `<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 32 32"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M28 10v18H10V10h18m0-2H10a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2Z"
|
||||
/><path fill="currentColor" d="M4 18H2V4a2 2 0 0 1 2-2h14v2H4Z" /></svg>`;
|
||||
const CHECK_ICON_CODE = `<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"><polyline points="20 6 9 17 4 12" /></svg>`;
|
||||
const COPY_BUTTON_CODE = `<button title="copy" class="copy_code_button">
|
||||
<span class="copy-text">${COPY_ICON_CODE}</span>
|
||||
<span class="check">${CHECK_ICON_CODE}</span>
|
||||
</button>`;
|
||||
|
||||
const escape_test = /[&<>"']/;
|
||||
const escape_replace = new RegExp(escape_test.source, "g");
|
||||
const escape_test_no_encode =
|
||||
/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/;
|
||||
const escape_replace_no_encode = new RegExp(escape_test_no_encode.source, "g");
|
||||
const escape_replacements: Record<string, any> = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'"
|
||||
};
|
||||
|
||||
const get_escape_replacement = (ch: string): string =>
|
||||
escape_replacements[ch] || "";
|
||||
|
||||
function escape(html: string, encode?: boolean): string {
|
||||
if (encode) {
|
||||
if (escape_test.test(html)) {
|
||||
return html.replace(escape_replace, get_escape_replacement);
|
||||
}
|
||||
} else {
|
||||
if (escape_test_no_encode.test(html)) {
|
||||
return html.replace(escape_replace_no_encode, get_escape_replacement);
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
const renderer: Partial<Omit<Renderer, "constructor" | "options">> = {
|
||||
code(
|
||||
this: Renderer,
|
||||
code: string,
|
||||
infostring: string | undefined,
|
||||
escaped: boolean
|
||||
) {
|
||||
const lang = (infostring ?? "").match(/\S*/)?.[0] ?? "";
|
||||
if (this.options.highlight) {
|
||||
const out = this.options.highlight(code, lang);
|
||||
if (out != null && out !== code) {
|
||||
escaped = true;
|
||||
code = out;
|
||||
}
|
||||
}
|
||||
|
||||
code = code.replace(/\n$/, "") + "\n";
|
||||
|
||||
if (!lang) {
|
||||
return (
|
||||
'<div class="code_wrap">' +
|
||||
COPY_BUTTON_CODE +
|
||||
"<pre><code>" +
|
||||
(escaped ? code : escape(code, true)) +
|
||||
"</code></pre></div>\n"
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
'<div class="code_wrap">' +
|
||||
COPY_BUTTON_CODE +
|
||||
'<pre><code class="' +
|
||||
this.options.langPrefix +
|
||||
escape(lang) +
|
||||
'">' +
|
||||
(escaped ? code : escape(code, true)) +
|
||||
"</code></pre></div>\n"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
marked.use(
|
||||
{
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
pedantic: false,
|
||||
headerIds: false,
|
||||
mangle: false
|
||||
},
|
||||
markedHighlight({
|
||||
highlight: (code: string, lang: string) => {
|
||||
if (Prism.languages[lang]) {
|
||||
return Prism.highlight(code, Prism.languages[lang], lang);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}),
|
||||
{ renderer }
|
||||
);
|
||||
|
||||
export function copy(node: HTMLDivElement): ActionReturn {
|
||||
node.addEventListener("click", handle_copy);
|
||||
|
||||
async function handle_copy(event: MouseEvent): Promise<void> {
|
||||
const path = event.composedPath() as HTMLButtonElement[];
|
||||
|
||||
const [copy_button] = path.filter(
|
||||
(e) => e?.tagName === "BUTTON" && e.classList.contains("copy_code_button")
|
||||
);
|
||||
|
||||
if (copy_button) {
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
const copy_text = copy_button.parentElement!.innerText.trim();
|
||||
const copy_sucess_button = Array.from(
|
||||
copy_button.children
|
||||
)[1] as HTMLDivElement;
|
||||
|
||||
const copied = await copy_to_clipboard(copy_text);
|
||||
|
||||
if (copied) copy_feedback(copy_sucess_button);
|
||||
|
||||
function copy_feedback(_copy_sucess_button: HTMLDivElement): void {
|
||||
_copy_sucess_button.style.opacity = "1";
|
||||
setTimeout(() => {
|
||||
_copy_sucess_button.style.opacity = "0";
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
destroy(): void {
|
||||
node.removeEventListener("click", handle_copy);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function copy_to_clipboard(value: string): Promise<boolean> {
|
||||
let copied = false;
|
||||
if ("clipboard" in navigator) {
|
||||
await navigator.clipboard.writeText(value);
|
||||
copied = true;
|
||||
} else {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = value;
|
||||
|
||||
textArea.style.position = "absolute";
|
||||
textArea.style.left = "-999999px";
|
||||
|
||||
document.body.prepend(textArea);
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
copied = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
copied = false;
|
||||
} finally {
|
||||
textArea.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
export { marked };
|
||||
|
||||
export const format_chat_for_sharing = async (
|
||||
chat: [string | FileData | null, string | FileData | null][]
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import Checkbox from "../shared";
|
||||
import { Block, Info } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -16,6 +17,11 @@
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: never;
|
||||
input: never;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block {visible} {elem_id} {elem_classes} {container} {scale} {min_width}>
|
||||
@ -28,8 +34,8 @@
|
||||
{label}
|
||||
bind:value
|
||||
bind:value_is_output
|
||||
on:change
|
||||
on:input
|
||||
on:select
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:select={() => gradio.dispatch("select")}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import Checkbox from "../shared";
|
||||
import { Block, Info } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -15,6 +16,11 @@
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: never;
|
||||
input: never;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block {visible} {elem_id} {elem_classes} {container} {scale} {min_width}>
|
||||
@ -27,9 +33,9 @@
|
||||
{label}
|
||||
bind:value
|
||||
bind:value_is_output
|
||||
on:change
|
||||
on:input
|
||||
on:select
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:select={() => gradio.dispatch("select")}
|
||||
disabled
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import CheckboxGroup from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -17,6 +18,11 @@
|
||||
export let label = $_("checkbox.checkbox_group");
|
||||
export let info: string | undefined = undefined;
|
||||
export let show_label: boolean;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: SelectData;
|
||||
input: never;
|
||||
}>;
|
||||
|
||||
export let loading_status: LoadingStatus;
|
||||
</script>
|
||||
@ -39,8 +45,8 @@
|
||||
{label}
|
||||
{info}
|
||||
{show_label}
|
||||
on:select
|
||||
on:change
|
||||
on:input
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import CheckboxGroup from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -16,6 +17,11 @@
|
||||
export let label = $_("checkbox.checkbox_group");
|
||||
export let info: string | undefined = undefined;
|
||||
export let show_label: boolean;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: SelectData;
|
||||
input: never;
|
||||
}>;
|
||||
|
||||
export let loading_status: LoadingStatus;
|
||||
</script>
|
||||
@ -38,9 +44,9 @@
|
||||
{label}
|
||||
{info}
|
||||
{show_label}
|
||||
on:select
|
||||
on:change
|
||||
on:input
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
disabled
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, afterUpdate } from "svelte";
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import { afterUpdate } from "svelte";
|
||||
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
import { _ } from "svelte-i18n";
|
||||
@ -8,11 +9,6 @@
|
||||
import { Block, BlockLabel } from "@gradio/atoms";
|
||||
import { Code as CodeIcon } from "@gradio/icons";
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: typeof value;
|
||||
input: undefined;
|
||||
}>();
|
||||
|
||||
export let value = "";
|
||||
export let value_is_output = false;
|
||||
export let language = "";
|
||||
@ -24,13 +20,17 @@
|
||||
export let label = $_("code.code");
|
||||
export let show_label = true;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let gradio: Gradio<{
|
||||
change: typeof value;
|
||||
input: never;
|
||||
}>;
|
||||
|
||||
let dark_mode = target.classList.contains("dark");
|
||||
|
||||
function handle_change(): void {
|
||||
dispatch("change", value);
|
||||
gradio.dispatch("change", value);
|
||||
if (!value_is_output) {
|
||||
dispatch("input");
|
||||
gradio.dispatch("input");
|
||||
}
|
||||
}
|
||||
afterUpdate(() => {
|
||||
|
@ -26,6 +26,7 @@
|
||||
"@gradio/icons": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/upload": "workspace:^",
|
||||
"@gradio/utils": "workspace:^",
|
||||
"@lezer/common": "^1.0.2",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
"@lezer/markdown": "^1.0.2",
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, afterUpdate } from "svelte";
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import { afterUpdate } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
@ -9,11 +10,6 @@
|
||||
import { Block, BlockLabel, Empty } from "@gradio/atoms";
|
||||
import { Code as CodeIcon } from "@gradio/icons";
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: typeof value;
|
||||
input: undefined;
|
||||
}>();
|
||||
|
||||
export let value = "";
|
||||
export let value_is_output = false;
|
||||
export let language = "";
|
||||
@ -25,13 +21,17 @@
|
||||
export let label = $_("code.code");
|
||||
export let show_label = true;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let gradio: Gradio<{
|
||||
change: typeof value;
|
||||
input: never;
|
||||
}>;
|
||||
|
||||
let dark_mode = target.classList.contains("dark");
|
||||
|
||||
function handle_change(): void {
|
||||
dispatch("change", value);
|
||||
gradio.dispatch("change", value);
|
||||
if (!value_is_output) {
|
||||
dispatch("input");
|
||||
gradio.dispatch("input");
|
||||
}
|
||||
}
|
||||
afterUpdate(() => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import Colorpicker from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -20,6 +21,13 @@
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let interactive = true;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
input: never;
|
||||
submit: never;
|
||||
blur: never;
|
||||
focus: never;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block {visible} {elem_id} {elem_classes} {container} {scale} {min_width}>
|
||||
@ -32,10 +40,10 @@
|
||||
{info}
|
||||
{show_label}
|
||||
disabled={!interactive}
|
||||
on:change
|
||||
on:input
|
||||
on:submit
|
||||
on:blur
|
||||
on:focus
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:submit={() => gradio.dispatch("submit")}
|
||||
on:blur={() => gradio.dispatch("blur")}
|
||||
on:focus={() => gradio.dispatch("focus")}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -16,6 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^"
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/utils": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import Colorpicker from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
import exp from "constants";
|
||||
|
||||
export let label = "ColorPicker";
|
||||
export let info: string | undefined = undefined;
|
||||
@ -19,6 +21,13 @@
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let interactive = true;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
input: never;
|
||||
submit: never;
|
||||
blur: never;
|
||||
focus: never;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block {visible} {elem_id} {elem_classes} {container} {scale} {min_width}>
|
||||
@ -31,10 +40,10 @@
|
||||
{info}
|
||||
{show_label}
|
||||
disabled={!interactive}
|
||||
on:change
|
||||
on:input
|
||||
on:submit
|
||||
on:blur
|
||||
on:focus
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:submit={() => gradio.dispatch("submit")}
|
||||
on:blur={() => gradio.dispatch("blur")}
|
||||
on:focus={() => gradio.dispatch("focus")}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import Table from "../shared";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
import { createEventDispatcher, afterUpdate } from "svelte";
|
||||
import { afterUpdate } from "svelte";
|
||||
|
||||
type Headers = string[];
|
||||
type Data = (string | number)[][];
|
||||
@ -33,15 +34,18 @@
|
||||
export let datatype: Datatype | Datatype[];
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: SelectData;
|
||||
input: never;
|
||||
}>;
|
||||
|
||||
export let loading_status: LoadingStatus;
|
||||
|
||||
function handle_change(): void {
|
||||
dispatch("change", value);
|
||||
gradio.dispatch("change");
|
||||
if (!value_is_output) {
|
||||
dispatch("input");
|
||||
gradio.dispatch("input");
|
||||
}
|
||||
}
|
||||
afterUpdate(() => {
|
||||
@ -75,7 +79,7 @@
|
||||
on:change={({ detail }) => {
|
||||
value = detail;
|
||||
}}
|
||||
on:select
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
editable
|
||||
{wrap}
|
||||
{datatype}
|
||||
|
@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^",
|
||||
"@gradio/button": "workspace:^",
|
||||
"@gradio/markdown": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/upload": "workspace:^",
|
||||
"@gradio/utils": "workspace:^",
|
||||
|
@ -1,13 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { afterUpdate, tick } from "svelte";
|
||||
import { marked } from "marked";
|
||||
import DOMPurify from "dompurify";
|
||||
import render_math_in_element from "katex/dist/contrib/auto-render.js";
|
||||
import "katex/dist/katex.min.css";
|
||||
import { MarkdownCode } from "@gradio/markdown";
|
||||
|
||||
export let edit: boolean;
|
||||
export let value: string | number = "";
|
||||
export let el: HTMLInputElement | null;
|
||||
export let header = false;
|
||||
export let datatype:
|
||||
| "str"
|
||||
@ -22,34 +17,15 @@
|
||||
display: boolean;
|
||||
}[];
|
||||
|
||||
let span: HTMLSpanElement;
|
||||
let mounted = false;
|
||||
|
||||
$: mounted &&
|
||||
latex_delimiters.length > 0 &&
|
||||
render_math_in_element(span, {
|
||||
delimiters: latex_delimiters,
|
||||
throwOnError: false
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
if (datatype == "markdown") {
|
||||
tick().then(() => {
|
||||
requestAnimationFrame(() => {
|
||||
span.innerHTML = DOMPurify.sanitize(marked.parse(value.toString()));
|
||||
mounted = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
export let el: HTMLInputElement | null;
|
||||
</script>
|
||||
|
||||
{#if edit}
|
||||
<input
|
||||
bind:this={el}
|
||||
class:header
|
||||
tabindex="-1"
|
||||
{value}
|
||||
bind:this={el}
|
||||
on:keydown
|
||||
on:blur={({ currentTarget }) => {
|
||||
value = currentTarget.value;
|
||||
@ -57,9 +33,16 @@
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<span on:dblclick tabindex="-1" role="button" class:edit bind:this={span}>
|
||||
|
||||
<span on:dblclick tabindex="-1" role="button" class:edit>
|
||||
{#if datatype === "html"}
|
||||
{@html value}
|
||||
{:else if datatype === "markdown"}
|
||||
<MarkdownCode
|
||||
message={value.toLocaleString()}
|
||||
{latex_delimiters}
|
||||
chatbot={false}
|
||||
/>
|
||||
{:else}
|
||||
{value}
|
||||
{/if}
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { createEventDispatcher, tick } from "svelte";
|
||||
import { dsvFormat } from "d3-dsv";
|
||||
import { dequal } from "dequal/lite";
|
||||
|
||||
import { copy } from "@gradio/utils";
|
||||
import { Upload } from "@gradio/upload";
|
||||
import { BaseButton } from "@gradio/button/static";
|
||||
import EditableCell from "./EditableCell.svelte";
|
||||
@ -534,13 +534,18 @@
|
||||
on:touchstart={handle_click_outside}
|
||||
/>
|
||||
|
||||
<div class:label={label && label.length !== 0}>
|
||||
<div class:label={label && label.length !== 0} use:copy>
|
||||
{#if label && label.length !== 0}
|
||||
<p>
|
||||
{label}
|
||||
</p>
|
||||
{/if}
|
||||
<div class="table-wrap" class:dragging class:no-wrap={!wrap} style="max-height: {typeof height === undefined ? 'auto' : height + 'px'};">
|
||||
<div
|
||||
class="table-wrap"
|
||||
class:dragging
|
||||
class:no-wrap={!wrap}
|
||||
style="max-height: {typeof height === undefined ? 'auto' : height + 'px'};"
|
||||
>
|
||||
<Upload
|
||||
flex={false}
|
||||
center={false}
|
||||
|
@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { afterUpdate } from "svelte";
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import Table from "../shared";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
import { createEventDispatcher, afterUpdate } from "svelte";
|
||||
|
||||
type Headers = string[];
|
||||
type Data = (string | number)[][];
|
||||
@ -26,6 +27,11 @@
|
||||
export let datatype: Datatype | Datatype[];
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: SelectData;
|
||||
input: never;
|
||||
}>;
|
||||
export let latex_delimiters: {
|
||||
left: string;
|
||||
right: string;
|
||||
@ -33,14 +39,12 @@
|
||||
}[];
|
||||
export let height: number | undefined = undefined;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let loading_status: LoadingStatus;
|
||||
|
||||
function handle_change(): void {
|
||||
dispatch("change", value);
|
||||
gradio.dispatch("change");
|
||||
if (!value_is_output) {
|
||||
dispatch("input");
|
||||
gradio.dispatch("input");
|
||||
}
|
||||
}
|
||||
afterUpdate(() => {
|
||||
@ -74,7 +78,7 @@
|
||||
on:change={({ detail }) => {
|
||||
value = detail;
|
||||
}}
|
||||
on:select
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
{wrap}
|
||||
{datatype}
|
||||
{latex_delimiters}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import Dropdown from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -21,6 +22,13 @@
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let allow_custom_value = false;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
input: never;
|
||||
select: SelectData;
|
||||
blur: never;
|
||||
focus: never;
|
||||
}>;
|
||||
|
||||
if (multiselect && !value) {
|
||||
value = [];
|
||||
@ -51,10 +59,10 @@
|
||||
{show_label}
|
||||
{allow_custom_value}
|
||||
{container}
|
||||
on:change
|
||||
on:input
|
||||
on:select
|
||||
on:blur
|
||||
on:focus
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
on:blur={() => gradio.dispatch("blur")}
|
||||
on:focus={() => gradio.dispatch("focus")}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -67,7 +67,7 @@
|
||||
dispatch("select", {
|
||||
index: choices.indexOf(option),
|
||||
value: option,
|
||||
selected: true,
|
||||
selected: true
|
||||
});
|
||||
}
|
||||
value = value;
|
||||
@ -81,7 +81,7 @@
|
||||
dispatch("select", {
|
||||
index: choices.indexOf(option),
|
||||
value: option,
|
||||
selected: false,
|
||||
selected: false
|
||||
});
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@
|
||||
dispatch("select", {
|
||||
index: choices.indexOf(option),
|
||||
value: option,
|
||||
selected: true,
|
||||
selected: true
|
||||
});
|
||||
filterInput.blur();
|
||||
}
|
||||
@ -151,7 +151,7 @@
|
||||
dispatch("select", {
|
||||
index: choices.indexOf(value),
|
||||
value: value,
|
||||
selected: true,
|
||||
selected: true
|
||||
});
|
||||
}
|
||||
inputValue = activeOption;
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import Dropdown from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -20,6 +21,13 @@
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let allow_custom_value = false;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
input: never;
|
||||
select: SelectData;
|
||||
blur: never;
|
||||
focus: never;
|
||||
}>;
|
||||
|
||||
if (multiselect && !value) {
|
||||
value = [];
|
||||
@ -50,11 +58,11 @@
|
||||
{show_label}
|
||||
{allow_custom_value}
|
||||
{container}
|
||||
on:change
|
||||
on:input
|
||||
on:select
|
||||
on:blur
|
||||
on:focus
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
on:blur={() => gradio.dispatch("blur")}
|
||||
on:focus={() => gradio.dispatch("focus")}
|
||||
disabled
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import { getContext } from "svelte";
|
||||
import FileUpload from "./FileUpload.svelte";
|
||||
import { blobToBase64 } from "@gradio/upload";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
@ -14,6 +15,7 @@
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
|
||||
import { _ } from "svelte-i18n";
|
||||
import type { S } from "@storybook/theming/dist/create-c2b2ce6d";
|
||||
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
@ -34,6 +36,13 @@
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let height: number | undefined = undefined;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
error: string;
|
||||
upload: never;
|
||||
clear: never;
|
||||
select: SelectData;
|
||||
}>;
|
||||
|
||||
const upload_files =
|
||||
getContext<typeof default_upload_files>("upload_files") ??
|
||||
@ -44,17 +53,11 @@
|
||||
let dragging = false;
|
||||
let pending_upload = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: undefined;
|
||||
error: string;
|
||||
upload: undefined;
|
||||
}>();
|
||||
|
||||
$: {
|
||||
if (JSON.stringify(_value) !== JSON.stringify(old_value)) {
|
||||
old_value = _value;
|
||||
if (_value === null) {
|
||||
dispatch("change");
|
||||
gradio.dispatch("change");
|
||||
pending_upload = false;
|
||||
} else if (
|
||||
!(Array.isArray(_value) ? _value : [_value]).every(
|
||||
@ -62,7 +65,7 @@
|
||||
)
|
||||
) {
|
||||
pending_upload = false;
|
||||
dispatch("change");
|
||||
gradio.dispatch("change");
|
||||
} else if (mode === "interactive") {
|
||||
let files = (Array.isArray(_value) ? _value : [_value]).map(
|
||||
(file_data) => file_data.blob!
|
||||
@ -96,8 +99,8 @@
|
||||
);
|
||||
old_value = _value = normalise_file(value, root, root_url);
|
||||
}
|
||||
dispatch("change");
|
||||
dispatch("upload");
|
||||
gradio.dispatch("change");
|
||||
gradio.dispatch("upload");
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -134,8 +137,8 @@
|
||||
{height}
|
||||
on:change={({ detail }) => (value = detail)}
|
||||
on:drag={({ detail }) => (dragging = detail)}
|
||||
on:clear
|
||||
on:select
|
||||
on:clear={() => gradio.dispatch("clear")}
|
||||
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
||||
>
|
||||
<UploadText type="file" />
|
||||
</FileUpload>
|
||||
|
@ -26,7 +26,7 @@
|
||||
on:click={() =>
|
||||
dispatch("select", {
|
||||
value: file.orig_name || file.name,
|
||||
index: i,
|
||||
index: i
|
||||
})}
|
||||
>
|
||||
<td>
|
||||
@ -45,7 +45,7 @@
|
||||
{@html display_file_size(file)} ⇣
|
||||
</a>
|
||||
{:else}
|
||||
{$_('file.uploading')}
|
||||
{$_("file.uploading")}
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import { getContext } from "svelte";
|
||||
import File from "./File.svelte";
|
||||
import { blobToBase64 } from "@gradio/upload";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
@ -33,6 +34,13 @@
|
||||
export let container = true;
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
error: string;
|
||||
upload: never;
|
||||
clear: never;
|
||||
select: SelectData;
|
||||
}>;
|
||||
|
||||
const upload_files =
|
||||
getContext<typeof default_upload_files>("upload_files") ??
|
||||
@ -43,17 +51,11 @@
|
||||
let dragging = false;
|
||||
let pending_upload = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: undefined;
|
||||
error: string;
|
||||
upload: undefined;
|
||||
}>();
|
||||
|
||||
$: {
|
||||
if (JSON.stringify(_value) !== JSON.stringify(old_value)) {
|
||||
old_value = _value;
|
||||
if (_value === null) {
|
||||
dispatch("change");
|
||||
gradio.dispatch("change");
|
||||
pending_upload = false;
|
||||
} else if (
|
||||
!(Array.isArray(_value) ? _value : [_value]).every(
|
||||
@ -61,7 +63,7 @@
|
||||
)
|
||||
) {
|
||||
pending_upload = false;
|
||||
dispatch("change");
|
||||
gradio.dispatch("change");
|
||||
} else if (mode === "interactive") {
|
||||
let files = (Array.isArray(_value) ? _value : [_value]).map(
|
||||
(file_data) => file_data.blob!
|
||||
@ -95,8 +97,8 @@
|
||||
);
|
||||
old_value = _value = normalise_file(value, root, root_url);
|
||||
}
|
||||
dispatch("change");
|
||||
dispatch("upload");
|
||||
gradio.dispatch("change");
|
||||
gradio.dispatch("upload");
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -121,5 +123,12 @@
|
||||
: loading_status?.status || "complete"}
|
||||
/>
|
||||
|
||||
<File on:select {selectable} value={_value} {label} {show_label} {height} />
|
||||
<File
|
||||
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
||||
{selectable}
|
||||
value={_value}
|
||||
{label}
|
||||
{show_label}
|
||||
{height}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -127,7 +127,7 @@
|
||||
if (selected_image !== null) {
|
||||
dispatch("select", {
|
||||
index: selected_image,
|
||||
value: _value?.[selected_image][1],
|
||||
value: _value?.[selected_image][1]
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -160,7 +160,7 @@
|
||||
|
||||
container_element?.scrollTo({
|
||||
left: pos < 0 ? 0 : pos,
|
||||
behavior: "smooth",
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
|
||||
@ -338,7 +338,9 @@
|
||||
.thumbnail-item {
|
||||
--ring-color: transparent;
|
||||
position: relative;
|
||||
box-shadow: 0 0 0 2px var(--ring-color), var(--shadow-drop);
|
||||
box-shadow:
|
||||
0 0 0 2px var(--ring-color),
|
||||
var(--shadow-drop);
|
||||
border: 1px solid var(--border-color-primary);
|
||||
border-radius: var(--button-small-radius);
|
||||
background: var(--background-fill-secondary);
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, ShareData, SelectData } from "@gradio/utils";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import Gallery from "./Gallery.svelte";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
@ -27,6 +28,11 @@
|
||||
"cover";
|
||||
export let show_share_button = false;
|
||||
export let show_download_button = false;
|
||||
export let gradio: Gradio<{
|
||||
select: SelectData;
|
||||
share: ShareData;
|
||||
error: string;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block
|
||||
@ -43,9 +49,9 @@
|
||||
>
|
||||
<StatusTracker {...loading_status} />
|
||||
<Gallery
|
||||
on:select
|
||||
on:share
|
||||
on:error
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
on:share={(e) => gradio.dispatch("share", e.detail)}
|
||||
on:error={(e) => gradio.dispatch("error", e.detail)}
|
||||
{label}
|
||||
{value}
|
||||
{show_label}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { Meta, Template, Story } from "@storybook/addon-svelte-csf";
|
||||
import HighlightedText from "./static";
|
||||
import { Gradio } from "../app/src/gradio_helper";
|
||||
</script>
|
||||
|
||||
<Meta title="Components/HighlightedText" component={HighlightedText} />
|
||||
@ -12,6 +13,13 @@
|
||||
["dogs", "-"],
|
||||
["elephants", "+"]
|
||||
]}
|
||||
gradio={new Gradio(
|
||||
0,
|
||||
document.body,
|
||||
"light",
|
||||
"1.1.1",
|
||||
"http://localhost:7860"
|
||||
)}
|
||||
{...args}
|
||||
/>
|
||||
</Template>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import HighlightedText from "./Highlightedtext.svelte";
|
||||
import { Block, BlockLabel, Empty } from "@gradio/atoms";
|
||||
import { TextHighlight } from "@gradio/icons";
|
||||
@ -19,6 +19,10 @@
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let selectable = false;
|
||||
export let gradio: Gradio<{
|
||||
select: SelectData;
|
||||
change: never;
|
||||
}>;
|
||||
|
||||
$: if (!color_map && Object.keys(color_map).length) {
|
||||
color_map = color_map;
|
||||
@ -26,12 +30,10 @@
|
||||
|
||||
export let loading_status: LoadingStatus;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: {
|
||||
if (value !== old_value) {
|
||||
old_value = value;
|
||||
dispatch("change");
|
||||
gradio.dispatch("change");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -57,7 +59,13 @@
|
||||
{/if}
|
||||
|
||||
{#if value}
|
||||
<HighlightedText on:select {selectable} {value} {show_legend} {color_map} />
|
||||
<HighlightedText
|
||||
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
||||
{selectable}
|
||||
{value}
|
||||
{show_legend}
|
||||
{color_map}
|
||||
/>
|
||||
{:else}
|
||||
<Empty>
|
||||
<TextHighlight />
|
||||
|
@ -10,7 +10,8 @@
|
||||
"main_changeset": true,
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^"
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/utils": "workspace:^"
|
||||
},
|
||||
"exports": {
|
||||
".": "./static/index.ts",
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
export let value: string;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import HTML from "./HTML.svelte";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
@ -11,10 +11,11 @@
|
||||
export let visible = true;
|
||||
export let value = "";
|
||||
export let loading_status: LoadingStatus;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
}>;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: label, dispatch("change");
|
||||
$: label, gradio.dispatch("change");
|
||||
</script>
|
||||
|
||||
<Block {visible} {elem_id} {elem_classes} container={false}>
|
||||
@ -26,7 +27,7 @@
|
||||
{elem_id}
|
||||
{elem_classes}
|
||||
{visible}
|
||||
on:change
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
|
@ -34,21 +34,25 @@ describe("Image", () => {
|
||||
afterEach(() => cleanup());
|
||||
|
||||
test("image change event trigger fires when value is changed and only fires once", async () => {
|
||||
const { component } = await render(Image, {
|
||||
const { component, listen } = await render(Image, {
|
||||
show_label: true,
|
||||
loading_status,
|
||||
mode: "dynamic",
|
||||
value:
|
||||
"https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png",
|
||||
root: "foo",
|
||||
root_url: null,
|
||||
streaming: false,
|
||||
pending: false,
|
||||
source: "upload"
|
||||
source: "upload",
|
||||
label: "Test Label",
|
||||
width: 224,
|
||||
height: 224,
|
||||
mirror_webcam: false,
|
||||
shape: [224, 224],
|
||||
brush_color: "#000000",
|
||||
brush_radius: 5,
|
||||
mask_opacity: 0.5
|
||||
});
|
||||
|
||||
const mock = spy();
|
||||
component.$on("change", mock);
|
||||
const mock = listen("change");
|
||||
|
||||
component.value =
|
||||
"https://github.com/gradio-app/gradio/blob/main/test/test_files/cheetah1.jpg";
|
||||
|
@ -1,7 +1,7 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio, SelectData, ShareData } from "@gradio/utils";
|
||||
import Image from "./Image.svelte";
|
||||
|
||||
import { Block } from "@gradio/atoms";
|
||||
@ -32,13 +32,19 @@
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: undefined;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
error: string;
|
||||
}>();
|
||||
edit: never;
|
||||
stream: never;
|
||||
drag: never;
|
||||
upload: never;
|
||||
clear: never;
|
||||
select: SelectData;
|
||||
share: ShareData;
|
||||
}>;
|
||||
|
||||
$: value, dispatch("change");
|
||||
$: value, gradio.dispatch("change");
|
||||
let dragging: boolean;
|
||||
const FIXED_HEIGHT = 240;
|
||||
|
||||
@ -70,17 +76,17 @@
|
||||
{tool}
|
||||
{selectable}
|
||||
{mask_opacity}
|
||||
on:edit
|
||||
on:clear
|
||||
on:stream
|
||||
on:edit={() => gradio.dispatch("edit")}
|
||||
on:clear={() => gradio.dispatch("clear")}
|
||||
on:stream={() => gradio.dispatch("stream")}
|
||||
on:drag={({ detail }) => (dragging = detail)}
|
||||
on:upload
|
||||
on:select
|
||||
on:share
|
||||
on:upload={() => gradio.dispatch("upload")}
|
||||
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
||||
on:share={({ detail }) => gradio.dispatch("share", detail)}
|
||||
on:error={({ detail }) => {
|
||||
loading_status = loading_status || {};
|
||||
loading_status.status = "error";
|
||||
dispatch("error", detail);
|
||||
gradio.dispatch("error", detail);
|
||||
}}
|
||||
{label}
|
||||
{show_label}
|
||||
|
@ -32,7 +32,7 @@
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: true,
|
||||
audio: include_audio,
|
||||
audio: include_audio
|
||||
});
|
||||
video_source.srcObject = stream;
|
||||
video_source.muted = true;
|
||||
@ -81,7 +81,7 @@
|
||||
dispatch("capture", {
|
||||
data: e.target.result,
|
||||
name: "sample." + mimeType.substring(6),
|
||||
is_example: false,
|
||||
is_example: false
|
||||
});
|
||||
dispatch("stop_recording");
|
||||
}
|
||||
@ -102,7 +102,7 @@
|
||||
return;
|
||||
}
|
||||
media_recorder = new MediaRecorder(stream, {
|
||||
mimeType: mimeType,
|
||||
mimeType: mimeType
|
||||
});
|
||||
media_recorder.addEventListener("dataavailable", function (e) {
|
||||
recorded_blobs.push(e.data);
|
||||
|
@ -1,7 +1,7 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import StaticImage from "./ImagePreview.svelte";
|
||||
|
||||
import { Block } from "@gradio/atoms";
|
||||
@ -26,13 +26,14 @@
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let show_share_button = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: undefined;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
error: string;
|
||||
}>();
|
||||
select: SelectData;
|
||||
share: ShareData;
|
||||
}>;
|
||||
|
||||
$: value, dispatch("change");
|
||||
$: value, gradio.dispatch("change");
|
||||
let dragging: boolean;
|
||||
|
||||
$: value = !value ? null : value;
|
||||
@ -54,9 +55,9 @@
|
||||
>
|
||||
<StatusTracker {...loading_status} />
|
||||
<StaticImage
|
||||
on:select
|
||||
on:share
|
||||
on:error
|
||||
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
||||
on:share={({ detail }) => gradio.dispatch("share", detail)}
|
||||
on:error={({ detail }) => gradio.dispatch("error", detail)}
|
||||
{value}
|
||||
{label}
|
||||
{show_label}
|
||||
|
@ -10,7 +10,8 @@
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^",
|
||||
"@gradio/icons": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^"
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/utils": "workspace:^"
|
||||
},
|
||||
"main_changeset": true,
|
||||
"exports": {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import JSON from "./JSON.svelte";
|
||||
import { Block, BlockLabel } from "@gradio/atoms";
|
||||
import { JSON as JSONIcon } from "@gradio/icons";
|
||||
@ -19,13 +19,14 @@
|
||||
export let container = true;
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
}>;
|
||||
|
||||
$: {
|
||||
if (value !== old_value) {
|
||||
old_value = value;
|
||||
dispatch("change");
|
||||
gradio.dispatch("change");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -30,7 +30,10 @@ test("gr.Label default value and label rendered with confidences", async ({
|
||||
},
|
||||
label: "My Label",
|
||||
show_label: true,
|
||||
loading_status: loading_status
|
||||
loading_status: loading_status,
|
||||
gradio: {
|
||||
dispatch() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
await expect(component).toContainText("My Label");
|
||||
@ -52,7 +55,10 @@ test("gr.Label hides label when show_label=false", async ({ mount, page }) => {
|
||||
},
|
||||
label: "My Label",
|
||||
show_label: false,
|
||||
loading_status: loading_status
|
||||
loading_status: loading_status,
|
||||
gradio: {
|
||||
dispatch() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
await expect(component.getByTestId("block-label")).toBeHidden();
|
||||
@ -68,7 +74,10 @@ test("gr.Label confidence bars not rendered without confidences", async ({
|
||||
},
|
||||
label: "My Label",
|
||||
show_label: true,
|
||||
loading_status: loading_status
|
||||
loading_status: loading_status,
|
||||
gradio: {
|
||||
dispatch() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
await expect(component).toContainText("My Label");
|
||||
@ -79,7 +88,14 @@ test("gr.Label confidence bars trigger select event when clicked", async ({
|
||||
mount,
|
||||
page
|
||||
}) => {
|
||||
const select = spy();
|
||||
const events = {
|
||||
select: [0, null]
|
||||
};
|
||||
|
||||
function event(name: "select", value: any) {
|
||||
events[name] = [events[name][0]! + 1, value];
|
||||
}
|
||||
|
||||
const component = await mount(Label, {
|
||||
props: {
|
||||
value: {
|
||||
@ -91,20 +107,27 @@ test("gr.Label confidence bars trigger select event when clicked", async ({
|
||||
},
|
||||
label: "My Label",
|
||||
show_label: true,
|
||||
loading_status: loading_status
|
||||
},
|
||||
on: {
|
||||
select: select
|
||||
loading_status: loading_status,
|
||||
gradio: {
|
||||
dispatch: event
|
||||
}
|
||||
}
|
||||
});
|
||||
await expect(component).toContainText("My Label");
|
||||
await component.getByTestId("Bad-confidence-set").click();
|
||||
expect(select.callCount).toEqual(1);
|
||||
expect(select.calls[0][0]).toEqual({ index: 1, value: "Bad" });
|
||||
expect(events.select[0]).toEqual(1);
|
||||
expect(events.select[1]).toEqual({ index: 1, value: "Bad" });
|
||||
});
|
||||
|
||||
test("gr.Label triggers change event", async ({ mount, page }) => {
|
||||
const change = spy();
|
||||
const events = {
|
||||
change: 0
|
||||
};
|
||||
|
||||
function event(name: "change") {
|
||||
events[name] += 1;
|
||||
}
|
||||
|
||||
const component = await mount(Label, {
|
||||
props: {
|
||||
value: {
|
||||
@ -116,12 +139,13 @@ test("gr.Label triggers change event", async ({ mount, page }) => {
|
||||
},
|
||||
label: "My Label",
|
||||
show_label: true,
|
||||
loading_status: loading_status
|
||||
},
|
||||
on: {
|
||||
change: change
|
||||
loading_status: loading_status,
|
||||
gradio: {
|
||||
dispatch: event
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await component.update({
|
||||
props: {
|
||||
value: {
|
||||
@ -133,5 +157,5 @@ test("gr.Label triggers change event", async ({ mount, page }) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(change.callCount).toEqual(1);
|
||||
expect(events.change).toEqual(2);
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import Label from "./Label.svelte";
|
||||
import { LineChart as LabelIcon } from "@gradio/icons";
|
||||
import { Block, BlockLabel, Empty } from "@gradio/atoms";
|
||||
@ -22,11 +22,13 @@
|
||||
export let loading_status: LoadingStatus;
|
||||
export let show_label = true;
|
||||
export let selectable = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: SelectData;
|
||||
}>;
|
||||
|
||||
$: ({ confidences, label: _label } = value);
|
||||
$: _label, confidences, dispatch("change");
|
||||
$: _label, confidences, gradio.dispatch("change");
|
||||
</script>
|
||||
|
||||
<Block
|
||||
@ -44,7 +46,12 @@
|
||||
<BlockLabel Icon={LabelIcon} {label} disable={container === false} />
|
||||
{/if}
|
||||
{#if _label !== undefined && _label !== null}
|
||||
<Label on:select {selectable} {value} {color} />
|
||||
<Label
|
||||
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
||||
{selectable}
|
||||
{value}
|
||||
{color}
|
||||
/>
|
||||
{:else}
|
||||
<Empty unpadded_box={true}><LabelIcon /></Empty>
|
||||
{/if}
|
||||
|
@ -18,10 +18,14 @@
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/utils": "workspace:^",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/katex": "^0.16.0",
|
||||
"@types/prismjs": "1.26.0",
|
||||
"dompurify": "^3.0.3",
|
||||
"katex": "^0.16.7",
|
||||
"marked": "^7.0.0"
|
||||
"marked": "^7.0.0",
|
||||
"marked-highlight": "^2.0.1",
|
||||
"prismjs": "1.29.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { afterUpdate, createEventDispatcher, tick } from "svelte";
|
||||
import { marked } from "marked";
|
||||
import DOMPurify from "dompurify";
|
||||
import render_math_in_element from "katex/dist/contrib/auto-render.js";
|
||||
import "katex/dist/katex.min.css";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { copy } from "@gradio/utils";
|
||||
|
||||
import MarkdownCode from "./MarkdownCode.svelte";
|
||||
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
@ -12,8 +11,6 @@
|
||||
export let min_height = false;
|
||||
export let rtl = false;
|
||||
|
||||
let div: HTMLDivElement;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: value, dispatch("change");
|
||||
@ -23,24 +20,6 @@
|
||||
right: string;
|
||||
display: boolean;
|
||||
}[];
|
||||
|
||||
let mounted = false;
|
||||
|
||||
$: mounted &&
|
||||
latex_delimiters.length > 0 &&
|
||||
render_math_in_element(div, {
|
||||
delimiters: latex_delimiters,
|
||||
throwOnError: false
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
tick().then(() => {
|
||||
requestAnimationFrame(() => {
|
||||
div.innerHTML = DOMPurify.sanitize(marked.parse(value));
|
||||
mounted = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
@ -50,8 +29,10 @@
|
||||
class:hide={!visible}
|
||||
data-testid="markdown"
|
||||
dir={rtl ? "rtl" : "ltr"}
|
||||
bind:this={div}
|
||||
></div>
|
||||
use:copy
|
||||
>
|
||||
<MarkdownCode message={value} {latex_delimiters} chatbot={false} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div :global(.math.inline) {
|
||||
|
105
js/markdown/static/MarkdownCode.svelte
Normal file
105
js/markdown/static/MarkdownCode.svelte
Normal file
@ -0,0 +1,105 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import DOMPurify from "dompurify";
|
||||
import render_math_in_element from "katex/dist/contrib/auto-render.js";
|
||||
import "katex/dist/katex.min.css";
|
||||
|
||||
import { marked } from "./utils";
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
import "./prism.css";
|
||||
// import "./prism-dark.css";
|
||||
|
||||
// const code_highlight_css = {
|
||||
// light: (): Promise<typeof import("prismjs/themes/prism.css")> =>
|
||||
// import("prismjs/themes/prism.css"),
|
||||
// dark: (): Promise<typeof import("prismjs/themes/prism.css")> =>
|
||||
// import("prismjs/themes/prism-dark.css")
|
||||
// };
|
||||
|
||||
export let chatbot = true;
|
||||
export let message: string;
|
||||
export let latex_delimiters: {
|
||||
left: string;
|
||||
right: string;
|
||||
display: boolean;
|
||||
}[];
|
||||
|
||||
let el: HTMLSpanElement;
|
||||
|
||||
DOMPurify.addHook("afterSanitizeAttributes", function (node) {
|
||||
if ("target" in node) {
|
||||
node.setAttribute("target", "_blank");
|
||||
node.setAttribute("rel", "noopener noreferrer");
|
||||
}
|
||||
});
|
||||
|
||||
$: el && html && render_html(message);
|
||||
|
||||
$: html =
|
||||
message && message.trim() ? DOMPurify.sanitize(marked.parse(message)) : "";
|
||||
|
||||
async function render_html(value: string): Promise<void> {
|
||||
if (latex_delimiters.length > 0) {
|
||||
render_math_in_element(el, {
|
||||
delimiters: latex_delimiters,
|
||||
throwOnError: false
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<span class:chatbot bind:this={el} class="md">
|
||||
{@html html}
|
||||
</span>
|
||||
|
||||
<style>
|
||||
span :global(div[class*="code_wrap"]) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* KaTeX */
|
||||
span :global(span.katex) {
|
||||
font-size: var(--text-lg);
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
span :global(div[class*="code_wrap"] > button) {
|
||||
position: absolute;
|
||||
top: var(--spacing-sm);
|
||||
right: var(--spacing-sm);
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
border-bottom-left-radius: var(--radius-sm);
|
||||
padding: 5px;
|
||||
padding: var(--spacing-md);
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
span :global(code > button > span) {
|
||||
position: absolute;
|
||||
top: var(--spacing-sm);
|
||||
right: var(--spacing-sm);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
span :global(.check) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
z-index: var(--layer-top);
|
||||
transition: opacity 0.2s;
|
||||
background: var(--background-fill-primary);
|
||||
padding: var(--size-1);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--body-text-color);
|
||||
}
|
||||
|
||||
span :global(pre) {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import Markdown from "./Markdown.svelte";
|
||||
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -13,15 +13,16 @@
|
||||
export let value = "";
|
||||
export let loading_status: LoadingStatus;
|
||||
export let rtl = false;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
}>;
|
||||
export let latex_delimiters: {
|
||||
left: string;
|
||||
right: string;
|
||||
display: boolean;
|
||||
}[];
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: label, dispatch("change");
|
||||
$: label, gradio.dispatch("change");
|
||||
</script>
|
||||
|
||||
<Block {visible} {elem_id} {elem_classes} container={false}>
|
||||
@ -34,8 +35,8 @@
|
||||
{elem_classes}
|
||||
{visible}
|
||||
{rtl}
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
{latex_delimiters}
|
||||
on:change
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
|
@ -1 +1,2 @@
|
||||
export { default } from "./StaticMarkdown.svelte";
|
||||
export { default as MarkdownCode } from "./MarkdownCode.svelte";
|
||||
|
68
js/markdown/static/prism-dark.css
Normal file
68
js/markdown/static/prism-dark.css
Normal file
@ -0,0 +1,68 @@
|
||||
.dark .md .token.comment,
|
||||
.dark .md .token.prolog,
|
||||
.dark .md .token.doctype,
|
||||
.dark .md .token.cdata {
|
||||
color: hsl(30, 20%, 50%);
|
||||
}
|
||||
|
||||
.dark .md .token.punctuation {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.dark .md .token.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.dark .md .token.property,
|
||||
.dark .md .token.tag,
|
||||
.dark .md .token.boolean,
|
||||
.dark .md .token.number,
|
||||
.dark .md .token.constant,
|
||||
.dark .md .token.symbol {
|
||||
color: hsl(350, 40%, 70%);
|
||||
}
|
||||
|
||||
.dark .md .token.selector,
|
||||
.dark .md .token.attr-name,
|
||||
.dark .md .token.string,
|
||||
.dark .md .token.char,
|
||||
.dark .md .token.builtin,
|
||||
.dark .md .token.inserted {
|
||||
color: hsl(75, 70%, 60%);
|
||||
}
|
||||
|
||||
.dark .md .token.operator,
|
||||
.dark .md .token.entity,
|
||||
.dark .md .token.url,
|
||||
.dark .md .language-css .token.string,
|
||||
.dark .md .style .token.string,
|
||||
.dark .md .token.variable {
|
||||
color: hsl(40, 90%, 60%);
|
||||
}
|
||||
|
||||
.dark .md .token.atrule,
|
||||
.dark .md .token.attr-value,
|
||||
.dark .md .token.keyword {
|
||||
color: hsl(350, 40%, 70%);
|
||||
}
|
||||
|
||||
.dark .md .token.regex,
|
||||
.dark .md .token.important {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.dark .md .token.important,
|
||||
.dark .md .token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.dark .md .token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.dark .md .token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.dark .md .token.deleted {
|
||||
color: red;
|
||||
}
|
210
js/markdown/static/prism.css
Normal file
210
js/markdown/static/prism.css
Normal file
@ -0,0 +1,210 @@
|
||||
Tables */ table,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
margin-top: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* .message-wrap :global(pre[class*="language-"]),
|
||||
.message-wrap :global(pre) {
|
||||
border: none;
|
||||
background: none;
|
||||
position: relative;
|
||||
direction: ltr;
|
||||
white-space: no-wrap;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.message-wrap :global(code) {
|
||||
} */
|
||||
|
||||
/* .message-wrap :global(div[class*="code_wrap"]) {
|
||||
|
||||
} */
|
||||
|
||||
.md code,
|
||||
.md pre {
|
||||
background: none;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
tab-size: 2;
|
||||
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.md pre[class*="language-"]::-moz-selection,
|
||||
.md pre[class*="language-"] ::-moz-selection,
|
||||
.md code[class*="language-"]::-moz-selection,
|
||||
.md code[class*="language-"] ::-moz-selection {
|
||||
}
|
||||
|
||||
.md pre[class*="language-"]::selection,
|
||||
.md pre[class*="language-"] ::selection,
|
||||
.md code[class*="language-"]::selection,
|
||||
.md code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
.md pre {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
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-lg) var(--spacing-xl);
|
||||
font-family: var(--font-mono);
|
||||
/* font-size: var(--text-sm) !important; */
|
||||
display: block;
|
||||
white-space: pre;
|
||||
border-radius: var(--radius-sm);
|
||||
text-shadow: none;
|
||||
border-radius: var(--radius-sm);
|
||||
/* font-size: 85%; */
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
}
|
||||
.prose pre > code {
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
.md :not(pre) > code[class*="language-"] {
|
||||
padding: 0.1em;
|
||||
border-radius: var(--radius-xs);
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.md .token.comment,
|
||||
.md .token.prolog,
|
||||
.md .token.doctype,
|
||||
.md .token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.md .token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.md .token.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.md .token.property,
|
||||
.md .token.tag,
|
||||
.md .token.boolean,
|
||||
.md .token.number,
|
||||
.md .token.constant,
|
||||
.md .token.symbol,
|
||||
.md .token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.md .token.selector,
|
||||
.md .token.attr-name,
|
||||
.md .token.string,
|
||||
.md .token.char,
|
||||
.md .token.builtin,
|
||||
.md .token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.md .token.atrule,
|
||||
.md .token.attr-value,
|
||||
.md .token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.md .token.function,
|
||||
.md .token.class-name {
|
||||
color: #dd4a68;
|
||||
}
|
||||
|
||||
.md .token.regex,
|
||||
.md .token.important,
|
||||
.md .token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.md .token.important,
|
||||
.md .token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.md .token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.md .token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.dark .md .token.comment,
|
||||
.dark .md .token.prolog,
|
||||
.dark .md .token.cdata {
|
||||
color: hsl(220, 10%, 40%);
|
||||
}
|
||||
|
||||
.dark .md .token.doctype,
|
||||
.dark .md .token.punctuation,
|
||||
.dark .md .token.entity {
|
||||
color: hsl(220, 14%, 71%);
|
||||
}
|
||||
|
||||
.dark .md .token.attr-name,
|
||||
.dark .md .token.class-name,
|
||||
.dark .md .token.boolean,
|
||||
.dark .md .token.constant,
|
||||
.dark .md .token.number,
|
||||
.dark .md .token.atrule {
|
||||
color: hsl(29, 54%, 61%);
|
||||
}
|
||||
|
||||
.dark .md .token.keyword {
|
||||
color: hsl(286, 60%, 67%);
|
||||
}
|
||||
|
||||
.dark .md .token.property,
|
||||
.dark .md .token.tag,
|
||||
.dark .md .token.symbol,
|
||||
.dark .md .token.deleted,
|
||||
.dark .md .token.important {
|
||||
color: hsl(355, 65%, 65%);
|
||||
}
|
||||
|
||||
.dark .md .token.selector,
|
||||
.dark .md .token.string,
|
||||
.dark .md .token.char,
|
||||
.dark .md .token.builtin,
|
||||
.dark .md .token.inserted,
|
||||
.dark .md .token.regex,
|
||||
.dark .md .token.attr-value,
|
||||
.dark .md .token.attr-value > .token.punctuation {
|
||||
color: hsl(95, 38%, 62%);
|
||||
}
|
||||
|
||||
.dark .md .token.variable,
|
||||
.dark .md .token.operator,
|
||||
.dark .md .token.function {
|
||||
color: hsl(207, 82%, 66%);
|
||||
}
|
||||
|
||||
.dark .md .token.url {
|
||||
color: hsl(187, 47%, 55%);
|
||||
}
|
193
js/markdown/static/utils.ts
Normal file
193
js/markdown/static/utils.ts
Normal file
@ -0,0 +1,193 @@
|
||||
import { marked, type Renderer } from "marked";
|
||||
import { markedHighlight } from "marked-highlight";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/components/prism-python";
|
||||
import "prismjs/components/prism-latex";
|
||||
// import loadLanguages from "prismjs/components/";
|
||||
|
||||
// loadLanguages(["python", "latex"]);
|
||||
|
||||
import type { ActionReturn } from "svelte/action";
|
||||
|
||||
const COPY_ICON_CODE = `<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 32 32"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M28 10v18H10V10h18m0-2H10a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2Z"
|
||||
/><path fill="currentColor" d="M4 18H2V4a2 2 0 0 1 2-2h14v2H4Z" /></svg>`;
|
||||
const CHECK_ICON_CODE = `<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"><polyline points="20 6 9 17 4 12" /></svg>`;
|
||||
const COPY_BUTTON_CODE = `<button title="copy" class="copy_code_button">
|
||||
<span class="copy-text">${COPY_ICON_CODE}</span>
|
||||
<span class="check">${CHECK_ICON_CODE}</span>
|
||||
</button>`;
|
||||
|
||||
const escape_test = /[&<>"']/;
|
||||
const escape_replace = new RegExp(escape_test.source, "g");
|
||||
const escape_test_no_encode =
|
||||
/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/;
|
||||
const escape_replace_no_encode = new RegExp(escape_test_no_encode.source, "g");
|
||||
const escape_replacements: Record<string, any> = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'"
|
||||
};
|
||||
|
||||
const get_escape_replacement = (ch: string): string =>
|
||||
escape_replacements[ch] || "";
|
||||
|
||||
function escape(html: string, encode?: boolean): string {
|
||||
if (encode) {
|
||||
if (escape_test.test(html)) {
|
||||
return html.replace(escape_replace, get_escape_replacement);
|
||||
}
|
||||
} else {
|
||||
if (escape_test_no_encode.test(html)) {
|
||||
return html.replace(escape_replace_no_encode, get_escape_replacement);
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
const renderer: Partial<Omit<Renderer, "constructor" | "options">> = {
|
||||
code(
|
||||
this: Renderer,
|
||||
code: string,
|
||||
infostring: string | undefined,
|
||||
escaped: boolean
|
||||
) {
|
||||
const lang = (infostring ?? "").match(/\S*/)?.[0] ?? "";
|
||||
if (this.options.highlight) {
|
||||
const out = this.options.highlight(code, lang);
|
||||
if (out != null && out !== code) {
|
||||
escaped = true;
|
||||
code = out;
|
||||
}
|
||||
}
|
||||
|
||||
code = code.replace(/\n$/, "") + "\n";
|
||||
|
||||
if (!lang) {
|
||||
return (
|
||||
'<div class="code_wrap">' +
|
||||
COPY_BUTTON_CODE +
|
||||
"<pre><code>" +
|
||||
(escaped ? code : escape(code, true)) +
|
||||
"</code></pre></div>\n"
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
'<div class="code_wrap">' +
|
||||
COPY_BUTTON_CODE +
|
||||
'<pre><code class="' +
|
||||
this.options.langPrefix +
|
||||
escape(lang) +
|
||||
'">' +
|
||||
(escaped ? code : escape(code, true)) +
|
||||
"</code></pre></div>\n"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
marked.use(
|
||||
{
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
pedantic: false,
|
||||
headerIds: false,
|
||||
mangle: false
|
||||
},
|
||||
markedHighlight({
|
||||
highlight: (code: string, lang: string) => {
|
||||
if (Prism.languages[lang]) {
|
||||
return Prism.highlight(code, Prism.languages[lang], lang);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}),
|
||||
{ renderer }
|
||||
);
|
||||
|
||||
export function copy(node: HTMLDivElement): ActionReturn {
|
||||
node.addEventListener("click", handle_copy);
|
||||
|
||||
async function handle_copy(event: MouseEvent): Promise<void> {
|
||||
const path = event.composedPath() as HTMLButtonElement[];
|
||||
|
||||
const [copy_button] = path.filter(
|
||||
(e) => e?.tagName === "BUTTON" && e.classList.contains("copy_code_button")
|
||||
);
|
||||
|
||||
if (copy_button) {
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
const copy_text = copy_button.parentElement!.innerText.trim();
|
||||
const copy_sucess_button = Array.from(
|
||||
copy_button.children
|
||||
)[1] as HTMLDivElement;
|
||||
|
||||
const copied = await copy_to_clipboard(copy_text);
|
||||
|
||||
if (copied) copy_feedback(copy_sucess_button);
|
||||
|
||||
function copy_feedback(_copy_sucess_button: HTMLDivElement): void {
|
||||
_copy_sucess_button.style.opacity = "1";
|
||||
setTimeout(() => {
|
||||
_copy_sucess_button.style.opacity = "0";
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
destroy(): void {
|
||||
node.removeEventListener("click", handle_copy);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function copy_to_clipboard(value: string): Promise<boolean> {
|
||||
let copied = false;
|
||||
if ("clipboard" in navigator) {
|
||||
await navigator.clipboard.writeText(value);
|
||||
copied = true;
|
||||
} else {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = value;
|
||||
|
||||
textArea.style.position = "absolute";
|
||||
textArea.style.left = "-999999px";
|
||||
|
||||
document.body.prepend(textArea);
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
copied = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
copied = false;
|
||||
} finally {
|
||||
textArea.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
export { marked };
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { normalise_file } from "@gradio/upload";
|
||||
import Model3DUpload from "./Model3DUpload.svelte";
|
||||
@ -21,6 +22,10 @@
|
||||
export let container = true;
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let gradio: Gradio<{
|
||||
change: typeof value;
|
||||
clear: never;
|
||||
}>;
|
||||
|
||||
let _value: null | FileData;
|
||||
$: _value = normalise_file(value, root, root_url);
|
||||
@ -48,8 +53,8 @@
|
||||
value={_value}
|
||||
on:change={({ detail }) => (value = detail)}
|
||||
on:drag={({ detail }) => (dragging = detail)}
|
||||
on:change
|
||||
on:clear
|
||||
on:change={({ detail }) => gradio.dispatch("change", detail)}
|
||||
on:clear={() => gradio.dispatch("clear")}
|
||||
>
|
||||
<UploadText type="file" />
|
||||
</Model3DUpload>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, tick, afterUpdate, onMount } from "svelte";
|
||||
import { createEventDispatcher, tick, onMount } from "svelte";
|
||||
import { Upload, ModifyUpload } from "@gradio/upload";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { BlockLabel } from "@gradio/atoms";
|
||||
@ -10,18 +10,23 @@
|
||||
export let label = "";
|
||||
export let show_label: boolean;
|
||||
|
||||
let mounted = false;
|
||||
|
||||
onMount(() => {
|
||||
if (value != null) {
|
||||
addNewModel();
|
||||
}
|
||||
mounted = true;
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
if (value != null && value.is_file) {
|
||||
addNewModel();
|
||||
}
|
||||
$: ({ data, is_file, name } = value || {
|
||||
data: undefined,
|
||||
is_file: undefined,
|
||||
name: undefined
|
||||
});
|
||||
|
||||
$: canvas && mounted && data != null && is_file && addNewModel();
|
||||
|
||||
async function handle_upload({
|
||||
detail
|
||||
}: CustomEvent<FileData>): Promise<void> {
|
||||
@ -89,6 +94,7 @@
|
||||
url = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
BABYLON.SceneLoader.ShowLoadingScreen = false;
|
||||
BABYLON.SceneLoader.Append(
|
||||
url,
|
||||
"",
|
||||
@ -132,5 +138,6 @@
|
||||
width: var(--size-full);
|
||||
height: var(--size-full);
|
||||
object-fit: contain;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
@ -12,6 +12,7 @@
|
||||
"@gradio/icons": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/upload": "workspace:^",
|
||||
"@gradio/utils": "workspace:^",
|
||||
"babylonjs": "^4.2.1",
|
||||
"babylonjs-loaders": "^4.2.1"
|
||||
},
|
||||
|
@ -3,30 +3,39 @@
|
||||
import { BlockLabel, IconButton } from "@gradio/atoms";
|
||||
import { File, Download } from "@gradio/icons";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { onMount } from "svelte";
|
||||
import * as BABYLON from "babylonjs";
|
||||
import * as BABYLON_LOADERS from "babylonjs-loaders";
|
||||
|
||||
export let value: FileData | null;
|
||||
export let clearColor: [number, number, number, number] = [0, 0, 0, 0];
|
||||
export let label = "";
|
||||
export let show_label: boolean;
|
||||
|
||||
import { onMount, afterUpdate } from "svelte";
|
||||
import * as BABYLON from "babylonjs";
|
||||
import * as BABYLON_LOADERS from "babylonjs-loaders";
|
||||
|
||||
BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS = true;
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let scene: BABYLON.Scene;
|
||||
let engine: BABYLON.Engine | null;
|
||||
|
||||
let mounted = false;
|
||||
|
||||
onMount(() => {
|
||||
engine = new BABYLON.Engine(canvas, true);
|
||||
window.addEventListener("resize", () => {
|
||||
engine?.resize();
|
||||
});
|
||||
mounted = true;
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
$: ({ data, name } = value || {
|
||||
data: undefined,
|
||||
name: undefined
|
||||
});
|
||||
|
||||
$: canvas && mounted && data != null && name && dispose();
|
||||
|
||||
function dispose(): void {
|
||||
if (scene && !scene.isDisposed) {
|
||||
scene.dispose();
|
||||
engine?.stopRenderLoop();
|
||||
@ -38,7 +47,7 @@
|
||||
});
|
||||
}
|
||||
addNewModel();
|
||||
});
|
||||
}
|
||||
|
||||
function addNewModel(): void {
|
||||
scene = new BABYLON.Scene(engine!);
|
||||
@ -62,6 +71,8 @@
|
||||
url = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
BABYLON.SceneLoader.ShowLoadingScreen = false;
|
||||
|
||||
BABYLON.SceneLoader.Append(
|
||||
"",
|
||||
url,
|
||||
@ -104,6 +115,7 @@
|
||||
width: var(--size-full);
|
||||
height: var(--size-full);
|
||||
object-fit: contain;
|
||||
overflow: hidden;
|
||||
}
|
||||
.download {
|
||||
position: absolute;
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import Number from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -20,6 +21,13 @@
|
||||
export let loading_status: LoadingStatus;
|
||||
export let value_is_output = false;
|
||||
export let step: number | null = null;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
input: never;
|
||||
submit: never;
|
||||
blur: never;
|
||||
focus: never;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block
|
||||
@ -43,10 +51,10 @@
|
||||
{maximum}
|
||||
{step}
|
||||
{container}
|
||||
on:change
|
||||
on:input
|
||||
on:submit
|
||||
on:blur
|
||||
on:focus
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:submit={() => gradio.dispatch("submit")}
|
||||
on:blur={() => gradio.dispatch("blur")}
|
||||
on:focus={() => gradio.dispatch("focus")}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -16,6 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^"
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/utils": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import Number from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -20,6 +21,13 @@
|
||||
export let loading_status: LoadingStatus;
|
||||
export let value_is_output = false;
|
||||
export let step: number | null = null;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
input: never;
|
||||
submit: never;
|
||||
blur: never;
|
||||
focus: never;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block
|
||||
@ -43,10 +51,10 @@
|
||||
{step}
|
||||
{container}
|
||||
disabled
|
||||
on:change
|
||||
on:input
|
||||
on:submit
|
||||
on:blur
|
||||
on:focus
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:submit={() => gradio.dispatch("submit")}
|
||||
on:blur={() => gradio.dispatch("blur")}
|
||||
on:focus={() => gradio.dispatch("focus")}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import Plot from "./Plot.svelte";
|
||||
|
||||
import { Block, BlockLabel } from "@gradio/atoms";
|
||||
@ -23,6 +24,9 @@
|
||||
export let theme_mode: ThemeMode;
|
||||
export let caption: string;
|
||||
export let bokeh_version: string | null;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block
|
||||
@ -36,5 +40,12 @@
|
||||
>
|
||||
<BlockLabel {show_label} label={label || $_("plot.plot")} Icon={PlotIcon} />
|
||||
<StatusTracker {...loading_status} />
|
||||
<Plot {value} {target} {theme_mode} {caption} {bokeh_version} on:change />
|
||||
<Plot
|
||||
{value}
|
||||
{target}
|
||||
{theme_mode}
|
||||
{caption}
|
||||
{bokeh_version}
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import Radio from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -18,6 +19,11 @@
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: SelectData;
|
||||
input: never;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block
|
||||
@ -39,8 +45,8 @@
|
||||
{elem_id}
|
||||
{show_label}
|
||||
{choices}
|
||||
on:change
|
||||
on:input
|
||||
on:select
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import Radio from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -18,6 +19,11 @@
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: SelectData;
|
||||
input: never;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block
|
||||
@ -40,8 +46,8 @@
|
||||
{show_label}
|
||||
{choices}
|
||||
disabled
|
||||
on:change
|
||||
on:input
|
||||
on:select
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -75,7 +75,10 @@ test("Slider respects show_label", async ({ mount, page }) => {
|
||||
show_label: false,
|
||||
step: 1,
|
||||
mode: "dynamic",
|
||||
loading_status: loading_status
|
||||
loading_status: loading_status,
|
||||
gradio: {
|
||||
dispatch() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
await expect(component.getByTestId("block-title")).toBeHidden();
|
||||
@ -91,7 +94,10 @@ test("Slider Maximum/Minimum values", async ({ mount, page }) => {
|
||||
show_label: true,
|
||||
step: 1,
|
||||
mode: "dynamic",
|
||||
loading_status: loading_status
|
||||
loading_status: loading_status,
|
||||
gradio: {
|
||||
dispatch() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
const slider = component.getByLabel("My Slider");
|
||||
@ -102,8 +108,15 @@ test("Slider Maximum/Minimum values", async ({ mount, page }) => {
|
||||
});
|
||||
|
||||
test("Slider Change event", async ({ mount, page }) => {
|
||||
let change = spy();
|
||||
let release = spy();
|
||||
const events = {
|
||||
change: 0,
|
||||
release: 0
|
||||
};
|
||||
|
||||
function event(name: "change" | "release") {
|
||||
events[name] += 1;
|
||||
}
|
||||
|
||||
const component = await mount(Slider, {
|
||||
props: {
|
||||
value: 3,
|
||||
@ -113,11 +126,10 @@ test("Slider Change event", async ({ mount, page }) => {
|
||||
show_label: true,
|
||||
step: 1,
|
||||
mode: "dynamic",
|
||||
loading_status: loading_status
|
||||
},
|
||||
on: {
|
||||
change: change,
|
||||
release: release
|
||||
loading_status: loading_status,
|
||||
gradio: {
|
||||
dispatch: event
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -127,6 +139,6 @@ test("Slider Change event", async ({ mount, page }) => {
|
||||
await expect(component.getByLabel("My Slider")).toHaveValue("7");
|
||||
|
||||
// More than one change event and one release event.
|
||||
await expect(change.callCount).toBeGreaterThan(1);
|
||||
await expect(release.callCount).toEqual(1);
|
||||
await expect(events.change).toBeGreaterThanOrEqual(1);
|
||||
await expect(events.release).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import Slider from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -21,6 +22,11 @@
|
||||
|
||||
export let loading_status: LoadingStatus;
|
||||
export let value_is_output = false;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
input: never;
|
||||
release: number;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block {visible} {elem_id} {elem_classes} {container} {scale} {min_width}>
|
||||
@ -35,8 +41,8 @@
|
||||
{minimum}
|
||||
{maximum}
|
||||
{step}
|
||||
on:input
|
||||
on:change
|
||||
on:release
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:release={(e) => gradio.dispatch("release", e.detail)}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -16,6 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^",
|
||||
"@gradio/statustracker": "workspace:^"
|
||||
"@gradio/statustracker": "workspace:^",
|
||||
"@gradio/utils": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, ShareData } from "@gradio/utils";
|
||||
import Slider from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
@ -21,6 +22,11 @@
|
||||
|
||||
export let loading_status: LoadingStatus;
|
||||
export let value_is_output = false;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
input: never;
|
||||
release: number;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<Block {visible} {elem_id} {elem_classes} {container} {scale} {min_width}>
|
||||
@ -36,8 +42,8 @@
|
||||
{maximum}
|
||||
{step}
|
||||
disabled
|
||||
on:input
|
||||
on:change
|
||||
on:release
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:release={(e) => gradio.dispatch("release", e.detail)}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1,12 +1,22 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import TabItem from "./TabItem.svelte";
|
||||
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
export let label: string;
|
||||
export let id: string | number;
|
||||
export let gradio: Gradio<{
|
||||
select: SelectData;
|
||||
}>;
|
||||
</script>
|
||||
|
||||
<TabItem {elem_id} {elem_classes} name={label} {id} on:select>
|
||||
<TabItem
|
||||
{elem_id}
|
||||
{elem_classes}
|
||||
name={label}
|
||||
{id}
|
||||
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
||||
>
|
||||
<slot />
|
||||
</TabItem>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import Tabs from "./Tabs.svelte";
|
||||
|
||||
@ -8,10 +9,21 @@
|
||||
export let elem_id = "";
|
||||
export let elem_classes: string[] = [];
|
||||
export let selected: number | string;
|
||||
export let gradio: Gradio<{
|
||||
change: never;
|
||||
select: SelectData;
|
||||
}>;
|
||||
|
||||
$: dispatch("prop_change", { selected });
|
||||
</script>
|
||||
|
||||
<Tabs {visible} {elem_id} {elem_classes} bind:selected on:change on:select>
|
||||
<Tabs
|
||||
{visible}
|
||||
{elem_id}
|
||||
{elem_classes}
|
||||
bind:selected
|
||||
on:change={() => gradio.dispatch("change")}
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
>
|
||||
<slot />
|
||||
</Tabs>
|
||||
|
@ -6,14 +6,15 @@ import event from "@testing-library/user-event";
|
||||
import Textbox from "./interactive";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
|
||||
const loading_status = {
|
||||
const loading_status: LoadingStatus = {
|
||||
eta: 0,
|
||||
queue_position: 1,
|
||||
queue_size: 1,
|
||||
status: "complete" as LoadingStatus["status"],
|
||||
scroll_to_output: false,
|
||||
visible: true,
|
||||
fn_index: 0
|
||||
fn_index: 0,
|
||||
show_progress: "full"
|
||||
};
|
||||
|
||||
describe("Textbox", () => {
|
||||
@ -25,7 +26,6 @@ describe("Textbox", () => {
|
||||
max_lines: 1,
|
||||
loading_status,
|
||||
lines: 1,
|
||||
mode: "dynamic",
|
||||
value: "hello world",
|
||||
label: "Textbox"
|
||||
});
|
||||
@ -37,20 +37,18 @@ describe("Textbox", () => {
|
||||
});
|
||||
|
||||
test("changing the text should update the value", async () => {
|
||||
const { component, getByDisplayValue } = await render(Textbox, {
|
||||
const { component, getByDisplayValue, listen } = await render(Textbox, {
|
||||
show_label: true,
|
||||
max_lines: 10,
|
||||
loading_status,
|
||||
lines: 1,
|
||||
mode: "dynamic",
|
||||
value: "hi ",
|
||||
label: "Textbox"
|
||||
});
|
||||
|
||||
const item: HTMLInputElement = getByDisplayValue("hi") as HTMLInputElement;
|
||||
|
||||
const mock = spy();
|
||||
component.$on("change", mock);
|
||||
const mock = listen("change");
|
||||
|
||||
item.focus();
|
||||
await event.keyboard("some text");
|
||||
@ -58,6 +56,6 @@ describe("Textbox", () => {
|
||||
assert.equal(item.value, "hi some text");
|
||||
assert.equal(component.value, "hi some text");
|
||||
assert.equal(mock.callCount, 9);
|
||||
assert.equal(mock.calls[8][0].detail, "hi some text");
|
||||
assert.equal(mock.calls[8][0].detail.data, "hi some text");
|
||||
});
|
||||
});
|
||||
|
@ -1,11 +1,21 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
|
||||
import TextBox from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
|
||||
export let gradio: Gradio<{
|
||||
change: string;
|
||||
submit: never;
|
||||
blur: never;
|
||||
select: SelectData;
|
||||
input: never;
|
||||
focus: never;
|
||||
}>;
|
||||
export let label = "Textbox";
|
||||
export let info: string | undefined = undefined;
|
||||
export let elem_id = "";
|
||||
@ -56,11 +66,11 @@
|
||||
{show_copy_button}
|
||||
{autofocus}
|
||||
{container}
|
||||
on:change
|
||||
on:input
|
||||
on:submit
|
||||
on:blur
|
||||
on:select
|
||||
on:focus
|
||||
on:change={() => gradio.dispatch("change", value)}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:submit={() => gradio.dispatch("submit")}
|
||||
on:blur={() => gradio.dispatch("blur")}
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
on:focus={() => gradio.dispatch("focus")}
|
||||
/>
|
||||
</Block>
|
||||
|
@ -1 +1,2 @@
|
||||
export { default } from "./InteractiveTextbox.svelte";
|
||||
export { default as BaseTextbox } from "../shared/Textbox.svelte";
|
||||
|
@ -1,11 +1,20 @@
|
||||
<svelte:options accessors={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import TextBox from "../shared";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { StatusTracker } from "@gradio/statustracker";
|
||||
import type { LoadingStatus } from "@gradio/statustracker";
|
||||
|
||||
export let gradio: Gradio<{
|
||||
change: string;
|
||||
submit: never;
|
||||
blur: never;
|
||||
select: SelectData;
|
||||
input: never;
|
||||
focus: never;
|
||||
}>;
|
||||
export let label = "Textbox";
|
||||
export let info: string | undefined = undefined;
|
||||
export let elem_id = "";
|
||||
@ -56,12 +65,12 @@
|
||||
{show_copy_button}
|
||||
{autofocus}
|
||||
{container}
|
||||
on:change
|
||||
on:input
|
||||
on:submit
|
||||
on:blur
|
||||
on:select
|
||||
on:focus
|
||||
on:change={() => gradio.dispatch("change", value)}
|
||||
on:input={() => gradio.dispatch("input")}
|
||||
on:submit={() => gradio.dispatch("submit")}
|
||||
on:blur={() => gradio.dispatch("blur")}
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
on:focus={() => gradio.dispatch("focus")}
|
||||
disabled
|
||||
/>
|
||||
</Block>
|
||||
|
@ -82,20 +82,6 @@
|
||||
|
||||
/* code
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
.prose code {
|
||||
border: 1px solid var(--border-color-primary);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--background-fill-secondary);
|
||||
padding: 1px 3px;
|
||||
font-size: 85%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.prose pre > code {
|
||||
display: block;
|
||||
padding: 0.5em 0.7em;
|
||||
/* font-size: 100%; */
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* tables
|
||||
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
||||
@ -172,6 +158,6 @@
|
||||
padding-left: var(--size-2);
|
||||
}
|
||||
|
||||
.prose :last-child {
|
||||
.prose:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { Gradio } from "@gradio/utils";
|
||||
import { Upload, ModifyUpload } from "@gradio/upload";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { Block, BlockLabel, Empty } from "@gradio/atoms";
|
||||
@ -18,11 +18,6 @@
|
||||
);
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: undefined;
|
||||
clear: undefined;
|
||||
}>();
|
||||
|
||||
interface StaticData {
|
||||
data: number[][];
|
||||
headers: string[];
|
||||
@ -45,6 +40,10 @@
|
||||
export let scale: number | null = null;
|
||||
export let min_width: number | undefined = undefined;
|
||||
export let loading_status: LoadingStatus;
|
||||
export let gradio: Gradio<{
|
||||
change: undefined;
|
||||
clear: undefined;
|
||||
}>;
|
||||
|
||||
let _value: string | null;
|
||||
|
||||
@ -128,13 +127,13 @@
|
||||
|
||||
function handle_clear({ detail }: CustomEvent<FileData | null>): void {
|
||||
value = null;
|
||||
dispatch("change");
|
||||
dispatch("clear");
|
||||
gradio.dispatch("change");
|
||||
gradio.dispatch("clear");
|
||||
}
|
||||
|
||||
$: _value = value == null ? null : _value;
|
||||
|
||||
$: value, dispatch("change");
|
||||
$: value, gradio.dispatch("change");
|
||||
</script>
|
||||
|
||||
<Block
|
||||
|
@ -13,6 +13,8 @@ import type {
|
||||
EventType,
|
||||
FireObject
|
||||
} from "@testing-library/dom";
|
||||
import { spy, type Spy } from "tinyspy";
|
||||
import { Gradio } from "../../app/src/gradio_helper";
|
||||
|
||||
const containerCache = new Map();
|
||||
const componentCache = new Set();
|
||||
@ -40,21 +42,36 @@ export interface RenderOptions<Q extends Queries = typeof queries> {
|
||||
export async function render<
|
||||
Events extends Record<string, any>,
|
||||
Props extends Record<string, any>,
|
||||
T extends SvelteComponent<Props, Events>
|
||||
T extends SvelteComponent<Props, Events>,
|
||||
X extends Record<string, any>
|
||||
>(
|
||||
Component: ComponentType<T, Props> | { default: ComponentType<T, Props> },
|
||||
props?: Props
|
||||
): Promise<RenderResult<T>> {
|
||||
props?: Omit<Props, "gradio">
|
||||
): Promise<
|
||||
RenderResult<T> & {
|
||||
listen: typeof listen;
|
||||
wait_for_event: typeof wait_for_event;
|
||||
}
|
||||
> {
|
||||
const container = document.body;
|
||||
const target = container.appendChild(document.createElement("div"));
|
||||
|
||||
const ComponentConstructor: ComponentType<T, Props> =
|
||||
const ComponentConstructor: ComponentType<
|
||||
T,
|
||||
Props & { gradio: typeof Gradio<X> }
|
||||
> =
|
||||
//@ts-ignore
|
||||
Component.default || Component;
|
||||
|
||||
const id = Math.floor(Math.random() * 1000000);
|
||||
|
||||
const component = new ComponentConstructor({
|
||||
target,
|
||||
props
|
||||
//@ts-ignore
|
||||
props: {
|
||||
...(props || {}),
|
||||
gradio: new Gradio(id, target, "light", "2.0.0", "http://localhost:8000")
|
||||
}
|
||||
});
|
||||
|
||||
containerCache.set(container, { target, component });
|
||||
@ -66,6 +83,36 @@ export async function render<
|
||||
|
||||
await tick();
|
||||
|
||||
type extractGeneric<Type> = Type extends Gradio<infer X> ? X : null;
|
||||
type event_name = keyof extractGeneric<Props["gradio"]>;
|
||||
|
||||
function listen(event: event_name): Spy {
|
||||
const mock = spy();
|
||||
target.addEventListener("gradio", (e: Event) => {
|
||||
if (isCustomEvent(e)) {
|
||||
if (e.detail.event === event && e.detail.id === id) {
|
||||
mock(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return mock;
|
||||
}
|
||||
|
||||
async function wait_for_event(event: event_name): Promise<Spy> {
|
||||
return new Promise((res) => {
|
||||
const mock = spy();
|
||||
target.addEventListener("gradio", (e: Event) => {
|
||||
if (isCustomEvent(e)) {
|
||||
if (e.detail.event === event && e.detail.id === id) {
|
||||
mock(e);
|
||||
res(mock);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
container,
|
||||
component,
|
||||
@ -74,7 +121,9 @@ export async function render<
|
||||
unmount: (): void => {
|
||||
if (componentCache.has(component)) component.$destroy();
|
||||
},
|
||||
...getQueriesForElement(container)
|
||||
...getQueriesForElement(container),
|
||||
listen,
|
||||
wait_for_event
|
||||
};
|
||||
}
|
||||
|
||||
@ -115,3 +164,7 @@ export type FireFunction = (
|
||||
) => Promise<boolean>;
|
||||
|
||||
export * from "@testing-library/dom";
|
||||
|
||||
function isCustomEvent(event: Event): event is CustomEvent {
|
||||
return "detail" in event;
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
export { default as Upload } from "./Upload.svelte";
|
||||
export { default as ModifyUpload } from "./ModifyUpload.svelte";
|
||||
export type { FileData } from "./types";
|
||||
export {normalise_file, get_fetchable_url_or_file, blobToBase64} from "./utils";
|
||||
export {
|
||||
normalise_file,
|
||||
get_fetchable_url_or_file,
|
||||
blobToBase64
|
||||
} from "./utils";
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user