From 3a4b1d8f8bfc28a2542588c135527656683df638 Mon Sep 17 00:00:00 2001 From: pngwn Date: Thu, 13 Jul 2023 17:41:47 +0100 Subject: [PATCH] move all of the chatbot component in to js/chatbot (#4900) * changes * fix test * changes --- js/app/src/components/Chatbot/index.ts | 17 +------ js/app/test/components.test.ts | 2 +- js/chatbot/package.json | 2 +- .../Chatbot => chatbot/src}/Chatbot.test.ts | 49 ++++++++++--------- .../src/Index.svelte} | 34 ++++++------- js/chatbot/src/index.ts | 1 - js/chatbot/src/{ => static}/ChatBot.svelte | 32 ++++++------ js/chatbot/src/{ => static}/Copy.svelte | 0 .../src/{ => static}/MarkdownCode.svelte | 8 +-- js/chatbot/src/{ => static}/autorender.d.ts | 0 js/chatbot/src/static/index.ts | 1 + js/chatbot/src/utils.ts | 45 ++++++++--------- package.json | 2 +- 13 files changed, 87 insertions(+), 106 deletions(-) rename js/{app/src/components/Chatbot => chatbot/src}/Chatbot.test.ts (79%) rename js/{app/src/components/Chatbot/Chatbot.svelte => chatbot/src/Index.svelte} (71%) delete mode 100644 js/chatbot/src/index.ts rename js/chatbot/src/{ => static}/ChatBot.svelte (92%) rename js/chatbot/src/{ => static}/Copy.svelte (100%) rename js/chatbot/src/{ => static}/MarkdownCode.svelte (91%) rename js/chatbot/src/{ => static}/autorender.d.ts (100%) create mode 100644 js/chatbot/src/static/index.ts diff --git a/js/app/src/components/Chatbot/index.ts b/js/app/src/components/Chatbot/index.ts index 810c494c0c7c..14d1f295a1e6 100644 --- a/js/app/src/components/Chatbot/index.ts +++ b/js/app/src/components/Chatbot/index.ts @@ -1,17 +1,2 @@ -export { default as Component } from "./Chatbot.svelte"; +export { default as Component } from "@gradio/chatbot"; export const modes = ["static"]; - -export const document = (config: Record) => ({ - type: { - payload: "Array<[string, string]>" - }, - description: { - payload: "list of message pairs of" - }, - example_data: config.value?.length - ? config.value - : [ - ["Hi", "Hello"], - ["1 + 1", "2"] - ] -}); diff --git a/js/app/test/components.test.ts b/js/app/test/components.test.ts index a8a8fa335420..70bbab0c499a 100644 --- a/js/app/test/components.test.ts +++ b/js/app/test/components.test.ts @@ -12,7 +12,7 @@ import { setupi18n } from "../src/i18n"; import AnnotatedImage from "../src/components/AnnotatedImage/AnnotatedImage.svelte"; import Audio from "../src/components/Audio/Audio.svelte"; -import Chatbot from "../src/components/Chatbot/Chatbot.svelte"; +import Chatbot from "@gradio/chatbot"; import Checkbox from "../src/components/Checkbox/Checkbox.svelte"; import CheckboxGroup from "../src/components/CheckboxGroup/CheckboxGroup.svelte"; import ColorPicker from "../src/components/ColorPicker/ColorPicker.svelte"; diff --git a/js/chatbot/package.json b/js/chatbot/package.json index bd271dc87a18..b3f100372757 100644 --- a/js/chatbot/package.json +++ b/js/chatbot/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Gradio UI packages", "type": "module", - "main": "src/index.ts", + "main": "src/Index.svelte", "author": "", "license": "ISC", "private": true, diff --git a/js/app/src/components/Chatbot/Chatbot.test.ts b/js/chatbot/src/Chatbot.test.ts similarity index 79% rename from js/app/src/components/Chatbot/Chatbot.test.ts rename to js/chatbot/src/Chatbot.test.ts index 3e656de0b79f..bd8d6d705c03 100644 --- a/js/app/src/components/Chatbot/Chatbot.test.ts +++ b/js/chatbot/src/Chatbot.test.ts @@ -1,7 +1,8 @@ import { test, describe, assert, afterEach } from "vitest"; import { cleanup, render } from "@gradio/tootils"; -import Chatbot from "./Chatbot.svelte"; -import type { LoadingStatus } from "../StatusTracker/types"; +import Chatbot from "./Index.svelte"; +import type { LoadingStatus } from "../../app/src/components/StatusTracker/types"; +import type { FileData } from "@gradio/upload"; const loading_status: LoadingStatus = { eta: 0, @@ -67,14 +68,16 @@ describe("Chatbot", () => { const { component, getAllByTestId } = await render(Chatbot, { loading_status, label: "chatbot", - value: null, + value: undefined, root: "", root_url: "", - latex_delimiters: null, + latex_delimiters: [], theme_mode: "dark" }); - let value = Array(2).fill([ + let value: [string | FileData | null, string | FileData | null][] = Array( + 2 + ).fill([ { name: "https://gradio-builds.s3.amazonaws.com/demo-files/cheetah1.jpg", mime_type: "image/jpeg", @@ -88,7 +91,7 @@ describe("Chatbot", () => { value: value }); - const image = getAllByTestId("chatbot-image"); + const image = getAllByTestId("chatbot-image") as HTMLImageElement[]; assert.isTrue(image[0].src.includes("cheetah1.jpg")); assert.isTrue(image[1].src.includes("cheetah1.jpg")); }); @@ -97,26 +100,26 @@ describe("Chatbot", () => { const { component, getAllByTestId } = await render(Chatbot, { loading_status, label: "chatbot", - value: null, root: "", root_url: "", - latex_delimiters: null, + latex_delimiters: [], theme_mode: "dark" }); - let value = Array(2).fill([ - { - name: "https://gradio-builds.s3.amazonaws.com/demo-files/video_sample.mp4", - mime_type: "video/mp4", - alt_text: null, - data: null, - is_file: true - } - ]); + let value: Array<[string | FileData | null, string | FileData | null]> = + Array(2).fill([ + { + name: "https://gradio-builds.s3.amazonaws.com/demo-files/video_sample.mp4", + mime_type: "video/mp4", + alt_text: null, + data: null, + is_file: true + } + ]); await component.$set({ value: value }); - const video = getAllByTestId("chatbot-video"); + const video = getAllByTestId("chatbot-video") as HTMLVideoElement[]; assert.isTrue(video[0].src.includes("video_sample.mp4")); assert.isTrue(video[1].src.includes("video_sample.mp4")); }); @@ -125,10 +128,9 @@ describe("Chatbot", () => { const { component, getAllByTestId } = await render(Chatbot, { loading_status, label: "chatbot", - value: null, root: "", root_url: "", - latex_delimiters: null, + latex_delimiters: [], theme_mode: "dark" }); @@ -146,7 +148,7 @@ describe("Chatbot", () => { value: value }); - const audio = getAllByTestId("chatbot-audio"); + const audio = getAllByTestId("chatbot-audio") as HTMLAudioElement[]; assert.isTrue(audio[0].src.includes("audio_sample.wav")); assert.isTrue(audio[1].src.includes("audio_sample.wav")); }); @@ -155,10 +157,9 @@ describe("Chatbot", () => { const { component, getAllByTestId } = await render(Chatbot, { loading_status, label: "chatbot", - value: null, root: "", root_url: "", - latex_delimiters: null, + latex_delimiters: [], theme_mode: "dark" }); @@ -176,7 +177,7 @@ describe("Chatbot", () => { value: value }); - const file_link = getAllByTestId("chatbot-file"); + const file_link = getAllByTestId("chatbot-file") as HTMLAnchorElement[]; assert.isTrue(file_link[0].href.includes("titanic.csv")); assert.isTrue(file_link[0].href.includes("titanic.csv")); }); diff --git a/js/app/src/components/Chatbot/Chatbot.svelte b/js/chatbot/src/Index.svelte similarity index 71% rename from js/app/src/components/Chatbot/Chatbot.svelte rename to js/chatbot/src/Index.svelte index 692cd8d91edf..d4b910edab7f 100644 --- a/js/app/src/components/Chatbot/Chatbot.svelte +++ b/js/chatbot/src/Index.svelte @@ -1,37 +1,35 @@ - import { copy, format_chat_for_sharing } from "./utils"; + import { copy, format_chat_for_sharing } from "../utils"; import "katex/dist/katex.min.css"; import { beforeUpdate, afterUpdate, createEventDispatcher } from "svelte"; import { ShareButton } from "@gradio/atoms"; @@ -9,25 +9,21 @@ import Markdown from "./MarkdownCode.svelte"; const code_highlight_css = { - light: () => import("prismjs/themes/prism.css"), - dark: () => import("prismjs/themes/prism-dark.css") + light: (): Promise => import("prismjs/themes/prism.css"), + dark: (): Promise => import("prismjs/themes/prism-dark.css") }; - export let value: Array< - [string | FileData | null, string | FileData | null] - > | null; - let old_value: Array< - [string | FileData | null, string | FileData | null] - > | null = null; - export let latex_delimiters: Array<{ + export let value: [string | FileData | null, string | FileData | null][] | null; + let old_value: [string | FileData | null, string | FileData | null][] | null = null; + export let latex_delimiters: { left: string; right: string; display: boolean; - }>; - export let pending_message: boolean = false; - export let feedback: Array | null = null; - export let selectable: boolean = false; - export let show_share_button: boolean = false; + }[]; + export let pending_message = false; + export let feedback: string[] | null = null; + export let selectable = false; + export let show_share_button = false; export let theme_mode: ThemeMode; $: if (theme_mode == "dark") { @@ -37,7 +33,7 @@ } let div: HTMLDivElement; - let autoscroll: Boolean; + let autoscroll: boolean; const dispatch = createEventDispatcher<{ change: undefined; @@ -49,7 +45,7 @@ div && div.offsetHeight + div.scrollTop > div.scrollHeight - 100; }); - const scroll = () => { + const scroll = (): void => { if (autoscroll) { div.scrollTo(0, div.scrollHeight); } @@ -76,7 +72,7 @@ i: number, j: number, message: string | FileData | null - ) { + ): void { dispatch("select", { index: [i, j], value: message diff --git a/js/chatbot/src/Copy.svelte b/js/chatbot/src/static/Copy.svelte similarity index 100% rename from js/chatbot/src/Copy.svelte rename to js/chatbot/src/static/Copy.svelte diff --git a/js/chatbot/src/MarkdownCode.svelte b/js/chatbot/src/static/MarkdownCode.svelte similarity index 91% rename from js/chatbot/src/MarkdownCode.svelte rename to js/chatbot/src/static/MarkdownCode.svelte index bdc18eda0b87..30c8a6d6786d 100644 --- a/js/chatbot/src/MarkdownCode.svelte +++ b/js/chatbot/src/static/MarkdownCode.svelte @@ -2,16 +2,16 @@ 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"; + import { marked } from "../utils"; const dispatch = createEventDispatcher(); export let message: string; - let old_message: string = ""; - export let latex_delimiters: Array<{ + let old_message = ""; + export let latex_delimiters: { left: string; right: string; display: boolean; - }>; + }[]; let el: HTMLSpanElement; let mounted = false; diff --git a/js/chatbot/src/autorender.d.ts b/js/chatbot/src/static/autorender.d.ts similarity index 100% rename from js/chatbot/src/autorender.d.ts rename to js/chatbot/src/static/autorender.d.ts diff --git a/js/chatbot/src/static/index.ts b/js/chatbot/src/static/index.ts new file mode 100644 index 000000000000..1318c92353b2 --- /dev/null +++ b/js/chatbot/src/static/index.ts @@ -0,0 +1 @@ +export { default } from "./ChatBot.svelte"; diff --git a/js/chatbot/src/utils.ts b/js/chatbot/src/utils.ts index eb566a7f4a9f..73efb8f93423 100644 --- a/js/chatbot/src/utils.ts +++ b/js/chatbot/src/utils.ts @@ -5,8 +5,9 @@ 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 = ``; -const check_icon = ``; -const copy_button = ``; const escape_test = /[&<>"']/; @@ -43,16 +44,17 @@ const escape_replacements: Record = { "'": "'" }; -const getEscapeReplacement = (ch: string) => escape_replacements[ch] || ""; +const get_escape_replacement = (ch: string): string => + escape_replacements[ch] || ""; -function escape(html: string, encode?: boolean) { +function escape(html: string, encode?: boolean): string { if (encode) { if (escape_test.test(html)) { - return html.replace(escape_replace, getEscapeReplacement); + return html.replace(escape_replace, get_escape_replacement); } } else { if (escape_test_no_encode.test(html)) { - return html.replace(escape_replace_no_encode, getEscapeReplacement); + return html.replace(escape_replace_no_encode, get_escape_replacement); } } @@ -82,7 +84,7 @@ const renderer: Partial< if (!lang) { return ( "
" +
-				copy_button +
+				COPY_BUTTON_CODE +
 				(escaped ? code : escape(code, true)) +
 				"
\n" ); @@ -93,7 +95,7 @@ const renderer: Partial< this.options.langPrefix + escape(lang) + '">' + - copy_button + + COPY_BUTTON_CODE + (escaped ? code : escape(code, true)) + "\n" ); @@ -112,18 +114,17 @@ marked.use( highlight: (code: string, lang: string) => { if (Prism.languages[lang]) { return Prism.highlight(code, Prism.languages[lang], lang); - } else { - return code; } + return code; } }), { renderer } ); -export function copy(node: HTMLDivElement) { +export function copy(node: HTMLDivElement): ActionReturn { node.addEventListener("click", handle_copy); - async function handle_copy(event: MouseEvent) { + async function handle_copy(event: MouseEvent): Promise { const path = event.composedPath() as HTMLButtonElement[]; const [copy_button] = path.filter( @@ -142,23 +143,23 @@ export function copy(node: HTMLDivElement) { if (copied) copy_feedback(copy_sucess_button); - function copy_feedback(copy_sucess_button: HTMLDivElement) { - copy_sucess_button.style.opacity = "1"; + function copy_feedback(_copy_sucess_button: HTMLDivElement): void { + _copy_sucess_button.style.opacity = "1"; setTimeout(() => { - copy_sucess_button.style.opacity = "0"; + _copy_sucess_button.style.opacity = "0"; }, 2000); } } } return { - destroy() { + destroy(): void { node.removeEventListener("click", handle_copy); } }; } -async function copy_to_clipboard(value: string) { +async function copy_to_clipboard(value: string): Promise { let copied = false; if ("clipboard" in navigator) { await navigator.clipboard.writeText(value); @@ -190,8 +191,8 @@ async function copy_to_clipboard(value: string) { export { marked }; export const format_chat_for_sharing = async ( - chat: Array<[string | FileData | null, string | FileData | null]> -) => { + chat: [string | FileData | null, string | FileData | null][] +): Promise => { let messages = await Promise.all( chat.map(async (message_pair) => { return await Promise.all( diff --git a/package.json b/package.json index c68615fdeab8..a6c04a64af4c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "format:check": "prettier --ignore-path .config/.prettierignore --check --config .config/.prettierrc.json .", "format:write": "prettier --ignore-path .config/.prettierignore --write --config .config/.prettierrc.json .", "lint": "ESLINT_USE_FLAT_CONFIG=true eslint -c .config/eslint.config.js js client/js", - "ts:check": "svelte-check --tsconfig tsconfig.json", + "ts:check": "svelte-check --tsconfig tsconfig.json --threshold error", "test": "pnpm --filter @gradio/client build && vitest dev --config .config/vitest.config.ts", "test:run": "pnpm --filter @gradio/client build && vitest run --config .config/vitest.config.ts --reporter=verbose", "test:node": "TEST_MODE=node pnpm vitest run --config .config/vitest.config.ts",