`;
+ })
+ .join('')}
+ `;
+ onMount(() => {
+ const inputs = Array.from(el.getElementsByTagName('input'));
+ Array.from(el.getElementsByTagName('label')).forEach((label, i) => {
+ label.addEventListener('click', () => {
+ inputs.forEach((input) => {
+ input.checked = false;
+ });
+ const input = label.getElementsByTagName('input')[0];
+ input.checked = true;
+ // 通过 cc.dispatch 向 python 侧发送通知
+ cc.dispatch(options[i]);
+ });
+ });
+ });
+};
diff --git a/components/Chatbot/resources/dog.mp4 b/components/Chatbot/resources/dog.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..062b9c81317de43f392c56e9e03444bf8cc31d51
--- /dev/null
+++ b/components/Chatbot/resources/dog.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:39d086ce29e48cf76e5042d2f3f0611ee46575f70fa3dc0c40dd4cfffde3d933
+size 8626383
diff --git a/components/Chatbot/resources/image-bot.jpeg b/components/Chatbot/resources/image-bot.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..5df101fedca7e0b76eaa1119506ca4c1941c9fc7
Binary files /dev/null and b/components/Chatbot/resources/image-bot.jpeg differ
diff --git a/components/Chatbot/resources/music-bot.jpeg b/components/Chatbot/resources/music-bot.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..00288fb576845921bbc3247ca8da3fbbdea12800
Binary files /dev/null and b/components/Chatbot/resources/music-bot.jpeg differ
diff --git a/components/Chatbot/resources/screen.jpeg b/components/Chatbot/resources/screen.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..574735acb117e86c5c0850e2b5489b8f8efa20cc
Binary files /dev/null and b/components/Chatbot/resources/screen.jpeg differ
diff --git a/components/Chatbot/resources/user.jpeg b/components/Chatbot/resources/user.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..536948b6bd19cb0b49c44b74e2790198301520e5
Binary files /dev/null and b/components/Chatbot/resources/user.jpeg differ
diff --git a/components/Docs.py b/components/Docs.py
new file mode 100644
index 0000000000000000000000000000000000000000..f68ae5dc5552a92c925f2ae4dfe56b5eaffc7bec
--- /dev/null
+++ b/components/Docs.py
@@ -0,0 +1,147 @@
+import os
+import re
+from typing import Callable
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+from .parse_markdown import parse_markdown
+
+with open(os.path.join(os.path.dirname(__file__), "tab-link.js")) as f:
+ tab_link_js = f.read()
+
+custom_components = {
+ "tab-link": {
+ "props": ["tab", "component-tab"],
+ "js": tab_link_js
+ }
+}
+
+
+def remove_formatter(markdown_text):
+ pattern = r"^---[\s\S]*?---"
+
+ replaced_text = re.sub(pattern, "", markdown_text)
+
+ return replaced_text
+
+
+def list_demos(dir_path: str, prefix=''):
+ result = []
+ if (not os.path.isdir(dir_path)):
+ return result
+ for name in os.listdir(dir_path):
+ path = os.path.join(dir_path, name)
+
+ if os.path.isfile(path):
+ result.append(prefix + name)
+ elif os.path.isdir(path):
+ sub_prefix = prefix + name + '/'
+ result.extend(list_demos(path, sub_prefix))
+
+ return result
+
+
+def get_demo_modules(file_path: str):
+ import importlib.util
+
+ demos = [
+ demo for demo in list_demos(
+ os.path.join(os.path.dirname(file_path), "demos"))
+ if demo.endswith(".py")
+ ]
+ demo_modules = {}
+ for demo in demos:
+ demo_name = demo.split(".")[0]
+ spec = importlib.util.spec_from_file_location(
+ "demo", os.path.join(os.path.dirname(file_path), "demos", demo))
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ demo_modules[demo_name] = module
+ return demo_modules
+
+
+class Docs:
+
+ def __init__(self, file_path: str, markdown_files: list = None):
+ self.file_path = file_path
+ self.demo_modules = get_demo_modules(file_path)
+ # default current directory
+ self.markdown_files = markdown_files if markdown_files else [
+ file_name for file_name in os.listdir(os.path.dirname(file_path))
+ if file_name.endswith(".md")
+ ]
+ self.tabs = None
+
+ def read_file(self, relative_path: str):
+ with open(os.path.join(os.path.dirname(self.file_path), relative_path),
+ "r") as f:
+ return f.read()
+
+ def render_demo(self, demo_name, prefix='', suffix=''):
+ content = self.read_file(f"./demos/{demo_name}.py")
+ module = self.demo_modules[demo_name]
+ with gr.Accordion("Show Demo", open=False):
+ with gr.Row():
+ with gr.Column():
+ mgr.Markdown(f"""
+{prefix}
+````python
+{content}
+````
+{suffix}
+""",
+ header_links=True,
+ custom_components=custom_components)
+ with gr.Column():
+ module.demo.render()
+
+ def render_markdown(self,
+ markdown_file,
+ on_tab_link_click: Callable = None,
+ components_tabs=None):
+ items = parse_markdown(remove_formatter(self.read_file(markdown_file)),
+ read_file=self.read_file)
+ for item in items:
+ if item["type"] == "text":
+ md = mgr.Markdown(item["value"],
+ header_links=True,
+ custom_components=custom_components)
+ # 过滤
+ deps = [dep for dep in [components_tabs, self.tabs] if dep]
+ if len(deps) > 0:
+ md.custom(fn=on_tab_link_click, outputs=deps)
+ elif item["type"] == "demo":
+ self.render_demo(item["name"],
+ prefix=item["prefix"],
+ suffix=item["suffix"])
+
+ def render(self, components_tabs=None):
+
+ def tab_link_click(data: gr.EventData):
+ tab: str = data._data["value"].get("tab", '')
+ component_tab: str = data._data["value"].get("component_tab", '')
+ if tab and tabs:
+ return {tabs: gr.update(selected=tab)}
+ elif components_tabs and component_tab:
+ return {components_tabs: gr.update(selected=component_tab)}
+
+ with gr.Blocks() as demo:
+
+ if len(self.markdown_files) > 1:
+ with gr.Tabs() as tabs:
+ self.tabs = tabs
+
+ for markdown_file in self.markdown_files:
+ tab_name = ".".join(markdown_file.split(".")[:-1])
+ with gr.TabItem(tab_name, id=tab_name):
+ self.render_markdown(
+ markdown_file,
+ on_tab_link_click=tab_link_click,
+ components_tabs=components_tabs)
+ else:
+ self.render_markdown(self.markdown_files[0],
+ on_tab_link_click=tab_link_click,
+ components_tabs=components_tabs)
+ return demo
diff --git a/components/Markdown/README.md b/components/Markdown/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e95ff7fa6b18e2a0c1005ef4f32a93642ada89c4
--- /dev/null
+++ b/components/Markdown/README.md
@@ -0,0 +1,87 @@
+# Markdown
+
+升级版的 gradio Markdown。
+
+- 支持输出多模态内容(音频、视频、语音、文件、文本)
+- 支持自定义渲染组件,并与 Python 侧事件交互
+
+## 如何使用
+
+### 基本使用
+
+
+
+### 多模态 & 支持本地文件的展示
+
+
+
+### 支持手风琴内容展示
+
+在返回的内容中加入 `accordion` 标签,更多用法详见
accordion
+
+
+
+### 支持用户选择交互
+
+在返回的内容中加入 `select-box` 标签,更多用法详见
select-box
+
+
+
+### 自定义标签(高阶用法,需要了解前端知识)
+
+
+
+#### 引入 js
+
+
+
+template只能做简单的变量替换,如果想要引入更多自定义的行为,如条件判断、循环渲染等,请使用 js 控制 el 自行处理,下面是简单的示例:
+
+
+
+custom_select.js
+
+```js
+
+```
+
+
+
+
+#### 与 Python 侧交互
+
+在 js 中可以使用`cc.dispatch`触发 Python 侧监听的`custom`事件,以前面的custom_select.js为例,我们在前端调用了`cc.dispatch(options[i])`,则会向 Python 侧同时发送通知。
+
+
+
+## API 及参数列表
+
+以下 API 均为在原有 gradio Markdown 外的额外拓展参数。
+
+### props
+
+| 属性 | 类型 | 默认值 | 描述 |
+| ----------------- | --------------------------------------------------------------- | ------ | --------------------------------------------------------------------------- |
+| enable_base64 | bool | False | 是否支持渲染的内容为 base64,因为直接渲染 base64 有安全问题,默认为 False。 |
+| preview | bool | True | 是否开启图片预览功能 |
+| custom_components | dict\[str, CustomComponentDict\] CustomComponentDict 定义见下方 | None | 支持用户定义自定义标签,并通过 js 控制标签渲染样式与触发 python 事件。 |
+
+**CustomComponent 定义如下**
+
+```python
+class CustomComponentDict(TypedDict):
+ props: Optional[List[str]]
+ template: Optional[str]
+ js: Optional[str]
+```
+
+### 内置的自定义标签
+
+-
select-box
+-
accordion
+
+### event listeners
+
+| 事件 | 描述 |
+| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `mgr.Markdown.custom(fn, ···)` | 自定义标签触发事件时触发,EventData 为:
- index:当前 message 的 index tuple ([message index, user group(index 0) or bot group(index 1), user/bot group index])。
- tag:当前触发的标签。
- tag_index:当前触发标签的 index,此 index 在 mesage 的 index tuple 基础上重新计算。
- value:自定义传入的值。 |
diff --git a/components/Markdown/app.py b/components/Markdown/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c9d836b38ee51f5de91d43011f5f3aaaaf4aea3
--- /dev/null
+++ b/components/Markdown/app.py
@@ -0,0 +1,20 @@
+import os
+
+from components.Docs import Docs
+
+
+def resolve(relative_path: str):
+ return os.path.join(os.path.dirname(__file__), relative_path)
+
+
+docs = Docs(
+ __file__,
+ markdown_files=(["README.md"] + [
+ f"custom_tags/{file_name}"
+ for file_name in os.listdir(resolve('custom_tags'))
+ if file_name.endswith(".md")
+ ]),
+)
+
+if __name__ == "__main__":
+ docs.render().queue().launch()
diff --git a/components/Markdown/custom_tags/accordion.md b/components/Markdown/custom_tags/accordion.md
new file mode 100644
index 0000000000000000000000000000000000000000..6a62712515e4dc14cdfd17865deb80811fa15abb
--- /dev/null
+++ b/components/Markdown/custom_tags/accordion.md
@@ -0,0 +1,23 @@
+# accordion
+
+在 markdown 文本中添加手风琴效果。
+
+## 如何使用
+
+### 基本使用
+
+
+
+### 使用 accordion-title 标记
+
+使用`::accordion-title[content]`的形式可以在标题输入 markdown 文本。
+
+
+
+## API 及参数列表
+
+### props
+
+| 属性 | 类型 | 默认值 | 描述 |
+| ----- | ------ | ------ | ------------ |
+| title | string | | 手风琴的标题 |
diff --git a/components/Markdown/custom_tags/select-box.md b/components/Markdown/custom_tags/select-box.md
new file mode 100644
index 0000000000000000000000000000000000000000..c309287508232e756d9dd039d9b49dadec959f53
--- /dev/null
+++ b/components/Markdown/custom_tags/select-box.md
@@ -0,0 +1,45 @@
+# select-box
+
+在 markdown 文本中添加选择交互框。
+
+## 如何使用
+
+### 基本使用
+
+
+
+### Card 样式
+
+
+
+### Card 自适应内部元素宽度
+
+
+
+### 监听 Python 事件
+
+
+
+## API 及参数列表
+
+### value
+
+custom 事件中 custom_data value 对应值, 返回值为用户 options 传入的对应 value ,如果type="checkbox",则返回一个 list。
+
+### props
+
+| 属性 | 类型 | 默认值 | 描述 |
+| ------------ | ------------------------------------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| type | 'checkbox' \| 'radio' | 'radio' | 选择框类型,'radio' 为单选框、'checkbox'为多选框。 |
+| disabled | boolean | | 禁用选择,通常在需要读取历史信息二次渲染时会用到。 |
+| value | string | | 默认选中值,通常适用于`type="checkbox"`时提前为用户选择部分选项和设置`disabled`后的默认值渲染。 |
+| direction | 'horizontal' \| 'vertical' | 'horizontal' | 横向或竖向排列选择框 |
+| shape | 'card' \| 'default' | 'default' | 选择框样式 |
+| options | (string\| { label?: string, value?: string, imgSrc?: string})\[\] | | 为用户提供的选项值,每一项可以为 string 或 object。 当值为 object 时可以接收更多自定义值,其中imgSrc只有当shape="card"时才生效。 |
+| select-once | boolean | false | 是否只允许用户选择一次 |
+| submit-text | string | | 提交按钮的展示值,当该属性有值时,会展示提交按钮,此时用户只有点击提交按钮后才会触发选择事件。 |
+| columns | number \| { xs?: number, sm?: number, md?: number, lg?: number, xl?: number, xxl?: number } | { xs: 1, sm: 2, md: 2, lg: 4} | 当shape="card"时才生效。每一行选项占用列数,值的范围为1 - 24,建议此项取值可以被 24 整除,否则可能列数会不符合预期。 当此项传入值为对象时,可以响应式控制每一行渲染列数,响应阈值如下:
- xs:屏幕 < 576px
- sm:屏幕 ≥ 576px
- md:屏幕 ≥ 768px
- lg:屏幕 ≥ 992px
- xl:屏幕 ≥ 1200px
- xxl:屏幕 ≥ 1600px 当direction为vertical时此配置不生效。 |
+| item-width | string | | 当shape="card"时才生效。每个选项的宽度,如:'auto'、'100px',默认使用 columns 自动分配的宽度。 |
+| item-height | string | | 当shape="card"时才生效。每个选项的高度,默认自适应元素高度。 |
+| img-height | string | '160px' | 当shape="card"时才生效。每个选项中图片的高度。 |
+| equal-height | boolean | false | 当shape="card"时才生效。是否每一行的选项高度都相等,会使用高度最高的选项。 |
diff --git a/components/Markdown/demos/accordion.py b/components/Markdown/demos/accordion.py
new file mode 100644
index 0000000000000000000000000000000000000000..d53349e79b996389397241f337a4fac0e86c78e0
--- /dev/null
+++ b/components/Markdown/demos/accordion.py
@@ -0,0 +1,30 @@
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+with gr.Blocks() as demo:
+ mgr.Markdown(f"""
+普通调用:
+
+
+
+```json
+{{"text": "风和日丽", "resolution": "1024*1024"}}
+```
+
+
+
+使用 ::accordion-title 标记支持 markdown 语法:
+
+
+
+::accordion-title[调用 `tool`]
+
+```json
+{{"text": "风和日丽", "resolution": "1024*1024"}}
+```
+
+""")
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/basic.py b/components/Markdown/demos/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..61f4fd73db76c4e91a9381f2bf2af563bee141a5
--- /dev/null
+++ b/components/Markdown/demos/basic.py
@@ -0,0 +1,11 @@
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+with gr.Blocks() as demo:
+ mgr.Markdown(
+ "This _example_ was **written** in [Markdown](https://en.wikipedia.org/wiki/Markdown)\n"
+ )
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom-tag.py b/components/Markdown/demos/custom-tag.py
new file mode 100644
index 0000000000000000000000000000000000000000..787c3ea8a91eafb66fa0da4d8eea8fb565d7267d
--- /dev/null
+++ b/components/Markdown/demos/custom-tag.py
@@ -0,0 +1,21 @@
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+with gr.Blocks() as demo:
+ mgr.Markdown(
+ f"""
+custom tag:
+""",
+ custom_components={
+ # key 为标签名
+ "custom-tag": {
+ # 自定义标签允许接收的值,可在调用标签时由用户传入
+ "props": ["value"],
+ # 实际渲染时的 template, 可以使用 {} 将用户传入的 props 替换。
+ "template": "
{value}
"
+ }
+ })
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom-tag2.py b/components/Markdown/demos/custom-tag2.py
new file mode 100644
index 0000000000000000000000000000000000000000..03b3d4448039bbc1572a92031911189bb2f6cf73
--- /dev/null
+++ b/components/Markdown/demos/custom-tag2.py
@@ -0,0 +1,38 @@
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+with gr.Blocks() as demo:
+ mgr.Markdown(
+ f"""
+custom tag:
+""",
+ custom_components={
+ # key 为标签名
+ "custom-tag": {
+ "props": ["value"],
+ "template":
+ "
",
+ # js 接收一个 function
+ "js":
+ """
+(props, cc, { el, onMount }) => {
+ // onMount 会在 template 渲染完成后调用
+ onMount(() => {
+ // el 是当前自定义标签挂载的 container
+ console.log(el)
+ })
+ console.log(props.children) // 默认会包含 children,可以拿到
xx 标签内的内容 xx
+ // 可以返回一个对象,对象里的值会与 props 做并集最后渲染到模板中
+ return {
+ value: 'Click Me: ' + props.value,
+ onClick: () => {
+ alert('hello')
+ }
+ }
+}"""
+ }
+ })
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom-tag3.py b/components/Markdown/demos/custom-tag3.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b1b6600c80ad77b963fa520a70f9c19aeaf5729
--- /dev/null
+++ b/components/Markdown/demos/custom-tag3.py
@@ -0,0 +1,31 @@
+import json
+import os
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+options = ["a", "b", "c"]
+
+
+def resolve_assets(relative_path):
+ return os.path.join(os.path.dirname(__file__), "../resources",
+ relative_path)
+
+
+with open(resolve_assets("./custom_components/custom_select.js"), 'r') as f:
+ custom_select_js = f.read()
+
+with gr.Blocks() as demo:
+ mgr.Markdown(value=f"""
+custom tag:
+""",
+ custom_components={
+ "custom-select": {
+ "props": ["options"],
+ "js": custom_select_js,
+ }
+ })
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom-tag4.py b/components/Markdown/demos/custom-tag4.py
new file mode 100644
index 0000000000000000000000000000000000000000..73bce9cfb79e90f00254b8c87f2a2d7296109c05
--- /dev/null
+++ b/components/Markdown/demos/custom-tag4.py
@@ -0,0 +1,39 @@
+import json
+import os
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+options = ["a", "b", "c"]
+
+
+def resolve_assets(relative_path):
+ return os.path.join(os.path.dirname(__file__), "../resources",
+ relative_path)
+
+
+with open(resolve_assets("./custom_components/custom_select.js"), 'r') as f:
+ custom_select_js = f.read()
+
+
+# 注意一定要显示指明类型,gradio 使用 ioc 机制注入值
+def fn(data: gr.EventData):
+ # custom {'index': [0, 1, 0], 'tag': 'custom-select', 'tag_index': 0, 'value': 'option A'}
+ print("custom value", data._data)
+
+
+with gr.Blocks() as demo:
+ md = mgr.Markdown(value=f"""
+custom tag:
+""",
+ custom_components={
+ "custom-select": {
+ "props": ["options"],
+ "js": custom_select_js,
+ }
+ })
+ md.custom(fn=fn)
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom_tags/accordion/accordion-title.py b/components/Markdown/demos/custom_tags/accordion/accordion-title.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e23e746616c5dfec35ea5a66f29497cdeb4afc8
--- /dev/null
+++ b/components/Markdown/demos/custom_tags/accordion/accordion-title.py
@@ -0,0 +1,19 @@
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+with gr.Blocks() as demo:
+ mgr.Markdown(f"""
+
+
+::accordion-title[调用 `tool`]
+
+```json
+{{"text": "风和日丽", "resolution": "1024*1024"}}
+```
+
+
+""")
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom_tags/accordion/basic.py b/components/Markdown/demos/custom_tags/accordion/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..60ca98080f9cf464f7024acd1cdf8ad85097d98e
--- /dev/null
+++ b/components/Markdown/demos/custom_tags/accordion/basic.py
@@ -0,0 +1,17 @@
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+with gr.Blocks() as demo:
+ mgr.Markdown(f"""
+
+
+```json
+{{"text": "风和日丽", "resolution": "1024*1024"}}
+```
+
+
+""")
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom_tags/select-box/basic.py b/components/Markdown/demos/custom_tags/select-box/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd2821db0eb043bae526d3d7c783a469fbb9209c
--- /dev/null
+++ b/components/Markdown/demos/custom_tags/select-box/basic.py
@@ -0,0 +1,21 @@
+import json
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+options = [{"label": "A", "value": "a"}, "b", "c"]
+
+with gr.Blocks() as demo:
+ mgr.Markdown(
+ f"""Single Select:
+
+Multiple Select:
+
+Vertical Direction:
+
+
+""", )
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom_tags/select-box/card_shape.py b/components/Markdown/demos/custom_tags/select-box/card_shape.py
new file mode 100644
index 0000000000000000000000000000000000000000..636b2974ca145b29ef73e413eec409a03d2104b4
--- /dev/null
+++ b/components/Markdown/demos/custom_tags/select-box/card_shape.py
@@ -0,0 +1,32 @@
+import json
+import os
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+# card 支持额外传入 imgSrc 属性作为样式封面
+options = [{
+ "label":
+ "A",
+ "imgSrc":
+ os.path.join(os.path.dirname(__file__), '../../../resources/screen.jpeg'),
+ "value":
+ "a"
+}, "b", "c", "d"]
+
+with gr.Blocks() as demo:
+ mgr.Markdown(
+ f"""
+
+Custom Columns:
+
+
+
+Vertical Direction:
+
+
+""")
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom_tags/select-box/card_shape_width_auto.py b/components/Markdown/demos/custom_tags/select-box/card_shape_width_auto.py
new file mode 100644
index 0000000000000000000000000000000000000000..9adbe8f45632c975fa7f541059fc45cfb6e51d7e
--- /dev/null
+++ b/components/Markdown/demos/custom_tags/select-box/card_shape_width_auto.py
@@ -0,0 +1,26 @@
+import json
+import os
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+# card 支持额外传入 imgSrc 属性作为样式封面
+options = [{
+ "label":
+ "A",
+ "imgSrc":
+ os.path.join(os.path.dirname(__file__), '../../../resources/screen.jpeg'),
+ "value":
+ "a"
+}, "b", "c", "d"]
+
+with gr.Blocks() as demo:
+ mgr.Markdown(
+ # 填写 item-width="auto"
+ f"""
+
+""", )
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/custom_tags/select-box/python_events.py b/components/Markdown/demos/custom_tags/select-box/python_events.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1487aaafb4bb2f07daf29a537c4413a908d378c
--- /dev/null
+++ b/components/Markdown/demos/custom_tags/select-box/python_events.py
@@ -0,0 +1,23 @@
+import json
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+options = [{"label": "A", "value": "a"}, "b", "c"]
+
+
+def fn(data: gr.EventData):
+ custom_data = data._data
+ if (custom_data["tag"] == "select-box"):
+ print(custom_data["value"]) # 用户选择的值,与 options 中的 value 对应
+
+
+with gr.Blocks() as demo:
+ md = mgr.Markdown(
+ f"
"
+ )
+ md.custom(fn=fn)
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/multimodal.py b/components/Markdown/demos/multimodal.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c6074105a216ceb4995dc721dfd7f8172947913
--- /dev/null
+++ b/components/Markdown/demos/multimodal.py
@@ -0,0 +1,31 @@
+import os
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+
+def resolve_assets(relative_path):
+ return os.path.join(os.path.dirname(__file__), "../resources",
+ relative_path)
+
+
+with gr.Blocks() as demo:
+ mgr.Markdown(f"""
+图片
+
+![image]({resolve_assets("bot.jpeg")})
+
+
+
+视频
+
+
+
+音频
+
+
+""")
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/demos/select-box.py b/components/Markdown/demos/select-box.py
new file mode 100644
index 0000000000000000000000000000000000000000..bfe975cb3c109bfc2c42420c19e5969d3b1e2921
--- /dev/null
+++ b/components/Markdown/demos/select-box.py
@@ -0,0 +1,32 @@
+import json
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+# label 为对用户展示值,value 为实际选择值
+options = [{"label": "A", "value": "a"}, "b", "c"]
+
+with gr.Blocks() as demo:
+ mgr.Markdown(f"""
+Single Select:
+
+Multiple Select:
+
+Vertical Direction:
+
+
+
+Card Shape:
+
+
+
+
+
+
+
+
+""")
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/Markdown/resources/audio.wav b/components/Markdown/resources/audio.wav
new file mode 100644
index 0000000000000000000000000000000000000000..105190ad88e2e177540361de340e54feb1587f3c
Binary files /dev/null and b/components/Markdown/resources/audio.wav differ
diff --git a/components/Markdown/resources/bot.jpeg b/components/Markdown/resources/bot.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..5fde8cc45f61b677c0581e6889b11e269c35be08
Binary files /dev/null and b/components/Markdown/resources/bot.jpeg differ
diff --git a/components/Markdown/resources/custom_components/custom_select.js b/components/Markdown/resources/custom_components/custom_select.js
new file mode 100644
index 0000000000000000000000000000000000000000..d2b2c5d8b41e7a50d72d219ffaad6f81c8c67824
--- /dev/null
+++ b/components/Markdown/resources/custom_components/custom_select.js
@@ -0,0 +1,26 @@
+(props, cc, { el, onMount }) => {
+ const options = JSON.parse(props.options);
+ el.innerHTML = `
+ ${options
+ .map((option) => {
+ return `
+
+
`;
+ })
+ .join('')}
+ `;
+ onMount(() => {
+ const inputs = Array.from(el.getElementsByTagName('input'));
+ Array.from(el.getElementsByTagName('label')).forEach((label, i) => {
+ label.addEventListener('click', () => {
+ inputs.forEach((input) => {
+ input.checked = false;
+ });
+ const input = label.getElementsByTagName('input')[0];
+ input.checked = true;
+ // 通过 cc.dispatch 向 python 侧发送通知
+ cc.dispatch(options[i]);
+ });
+ });
+ });
+};
diff --git a/components/Markdown/resources/dog.mp4 b/components/Markdown/resources/dog.mp4
new file mode 100644
index 0000000000000000000000000000000000000000..062b9c81317de43f392c56e9e03444bf8cc31d51
--- /dev/null
+++ b/components/Markdown/resources/dog.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:39d086ce29e48cf76e5042d2f3f0611ee46575f70fa3dc0c40dd4cfffde3d933
+size 8626383
diff --git a/components/Markdown/resources/screen.jpeg b/components/Markdown/resources/screen.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..574735acb117e86c5c0850e2b5489b8f8efa20cc
Binary files /dev/null and b/components/Markdown/resources/screen.jpeg differ
diff --git a/components/Markdown/resources/user.jpeg b/components/Markdown/resources/user.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..536948b6bd19cb0b49c44b74e2790198301520e5
Binary files /dev/null and b/components/Markdown/resources/user.jpeg differ
diff --git a/components/MultimodalInput/README.md b/components/MultimodalInput/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..d98029e5adc6f54f066f900211daf7fb1e8e5e15
--- /dev/null
+++ b/components/MultimodalInput/README.md
@@ -0,0 +1,50 @@
+# MutilmodalInput
+
+多模态输入框,支持上传文件、录音、照相等功能。
+
+- 支持文本输入+文件上传共同提交
+- 支持文件上传时的图片、音频预览
+- 提交内容作为 Chatbot 输入多模态内容作为用户输入问题自动匹配
+- 支持用户录音和拍照
+
+## 如何使用
+
+### 基本使用
+
+
+
+### 与 Chatbot 配合使用
+
+
+
+### 配置上传/提交按钮
+
+
+
+### 允许用户录音或拍照
+
+
+
+## API 及参数列表
+
+以下 API 均为在原有 gradio Textbox 外的额外拓展参数。
+
+### value
+
+接口定义:
+
+```python
+class MultimodalInputData(GradioModel):
+ files: List[Union[FileData, str]] = []
+ text: str
+```
+
+### props
+
+| 属性 | 类型 | 默认值 | 描述 |
+| ------------------- | -------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------- |
+| sources | list\[Literal\['upload', 'microphone','webcam'\]\] | \['upload'\] | 上传文件的类型列表。 "upload"会提供上文文件按钮。 "microphone"支持用户录音输入。 "webcam"支持用户照相生成图片或视频 |
+| webcam_props | dict | None | webcam 组件属性,目前支持传入mirror_webcam(bool)、include_audio(bool) |
+| upload_button_props | dict | None | 上传文件按钮属性,同 gradio UploadButton |
+| submit_button_props | dict | None | 提交按钮属性,同 gradio Button |
+| file_preview_props | dict | None | 文件预览组件属性,目前支持传入 height (int) |
diff --git a/components/MultimodalInput/app.py b/components/MultimodalInput/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..77372ef291165f2cf77a8944f3f8b18773972190
--- /dev/null
+++ b/components/MultimodalInput/app.py
@@ -0,0 +1,6 @@
+from components.Docs import Docs
+
+docs = Docs(__file__)
+
+if __name__ == "__main__":
+ docs.render().queue().launch()
diff --git a/components/MultimodalInput/demos/basic.py b/components/MultimodalInput/demos/basic.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8bc1d2066949125b52492a2d86cc82bb26fe9dd
--- /dev/null
+++ b/components/MultimodalInput/demos/basic.py
@@ -0,0 +1,16 @@
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+
+def fn(value):
+ # value 包含 text 与 files
+ print(value.text, value.files)
+
+
+with gr.Blocks() as demo:
+ input = mgr.MultimodalInput()
+ input.change(fn=fn, inputs=[input])
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/MultimodalInput/demos/config_buttons.py b/components/MultimodalInput/demos/config_buttons.py
new file mode 100644
index 0000000000000000000000000000000000000000..31ea46c6adfef7cecfdaa1b1cc804cb592d824cd
--- /dev/null
+++ b/components/MultimodalInput/demos/config_buttons.py
@@ -0,0 +1,16 @@
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+
+def fn(value):
+ print(value.text, value.files)
+
+
+with gr.Blocks() as demo:
+ input = mgr.MultimodalInput(upload_button_props=dict(variant="primary"),
+ submit_button_props=dict(visible=False))
+ input.change(fn=fn, inputs=[input])
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/MultimodalInput/demos/upload_sources.py b/components/MultimodalInput/demos/upload_sources.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab7c51f8e6cefeecfca19b1d6109a4a185e115b5
--- /dev/null
+++ b/components/MultimodalInput/demos/upload_sources.py
@@ -0,0 +1,15 @@
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+
+def fn(value):
+ print(value.text, value.files)
+
+
+with gr.Blocks() as demo:
+ input = mgr.MultimodalInput(sources=["upload", "microphone", "webcam"])
+ input.change(fn=fn, inputs=[input])
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/MultimodalInput/demos/with_chatbot.py b/components/MultimodalInput/demos/with_chatbot.py
new file mode 100644
index 0000000000000000000000000000000000000000..65700aebdae53c509c1978428446e4c44e4cc29a
--- /dev/null
+++ b/components/MultimodalInput/demos/with_chatbot.py
@@ -0,0 +1,36 @@
+import time
+
+import gradio as gr
+
+import modelscope_gradio_components as mgr
+
+
+def fn(input, chatbot):
+ chatbot.append([{
+ "text": input.text,
+ "files": input.files,
+ }, None])
+ yield {
+ user_input: mgr.MultimodalInput(interactive=False),
+ user_chatbot: chatbot
+ }
+ time.sleep(2)
+ chatbot[-1][1] = {"text": "Hello!"}
+ yield {user_chatbot: chatbot}
+
+
+# 打字机效果结束触发
+def flushed():
+ return mgr.MultimodalInput(interactive=True)
+
+
+with gr.Blocks() as demo:
+ user_chatbot = mgr.Chatbot()
+ user_input = mgr.MultimodalInput()
+ user_input.submit(fn=fn,
+ inputs=[user_input, user_chatbot],
+ outputs=[user_input, user_chatbot])
+ user_chatbot.flushed(fn=flushed, outputs=[user_input])
+
+if __name__ == "__main__":
+ demo.queue().launch()
diff --git a/components/parse_markdown.py b/components/parse_markdown.py
new file mode 100644
index 0000000000000000000000000000000000000000..447c5ab691176b3bce702f44f7f4a0451b25ae54
--- /dev/null
+++ b/components/parse_markdown.py
@@ -0,0 +1,81 @@
+from html.parser import HTMLParser
+
+
+def default_read_file(path):
+ with open(path, "r") as f:
+ return f.read()
+
+
+enable_tags = ["demo", "demo-prefix", "demo-suffix", "file"]
+
+
+class MarkdownParser(HTMLParser):
+
+ def __init__(self, read_file=None):
+ super().__init__()
+ self.value = [{"type": "text", "value": ""}]
+ self.tag_stack = []
+ self.read_file = read_file or default_read_file
+ self.current_tag = None
+
+ def get_value(self):
+ return self.value
+
+ def handle_data(self, data: str) -> None:
+ if self.value[-1]["type"] == "text":
+ self.value[-1]["value"] += data
+ elif self.current_tag is None:
+ self.value.append({"type": "text", "value": data})
+ elif self.current_tag == "demo-prefix":
+ self.value[-1]["prefix"] += data
+ elif self.current_tag == "demo-suffix":
+ self.value[-1]["suffix"] += data
+
+ def handle_startendtag(self, tag: str,
+ attrs: list[tuple[str, str | None]]) -> None:
+ if tag not in enable_tags:
+ self.handle_data(self.get_starttag_text())
+ return
+
+ def handle_starttag(self, tag: str,
+ attrs: list[tuple[str, str | None]]) -> None:
+ if (tag not in enable_tags):
+ self.handle_data(self.get_starttag_text())
+ return
+ if tag == "demo":
+ self.value.append({
+ "type": "demo",
+ "name": dict(attrs)["name"],
+ "prefix": "",
+ "suffix": ""
+ })
+ elif tag == "file":
+ content = self.read_file(dict(attrs)["src"])
+ if self.value[-1]["type"] == "text":
+ self.value[-1]["value"] += content
+ elif self.current_tag == "demo-prefix":
+ self.value[-1]["prefix"] += content
+ elif self.current_tag == "demo-suffix":
+ self.value[-1]["suffix"] += content
+ self.current_tag = tag
+ self.tag_stack.append(self.current_tag)
+
+ def handle_endtag(self, tag: str) -> None:
+
+ if (tag not in enable_tags):
+ self.handle_data(f"{tag}>")
+ return
+ if (len(self.tag_stack) > 0):
+ self.tag_stack.pop()
+ if (len(self.tag_stack) > 0):
+ self.current_tag = self.tag_stack[-1]
+ else:
+ self.current_tag = None
+ else:
+ self.current_tag = None
+
+
+def parse_markdown(markdown: str, read_file=None):
+ parser = MarkdownParser(read_file=read_file)
+ parser.feed(markdown)
+ return parser.get_value()
diff --git a/components/tab-link.js b/components/tab-link.js
new file mode 100644
index 0000000000000000000000000000000000000000..bcd9143dcafadafedfb4b711e704a6ca7e32b74c
--- /dev/null
+++ b/components/tab-link.js
@@ -0,0 +1,15 @@
+(props, cc, { onMount, el }) => {
+ onMount(() => {
+ el.addEventListener('click', () => {
+ cc.dispatch({
+ tab: props.tab,
+ component_tab: props['component-tab'],
+ });
+ });
+ });
+ const children = props.children[0].value;
+ el.innerHTML = `${children}`;
+ el.style.display = 'inline-block';
+ el.style.cursor = 'pointer';
+ el.style.color = 'var(--link-text-color)';
+};
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ab4bfbab5d7e2b99db0091abdd89c69fe47bf1bb
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+modelscope_gradio_components
+modelscope_gradio_components-0.0.1b8-py3-none-any.whl
\ No newline at end of file