simonduerr commited on
Commit
8b49f1d
1 Parent(s): 2d8998c

Upload folder using huggingface_hub

Browse files
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eggs/
2
+ dist/
3
+ *.pyc
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ __tmp/*
8
+ *.pyi
9
+ .mypycache
10
+ .ruff_cache
11
+ node_modules
12
+ backend/**/templates/
README.md CHANGED
@@ -1,12 +1,259 @@
1
  ---
2
- title: Gradio Molecule2d
3
- emoji: 🚀
4
- colorFrom: indigo
5
- colorTo: blue
 
6
  sdk: gradio
7
- sdk_version: 4.28.3
8
- app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ tags: [gradio-custom-component, SimpleTextbox, chemistry, smiles]
3
+ title: gradio_molecule2d
4
+ short_description: Input smiles strings and visualize them
5
+ colorFrom: blue
6
+ colorTo: yellow
7
  sdk: gradio
 
 
8
  pinned: false
9
+ app_file: space.py
10
  ---
11
 
12
+ # `gradio_molecule2d`
13
+ <a href="https://pypi.org/project/gradio_molecule2d/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_molecule2d"></a>
14
+
15
+ Input chemical molecules as smiles strings and visualize them
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install gradio_molecule2d
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+
27
+ import gradio as gr
28
+ from gradio_molecule2d import molecule2d
29
+
30
+
31
+ example = molecule2d().example_value()
32
+
33
+ demo = gr.Interface(
34
+ lambda x:x,
35
+ molecule2d(), # interactive version of your component
36
+ molecule2d(), # static version of your component
37
+ # examples=[[example]], # uncomment this line to view the "example version" of your component
38
+ )
39
+
40
+
41
+ if __name__ == "__main__":
42
+ demo.launch()
43
+
44
+ ```
45
+
46
+ ## `molecule2d`
47
+
48
+ ### Initialization
49
+
50
+ <table>
51
+ <thead>
52
+ <tr>
53
+ <th align="left">name</th>
54
+ <th align="left" style="width: 25%;">type</th>
55
+ <th align="left">default</th>
56
+ <th align="left">description</th>
57
+ </tr>
58
+ </thead>
59
+ <tbody>
60
+ <tr>
61
+ <td align="left"><code>value</code></td>
62
+ <td align="left" style="width: 25%;">
63
+
64
+ ```python
65
+ str | Callable | None
66
+ ```
67
+
68
+ </td>
69
+ <td align="left"><code>None</code></td>
70
+ <td align="left">default text to provide in textbox. If callable, the function will be called whenever the app loads to set the initial value of the component.</td>
71
+ </tr>
72
+
73
+ <tr>
74
+ <td align="left"><code>placeholder</code></td>
75
+ <td align="left" style="width: 25%;">
76
+
77
+ ```python
78
+ str | None
79
+ ```
80
+
81
+ </td>
82
+ <td align="left"><code>None</code></td>
83
+ <td align="left">placeholder hint to provide behind textbox.</td>
84
+ </tr>
85
+
86
+ <tr>
87
+ <td align="left"><code>label</code></td>
88
+ <td align="left" style="width: 25%;">
89
+
90
+ ```python
91
+ str | None
92
+ ```
93
+
94
+ </td>
95
+ <td align="left"><code>None</code></td>
96
+ <td align="left">component name in interface.</td>
97
+ </tr>
98
+
99
+ <tr>
100
+ <td align="left"><code>every</code></td>
101
+ <td align="left" style="width: 25%;">
102
+
103
+ ```python
104
+ float | None
105
+ ```
106
+
107
+ </td>
108
+ <td align="left"><code>None</code></td>
109
+ <td align="left">If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.</td>
110
+ </tr>
111
+
112
+ <tr>
113
+ <td align="left"><code>show_label</code></td>
114
+ <td align="left" style="width: 25%;">
115
+
116
+ ```python
117
+ bool | None
118
+ ```
119
+
120
+ </td>
121
+ <td align="left"><code>None</code></td>
122
+ <td align="left">if True, will display label.</td>
123
+ </tr>
124
+
125
+ <tr>
126
+ <td align="left"><code>scale</code></td>
127
+ <td align="left" style="width: 25%;">
128
+
129
+ ```python
130
+ int | None
131
+ ```
132
+
133
+ </td>
134
+ <td align="left"><code>None</code></td>
135
+ <td align="left">relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.</td>
136
+ </tr>
137
+
138
+ <tr>
139
+ <td align="left"><code>min_width</code></td>
140
+ <td align="left" style="width: 25%;">
141
+
142
+ ```python
143
+ int
144
+ ```
145
+
146
+ </td>
147
+ <td align="left"><code>160</code></td>
148
+ <td align="left">minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.</td>
149
+ </tr>
150
+
151
+ <tr>
152
+ <td align="left"><code>interactive</code></td>
153
+ <td align="left" style="width: 25%;">
154
+
155
+ ```python
156
+ bool | None
157
+ ```
158
+
159
+ </td>
160
+ <td align="left"><code>None</code></td>
161
+ <td align="left">if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.</td>
162
+ </tr>
163
+
164
+ <tr>
165
+ <td align="left"><code>visible</code></td>
166
+ <td align="left" style="width: 25%;">
167
+
168
+ ```python
169
+ bool
170
+ ```
171
+
172
+ </td>
173
+ <td align="left"><code>True</code></td>
174
+ <td align="left">If False, component will be hidden.</td>
175
+ </tr>
176
+
177
+ <tr>
178
+ <td align="left"><code>rtl</code></td>
179
+ <td align="left" style="width: 25%;">
180
+
181
+ ```python
182
+ bool
183
+ ```
184
+
185
+ </td>
186
+ <td align="left"><code>False</code></td>
187
+ <td align="left">If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right.</td>
188
+ </tr>
189
+
190
+ <tr>
191
+ <td align="left"><code>elem_id</code></td>
192
+ <td align="left" style="width: 25%;">
193
+
194
+ ```python
195
+ str | None
196
+ ```
197
+
198
+ </td>
199
+ <td align="left"><code>None</code></td>
200
+ <td align="left">An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
201
+ </tr>
202
+
203
+ <tr>
204
+ <td align="left"><code>elem_classes</code></td>
205
+ <td align="left" style="width: 25%;">
206
+
207
+ ```python
208
+ list[str] | str | None
209
+ ```
210
+
211
+ </td>
212
+ <td align="left"><code>None</code></td>
213
+ <td align="left">An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
214
+ </tr>
215
+
216
+ <tr>
217
+ <td align="left"><code>render</code></td>
218
+ <td align="left" style="width: 25%;">
219
+
220
+ ```python
221
+ bool
222
+ ```
223
+
224
+ </td>
225
+ <td align="left"><code>True</code></td>
226
+ <td align="left">If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.</td>
227
+ </tr>
228
+ </tbody></table>
229
+
230
+
231
+ ### Events
232
+
233
+ | name | description |
234
+ |:-----|:------------|
235
+ | `change` | Triggered when the value of the molecule2d changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
236
+ | `input` | This listener is triggered when the user changes the value of the molecule2d. |
237
+ | `submit` | This listener is triggered when the user presses the Enter key while the molecule2d is focused. |
238
+
239
+
240
+
241
+ ### User function
242
+
243
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
244
+
245
+ - When used as an Input, the component only impacts the input signature of the user function.
246
+ - When used as an output, the component only impacts the return signature of the user function.
247
+
248
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
249
+
250
+ - **As output:** Is passed, passes text value as a {str} into the function.
251
+ - **As input:** Should return, expects a {str} returned from function and sets textarea value to it.
252
+
253
+ ```python
254
+ def predict(
255
+ value: str | None
256
+ ) -> str | None:
257
+ return value
258
+ ```
259
+
__init__.py ADDED
File without changes
app.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from gradio_molecule2d import molecule2d
4
+
5
+
6
+ example = molecule2d().example_value()
7
+
8
+ demo = gr.Interface(
9
+ lambda x:x,
10
+ molecule2d(), # interactive version of your component
11
+ molecule2d(), # static version of your component
12
+ # examples=[[example]], # uncomment this line to view the "example version" of your component
13
+ )
14
+
15
+
16
+ if __name__ == "__main__":
17
+ demo.launch()
css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_molecule2d
space.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from app import demo as app
4
+ import os
5
+
6
+ _docs = {'molecule2d': {'description': 'Creates a very simple textbox for user to enter string input or display string output.', 'members': {'__init__': {'value': {'type': 'str | Callable | None', 'default': 'None', 'description': 'default text to provide in textbox. If callable, the function will be called whenever the app loads to set the initial value of the component.'}, 'placeholder': {'type': 'str | None', 'default': 'None', 'description': 'placeholder hint to provide behind textbox.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'component name in interface.'}, 'every': {'type': 'float | None', 'default': 'None', 'description': "If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute."}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'rtl': {'type': 'bool', 'default': 'False', 'description': 'If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}}, 'postprocess': {'value': {'type': 'str | None', 'description': 'Expects a {str} returned from function and sets textarea value to it.'}}, 'preprocess': {'return': {'type': 'str | None', 'description': 'Passes text value as a {str} into the function.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the molecule2d changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'input': {'type': None, 'default': None, 'description': 'This listener is triggered when the user changes the value of the molecule2d.'}, 'submit': {'type': None, 'default': None, 'description': 'This listener is triggered when the user presses the Enter key while the molecule2d is focused.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'molecule2d': []}}}
7
+
8
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
+
10
+ with gr.Blocks(
11
+ css=abs_path,
12
+ theme=gr.themes.Default(
13
+ font_mono=[
14
+ gr.themes.GoogleFont("Inconsolata"),
15
+ "monospace",
16
+ ],
17
+ ),
18
+ ) as demo:
19
+ gr.Markdown(
20
+ """
21
+ # `gradio_molecule2d`
22
+
23
+ <div style="display: flex; gap: 7px;">
24
+ <a href="https://pypi.org/project/gradio_molecule2d/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_molecule2d"></a>
25
+ </div>
26
+
27
+ Input chemical molecules as smiles strings and visualize them
28
+ """, elem_classes=["md-custom"], header_links=True)
29
+ app.render()
30
+ gr.Markdown(
31
+ """
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install gradio_molecule2d
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+
42
+ import gradio as gr
43
+ from gradio_molecule2d import molecule2d
44
+
45
+
46
+ example = molecule2d().example_value()
47
+
48
+ demo = gr.Interface(
49
+ lambda x:x,
50
+ molecule2d(), # interactive version of your component
51
+ molecule2d(), # static version of your component
52
+ # examples=[[example]], # uncomment this line to view the "example version" of your component
53
+ )
54
+
55
+
56
+ if __name__ == "__main__":
57
+ demo.launch()
58
+
59
+ ```
60
+ """, elem_classes=["md-custom"], header_links=True)
61
+
62
+
63
+ gr.Markdown("""
64
+ ## `molecule2d`
65
+
66
+ ### Initialization
67
+ """, elem_classes=["md-custom"], header_links=True)
68
+
69
+ gr.ParamViewer(value=_docs["molecule2d"]["members"]["__init__"], linkify=[])
70
+
71
+
72
+ gr.Markdown("### Events")
73
+ gr.ParamViewer(value=_docs["molecule2d"]["events"], linkify=['Event'])
74
+
75
+
76
+
77
+
78
+ gr.Markdown("""
79
+
80
+ ### User function
81
+
82
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
83
+
84
+ - When used as an Input, the component only impacts the input signature of the user function.
85
+ - When used as an output, the component only impacts the return signature of the user function.
86
+
87
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
88
+
89
+ - **As input:** Is passed, passes text value as a {str} into the function.
90
+ - **As output:** Should return, expects a {str} returned from function and sets textarea value to it.
91
+
92
+ ```python
93
+ def predict(
94
+ value: str | None
95
+ ) -> str | None:
96
+ return value
97
+ ```
98
+ """, elem_classes=["md-custom", "molecule2d-user-fn"], header_links=True)
99
+
100
+
101
+
102
+
103
+ demo.load(None, js=r"""function() {
104
+ const refs = {};
105
+ const user_fn_refs = {
106
+ molecule2d: [], };
107
+ requestAnimationFrame(() => {
108
+
109
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
110
+ if (refs.length > 0) {
111
+ const el = document.querySelector(`.${key}-user-fn`);
112
+ if (!el) return;
113
+ refs.forEach(ref => {
114
+ el.innerHTML = el.innerHTML.replace(
115
+ new RegExp("\\b"+ref+"\\b", "g"),
116
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
117
+ );
118
+ })
119
+ }
120
+ })
121
+
122
+ Object.entries(refs).forEach(([key, refs]) => {
123
+ if (refs.length > 0) {
124
+ const el = document.querySelector(`.${key}`);
125
+ if (!el) return;
126
+ refs.forEach(ref => {
127
+ el.innerHTML = el.innerHTML.replace(
128
+ new RegExp("\\b"+ref+"\\b", "g"),
129
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
130
+ );
131
+ })
132
+ }
133
+ })
134
+ })
135
+ }
136
+
137
+ """)
138
+
139
+ demo.launch()
src/.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eggs/
2
+ dist/
3
+ *.pyc
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ __tmp/*
8
+ *.pyi
9
+ .mypycache
10
+ .ruff_cache
11
+ node_modules
12
+ backend/**/templates/
src/README.md ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags: [gradio-custom-component, SimpleTextbox, chemistry, smiles]
3
+ title: gradio_molecule2d
4
+ short_description: Input smiles strings and visualize them
5
+ colorFrom: blue
6
+ colorTo: yellow
7
+ sdk: gradio
8
+ pinned: false
9
+ app_file: space.py
10
+ ---
11
+
12
+ # `gradio_molecule2d`
13
+ <a href="https://pypi.org/project/gradio_molecule2d/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_molecule2d"></a>
14
+
15
+ Input chemical molecules as smiles strings and visualize them
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install gradio_molecule2d
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+
27
+ import gradio as gr
28
+ from gradio_molecule2d import molecule2d
29
+
30
+
31
+ example = molecule2d().example_value()
32
+
33
+ demo = gr.Interface(
34
+ lambda x:x,
35
+ molecule2d(), # interactive version of your component
36
+ molecule2d(), # static version of your component
37
+ # examples=[[example]], # uncomment this line to view the "example version" of your component
38
+ )
39
+
40
+
41
+ if __name__ == "__main__":
42
+ demo.launch()
43
+
44
+ ```
45
+
46
+ ## `molecule2d`
47
+
48
+ ### Initialization
49
+
50
+ <table>
51
+ <thead>
52
+ <tr>
53
+ <th align="left">name</th>
54
+ <th align="left" style="width: 25%;">type</th>
55
+ <th align="left">default</th>
56
+ <th align="left">description</th>
57
+ </tr>
58
+ </thead>
59
+ <tbody>
60
+ <tr>
61
+ <td align="left"><code>value</code></td>
62
+ <td align="left" style="width: 25%;">
63
+
64
+ ```python
65
+ str | Callable | None
66
+ ```
67
+
68
+ </td>
69
+ <td align="left"><code>None</code></td>
70
+ <td align="left">default text to provide in textbox. If callable, the function will be called whenever the app loads to set the initial value of the component.</td>
71
+ </tr>
72
+
73
+ <tr>
74
+ <td align="left"><code>placeholder</code></td>
75
+ <td align="left" style="width: 25%;">
76
+
77
+ ```python
78
+ str | None
79
+ ```
80
+
81
+ </td>
82
+ <td align="left"><code>None</code></td>
83
+ <td align="left">placeholder hint to provide behind textbox.</td>
84
+ </tr>
85
+
86
+ <tr>
87
+ <td align="left"><code>label</code></td>
88
+ <td align="left" style="width: 25%;">
89
+
90
+ ```python
91
+ str | None
92
+ ```
93
+
94
+ </td>
95
+ <td align="left"><code>None</code></td>
96
+ <td align="left">component name in interface.</td>
97
+ </tr>
98
+
99
+ <tr>
100
+ <td align="left"><code>every</code></td>
101
+ <td align="left" style="width: 25%;">
102
+
103
+ ```python
104
+ float | None
105
+ ```
106
+
107
+ </td>
108
+ <td align="left"><code>None</code></td>
109
+ <td align="left">If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.</td>
110
+ </tr>
111
+
112
+ <tr>
113
+ <td align="left"><code>show_label</code></td>
114
+ <td align="left" style="width: 25%;">
115
+
116
+ ```python
117
+ bool | None
118
+ ```
119
+
120
+ </td>
121
+ <td align="left"><code>None</code></td>
122
+ <td align="left">if True, will display label.</td>
123
+ </tr>
124
+
125
+ <tr>
126
+ <td align="left"><code>scale</code></td>
127
+ <td align="left" style="width: 25%;">
128
+
129
+ ```python
130
+ int | None
131
+ ```
132
+
133
+ </td>
134
+ <td align="left"><code>None</code></td>
135
+ <td align="left">relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.</td>
136
+ </tr>
137
+
138
+ <tr>
139
+ <td align="left"><code>min_width</code></td>
140
+ <td align="left" style="width: 25%;">
141
+
142
+ ```python
143
+ int
144
+ ```
145
+
146
+ </td>
147
+ <td align="left"><code>160</code></td>
148
+ <td align="left">minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.</td>
149
+ </tr>
150
+
151
+ <tr>
152
+ <td align="left"><code>interactive</code></td>
153
+ <td align="left" style="width: 25%;">
154
+
155
+ ```python
156
+ bool | None
157
+ ```
158
+
159
+ </td>
160
+ <td align="left"><code>None</code></td>
161
+ <td align="left">if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.</td>
162
+ </tr>
163
+
164
+ <tr>
165
+ <td align="left"><code>visible</code></td>
166
+ <td align="left" style="width: 25%;">
167
+
168
+ ```python
169
+ bool
170
+ ```
171
+
172
+ </td>
173
+ <td align="left"><code>True</code></td>
174
+ <td align="left">If False, component will be hidden.</td>
175
+ </tr>
176
+
177
+ <tr>
178
+ <td align="left"><code>rtl</code></td>
179
+ <td align="left" style="width: 25%;">
180
+
181
+ ```python
182
+ bool
183
+ ```
184
+
185
+ </td>
186
+ <td align="left"><code>False</code></td>
187
+ <td align="left">If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right.</td>
188
+ </tr>
189
+
190
+ <tr>
191
+ <td align="left"><code>elem_id</code></td>
192
+ <td align="left" style="width: 25%;">
193
+
194
+ ```python
195
+ str | None
196
+ ```
197
+
198
+ </td>
199
+ <td align="left"><code>None</code></td>
200
+ <td align="left">An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
201
+ </tr>
202
+
203
+ <tr>
204
+ <td align="left"><code>elem_classes</code></td>
205
+ <td align="left" style="width: 25%;">
206
+
207
+ ```python
208
+ list[str] | str | None
209
+ ```
210
+
211
+ </td>
212
+ <td align="left"><code>None</code></td>
213
+ <td align="left">An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
214
+ </tr>
215
+
216
+ <tr>
217
+ <td align="left"><code>render</code></td>
218
+ <td align="left" style="width: 25%;">
219
+
220
+ ```python
221
+ bool
222
+ ```
223
+
224
+ </td>
225
+ <td align="left"><code>True</code></td>
226
+ <td align="left">If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.</td>
227
+ </tr>
228
+ </tbody></table>
229
+
230
+
231
+ ### Events
232
+
233
+ | name | description |
234
+ |:-----|:------------|
235
+ | `change` | Triggered when the value of the molecule2d changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
236
+ | `input` | This listener is triggered when the user changes the value of the molecule2d. |
237
+ | `submit` | This listener is triggered when the user presses the Enter key while the molecule2d is focused. |
238
+
239
+
240
+
241
+ ### User function
242
+
243
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
244
+
245
+ - When used as an Input, the component only impacts the input signature of the user function.
246
+ - When used as an output, the component only impacts the return signature of the user function.
247
+
248
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
249
+
250
+ - **As output:** Is passed, passes text value as a {str} into the function.
251
+ - **As input:** Should return, expects a {str} returned from function and sets textarea value to it.
252
+
253
+ ```python
254
+ def predict(
255
+ value: str | None
256
+ ) -> str | None:
257
+ return value
258
+ ```
259
+
src/backend/gradio_molecule2d/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+
2
+ from .molecule2d import molecule2d
3
+
4
+ __all__ = ['molecule2d']
src/backend/gradio_molecule2d/molecule2d.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable
4
+
5
+ from gradio.components.base import FormComponent
6
+ from gradio.events import Events
7
+
8
+
9
+ class molecule2d(FormComponent):
10
+ """
11
+ Creates a very simple textbox for user to enter string input or display string output.
12
+ """
13
+
14
+ EVENTS = [
15
+ Events.change,
16
+ Events.input,
17
+ Events.submit,
18
+ ]
19
+
20
+ def __init__(
21
+ self,
22
+ value: str | Callable | None = None,
23
+ *,
24
+ placeholder: str | None = None,
25
+ label: str | None = None,
26
+ every: float | None = None,
27
+ show_label: bool | None = None,
28
+ scale: int | None = None,
29
+ min_width: int = 160,
30
+ interactive: bool | None = None,
31
+ visible: bool = True,
32
+ rtl: bool = False,
33
+ elem_id: str | None = None,
34
+ elem_classes: list[str] | str | None = None,
35
+ render: bool = True,
36
+ ):
37
+ """
38
+ Parameters:
39
+ value: default text to provide in textbox. If callable, the function will be called whenever the app loads to set the initial value of the component.
40
+ placeholder: placeholder hint to provide behind textbox.
41
+ label: component name in interface.
42
+ every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
43
+ show_label: if True, will display label.
44
+ scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.
45
+ min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
46
+ interactive: if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
47
+ visible: If False, component will be hidden.
48
+ rtl: If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right.
49
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
50
+ elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
51
+ render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
52
+ """
53
+ self.placeholder = placeholder
54
+ self.rtl = rtl
55
+ super().__init__(
56
+ label=label,
57
+ every=every,
58
+ show_label=show_label,
59
+ scale=scale,
60
+ min_width=min_width,
61
+ interactive=interactive,
62
+ visible=visible,
63
+ elem_id=elem_id,
64
+ elem_classes=elem_classes,
65
+ value=value,
66
+ render=render,
67
+ )
68
+
69
+ def preprocess(self, payload: str | None) -> str | None:
70
+ """
71
+ Parameters:
72
+ payload: the text entered in the textarea.
73
+ Returns:
74
+ Passes text value as a {str} into the function.
75
+ """
76
+ return None if payload is None else str(payload)
77
+
78
+ def postprocess(self, value: str | None) -> str | None:
79
+ """
80
+ Parameters:
81
+ value: Expects a {str} returned from function and sets textarea value to it.
82
+ Returns:
83
+ The value to display in the textarea.
84
+ """
85
+ return None if value is None else str(value)
86
+
87
+ def api_info(self) -> dict[str, Any]:
88
+ return {"type": "string"}
89
+
90
+ def example_payload(self) -> Any:
91
+ return "Hello!!"
92
+
93
+ def example_value(self) -> Any:
94
+ return "Hello!!"
src/backend/gradio_molecule2d/templates/component/index.js ADDED
The diff for this file is too large to render. See raw diff
 
src/backend/gradio_molecule2d/templates/component/style.css ADDED
@@ -0,0 +1 @@
 
 
1
+ .block.svelte-nl1om8{position:relative;margin:0;box-shadow:var(--block-shadow);border-width:var(--block-border-width);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);width:100%;line-height:var(--line-sm)}.block.border_focus.svelte-nl1om8{border-color:var(--color-accent)}.block.border_contrast.svelte-nl1om8{border-color:var(--body-text-color)}.padded.svelte-nl1om8{padding:var(--block-padding)}.hidden.svelte-nl1om8{display:none}.hide-container.svelte-nl1om8{margin:0;box-shadow:none;--block-border-width:0;background:transparent;padding:0;overflow:visible}div.svelte-1hnfib2{margin-bottom:var(--spacing-lg);color:var(--block-info-text-color);font-weight:var(--block-info-text-weight);font-size:var(--block-info-text-size);line-height:var(--line-sm)}span.has-info.svelte-22c38v{margin-bottom:var(--spacing-xs)}span.svelte-22c38v:not(.has-info){margin-bottom:var(--spacing-lg)}span.svelte-22c38v{display:inline-block;position:relative;z-index:var(--layer-4);border:solid var(--block-title-border-width) var(--block-title-border-color);border-radius:var(--block-title-radius);background:var(--block-title-background-fill);padding:var(--block-title-padding);color:var(--block-title-text-color);font-weight:var(--block-title-text-weight);font-size:var(--block-title-text-size);line-height:var(--line-sm)}.hide.svelte-22c38v{margin:0;height:0}label.svelte-9gxdi0{display:inline-flex;align-items:center;z-index:var(--layer-2);box-shadow:var(--block-label-shadow);border:var(--block-label-border-width) solid var(--border-color-primary);border-top:none;border-left:none;border-radius:var(--block-label-radius);background:var(--block-label-background-fill);padding:var(--block-label-padding);pointer-events:none;color:var(--block-label-text-color);font-weight:var(--block-label-text-weight);font-size:var(--block-label-text-size);line-height:var(--line-sm)}.gr-group label.svelte-9gxdi0{border-top-left-radius:0}label.float.svelte-9gxdi0{position:absolute;top:var(--block-label-margin);left:var(--block-label-margin)}label.svelte-9gxdi0:not(.float){position:static;margin-top:var(--block-label-margin);margin-left:var(--block-label-margin)}.hide.svelte-9gxdi0{height:0}span.svelte-9gxdi0{opacity:.8;margin-right:var(--size-2);width:calc(var(--block-label-text-size) - 1px);height:calc(var(--block-label-text-size) - 1px)}.hide-label.svelte-9gxdi0{box-shadow:none;border-width:0;background:transparent;overflow:visible}button.svelte-1lrphxw{display:flex;justify-content:center;align-items:center;gap:1px;z-index:var(--layer-2);border-radius:var(--radius-sm);color:var(--block-label-text-color);border:1px solid transparent}button[disabled].svelte-1lrphxw{opacity:.5;box-shadow:none}button[disabled].svelte-1lrphxw:hover{cursor:not-allowed}.padded.svelte-1lrphxw{padding:2px;background:var(--bg-color);box-shadow:var(--shadow-drop);border:1px solid var(--button-secondary-border-color)}button.svelte-1lrphxw:hover,button.highlight.svelte-1lrphxw{cursor:pointer;color:var(--color-accent)}.padded.svelte-1lrphxw:hover{border:2px solid var(--button-secondary-border-color-hover);padding:1px;color:var(--block-label-text-color)}span.svelte-1lrphxw{padding:0 1px;font-size:10px}div.svelte-1lrphxw{padding:2px;display:flex;align-items:flex-end}.small.svelte-1lrphxw{width:14px;height:14px}.medium.svelte-1lrphxw{width:20px;height:20px}.large.svelte-1lrphxw{width:22px;height:22px}.pending.svelte-1lrphxw{animation:svelte-1lrphxw-flash .5s infinite}@keyframes svelte-1lrphxw-flash{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.transparent.svelte-1lrphxw{background:transparent;border:none;box-shadow:none}.empty.svelte-3w3rth{display:flex;justify-content:center;align-items:center;margin-top:calc(0px - var(--size-6));height:var(--size-full)}.icon.svelte-3w3rth{opacity:.5;height:var(--size-5);color:var(--body-text-color)}.small.svelte-3w3rth{min-height:calc(var(--size-32) - 20px)}.large.svelte-3w3rth{min-height:calc(var(--size-64) - 20px)}.unpadded_box.svelte-3w3rth{margin-top:0}.small_parent.svelte-3w3rth{min-height:100%!important}.dropdown-arrow.svelte-145leq6{fill:currentColor}.wrap.svelte-kzcjhc{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);line-height:var(--line-md);height:100%;padding-top:var(--size-3)}.or.svelte-kzcjhc{color:var(--body-text-color-subdued);display:flex}.icon-wrap.svelte-kzcjhc{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-kzcjhc{font-size:var(--text-lg)}}.hovered.svelte-kzcjhc{color:var(--color-accent)}div.svelte-q32hvf{border-top:1px solid transparent;display:flex;max-height:100%;justify-content:center;align-items:center;gap:var(--spacing-sm);height:auto;align-items:flex-end;color:var(--block-label-text-color);flex-shrink:0}.show_border.svelte-q32hvf{border-top:1px solid var(--block-border-color);margin-top:var(--spacing-xxl);box-shadow:var(--shadow-drop)}.source-selection.svelte-1jp3vgd{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:95%;bottom:0;left:0;right:0;margin-left:auto;margin-right:auto}.icon.svelte-1jp3vgd{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.selected.svelte-1jp3vgd{color:var(--color-accent)}.icon.svelte-1jp3vgd:hover,.icon.svelte-1jp3vgd:focus{color:var(--color-accent)}svg.svelte-43sxxs.svelte-43sxxs{width:var(--size-20);height:var(--size-20)}svg.svelte-43sxxs path.svelte-43sxxs{fill:var(--loader-color)}div.svelte-43sxxs.svelte-43sxxs{z-index:var(--layer-2)}.margin.svelte-43sxxs.svelte-43sxxs{margin:var(--size-4)}.wrap.svelte-1yk38uw.svelte-1yk38uw{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-top);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden}.wrap.center.svelte-1yk38uw.svelte-1yk38uw{top:0;right:0;left:0}.wrap.default.svelte-1yk38uw.svelte-1yk38uw{top:0;right:0;bottom:0;left:0}.hide.svelte-1yk38uw.svelte-1yk38uw{opacity:0;pointer-events:none}.generating.svelte-1yk38uw.svelte-1yk38uw{animation:svelte-1yk38uw-pulse 2s cubic-bezier(.4,0,.6,1) infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1)}.translucent.svelte-1yk38uw.svelte-1yk38uw{background:none}@keyframes svelte-1yk38uw-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-1yk38uw.svelte-1yk38uw{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-1yk38uw.svelte-1yk38uw{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-1yk38uw.svelte-1yk38uw{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-1yk38uw.svelte-1yk38uw{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-1yk38uw.svelte-1yk38uw{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-1yk38uw.svelte-1yk38uw{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-1yk38uw.svelte-1yk38uw{position:absolute;top:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-1yk38uw.svelte-1yk38uw{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-1yk38uw.svelte-1yk38uw{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-1yk38uw .progress-text.svelte-1yk38uw{background:var(--block-background-fill)}.border.svelte-1yk38uw.svelte-1yk38uw{border:1px solid var(--border-color-primary)}.clear-status.svelte-1yk38uw.svelte-1yk38uw{position:absolute;display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.toast-body.svelte-solcu7{display:flex;position:relative;right:0;left:0;align-items:center;margin:var(--size-6) var(--size-4);margin:auto;border-radius:var(--container-radius);overflow:hidden;pointer-events:auto}.toast-body.error.svelte-solcu7{border:1px solid var(--color-red-700);background:var(--color-red-50)}.dark .toast-body.error.svelte-solcu7{border:1px solid var(--color-red-500);background-color:var(--color-grey-950)}.toast-body.warning.svelte-solcu7{border:1px solid var(--color-yellow-700);background:var(--color-yellow-50)}.dark .toast-body.warning.svelte-solcu7{border:1px solid var(--color-yellow-500);background-color:var(--color-grey-950)}.toast-body.info.svelte-solcu7{border:1px solid var(--color-grey-700);background:var(--color-grey-50)}.dark .toast-body.info.svelte-solcu7{border:1px solid var(--color-grey-500);background-color:var(--color-grey-950)}.toast-title.svelte-solcu7{display:flex;align-items:center;font-weight:var(--weight-bold);font-size:var(--text-lg);line-height:var(--line-sm);text-transform:capitalize}.toast-title.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-title.error.svelte-solcu7{color:var(--color-red-50)}.toast-title.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-title.warning.svelte-solcu7{color:var(--color-yellow-50)}.toast-title.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-title.info.svelte-solcu7{color:var(--color-grey-50)}.toast-close.svelte-solcu7{margin:0 var(--size-3);border-radius:var(--size-3);padding:0px var(--size-1-5);font-size:var(--size-5);line-height:var(--size-5)}.toast-close.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-close.error.svelte-solcu7{color:var(--color-red-500)}.toast-close.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-close.warning.svelte-solcu7{color:var(--color-yellow-500)}.toast-close.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-close.info.svelte-solcu7{color:var(--color-grey-500)}.toast-text.svelte-solcu7{font-size:var(--text-lg)}.toast-text.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-text.error.svelte-solcu7{color:var(--color-red-50)}.toast-text.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-text.warning.svelte-solcu7{color:var(--color-yellow-50)}.toast-text.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-text.info.svelte-solcu7{color:var(--color-grey-50)}.toast-details.svelte-solcu7{margin:var(--size-3) var(--size-3) var(--size-3) 0;width:100%}.toast-icon.svelte-solcu7{display:flex;position:absolute;position:relative;flex-shrink:0;justify-content:center;align-items:center;margin:var(--size-2);border-radius:var(--radius-full);padding:var(--size-1);padding-left:calc(var(--size-1) - 1px);width:35px;height:35px}.toast-icon.error.svelte-solcu7{color:var(--color-red-700)}.dark .toast-icon.error.svelte-solcu7{color:var(--color-red-500)}.toast-icon.warning.svelte-solcu7{color:var(--color-yellow-700)}.dark .toast-icon.warning.svelte-solcu7{color:var(--color-yellow-500)}.toast-icon.info.svelte-solcu7{color:var(--color-grey-700)}.dark .toast-icon.info.svelte-solcu7{color:var(--color-grey-500)}@keyframes svelte-solcu7-countdown{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.timer.svelte-solcu7{position:absolute;bottom:0;left:0;transform-origin:0 0;animation:svelte-solcu7-countdown 10s linear forwards;width:100%;height:var(--size-1)}.timer.error.svelte-solcu7{background:var(--color-red-700)}.dark .timer.error.svelte-solcu7{background:var(--color-red-500)}.timer.warning.svelte-solcu7{background:var(--color-yellow-700)}.dark .timer.warning.svelte-solcu7{background:var(--color-yellow-500)}.timer.info.svelte-solcu7{background:var(--color-grey-700)}.dark .timer.info.svelte-solcu7{background:var(--color-grey-500)}.toast-wrap.svelte-gatr8h{display:flex;position:fixed;top:var(--size-4);right:var(--size-4);flex-direction:column;align-items:end;gap:var(--size-2);z-index:var(--layer-top);width:calc(100% - var(--size-8))}@media (--screen-sm){.toast-wrap.svelte-gatr8h{width:calc(var(--size-96) + var(--size-10))}}svg.svelte-zjukdr{width:100%;height:400px}div.svelte-181q4hi{display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.not-absolute.svelte-181q4hi{margin:var(--size-1)}iframe.svelte-rryiqv{width:100%;height:65vh}/*! tailwindcss v4.0.0-alpha.14 | MIT License | https://tailwindcss.com */@layer theme{:root{--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-family-sans);--default-font-feature-settings:var(--font-family-sans--font-feature-settings);--default-font-variation-settings:var(--font-family-sans--font-variation-settings);--default-mono-font-family:var(--font-family-mono);--default-mono-font-feature-settings:var(--font-family-mono--font-feature-settings);--default-mono-font-variation-settings:var(--font-family-mono--font-variation-settings);--breakpoint-sm:640px;--breakpoint-md:768px;--breakpoint-lg:1024px;--breakpoint-xl:1280px;--breakpoint-2xl:1536px;--color-black:#000;--color-white:#fff;--color-slate-50:#f8fafc;--color-slate-100:#f1f5f9;--color-slate-200:#e2e8f0;--color-slate-300:#cbd5e1;--color-slate-400:#94a3b8;--color-slate-500:#64748b;--color-slate-600:#475569;--color-slate-700:#334155;--color-slate-800:#1e293b;--color-slate-900:#0f172a;--color-slate-950:#020617;--color-gray-50:#f9fafb;--color-gray-100:#f3f4f6;--color-gray-200:#e5e7eb;--color-gray-300:#d1d5db;--color-gray-400:#9ca3af;--color-gray-500:#6b7280;--color-gray-600:#4b5563;--color-gray-700:#374151;--color-gray-800:#1f2937;--color-gray-900:#111827;--color-gray-950:#030712;--color-zinc-50:#fafafa;--color-zinc-100:#f4f4f5;--color-zinc-200:#e4e4e7;--color-zinc-300:#d4d4d8;--color-zinc-400:#a1a1aa;--color-zinc-500:#71717a;--color-zinc-600:#52525b;--color-zinc-700:#3f3f46;--color-zinc-800:#27272a;--color-zinc-900:#18181b;--color-zinc-950:#09090b;--color-neutral-50:#fafafa;--color-neutral-100:#f5f5f5;--color-neutral-200:#e5e5e5;--color-neutral-300:#d4d4d4;--color-neutral-400:#a3a3a3;--color-neutral-500:#737373;--color-neutral-600:#525252;--color-neutral-700:#404040;--color-neutral-800:#262626;--color-neutral-900:#171717;--color-neutral-950:#0a0a0a;--color-stone-50:#fafaf9;--color-stone-100:#f5f5f4;--color-stone-200:#e7e5e4;--color-stone-300:#d6d3d1;--color-stone-400:#a8a29e;--color-stone-500:#78716c;--color-stone-600:#57534e;--color-stone-700:#44403c;--color-stone-800:#292524;--color-stone-900:#1c1917;--color-stone-950:#0c0a09;--color-red-50:#fef2f2;--color-red-100:#fee2e2;--color-red-200:#fecaca;--color-red-300:#fca5a5;--color-red-400:#f87171;--color-red-500:#ef4444;--color-red-600:#dc2626;--color-red-700:#b91c1c;--color-red-800:#991b1b;--color-red-900:#7f1d1d;--color-red-950:#450a0a;--color-orange-50:#fff7ed;--color-orange-100:#ffedd5;--color-orange-200:#fed7aa;--color-orange-300:#fdba74;--color-orange-400:#fb923c;--color-orange-500:#f97316;--color-orange-600:#ea580c;--color-orange-700:#c2410c;--color-orange-800:#9a3412;--color-orange-900:#7c2d12;--color-orange-950:#431407;--color-amber-50:#fffbeb;--color-amber-100:#fef3c7;--color-amber-200:#fde68a;--color-amber-300:#fcd34d;--color-amber-400:#fbbf24;--color-amber-500:#f59e0b;--color-amber-600:#d97706;--color-amber-700:#b45309;--color-amber-800:#92400e;--color-amber-900:#78350f;--color-amber-950:#451a03;--color-yellow-50:#fefce8;--color-yellow-100:#fef9c3;--color-yellow-200:#fef08a;--color-yellow-300:#fde047;--color-yellow-400:#facc15;--color-yellow-500:#eab308;--color-yellow-600:#ca8a04;--color-yellow-700:#a16207;--color-yellow-800:#854d0e;--color-yellow-900:#713f12;--color-yellow-950:#422006;--color-lime-50:#f7fee7;--color-lime-100:#ecfccb;--color-lime-200:#d9f99d;--color-lime-300:#bef264;--color-lime-400:#a3e635;--color-lime-500:#84cc16;--color-lime-600:#65a30d;--color-lime-700:#4d7c0f;--color-lime-800:#3f6212;--color-lime-900:#365314;--color-lime-950:#1a2e05;--color-green-50:#f0fdf4;--color-green-100:#dcfce7;--color-green-200:#bbf7d0;--color-green-300:#86efac;--color-green-400:#4ade80;--color-green-500:#22c55e;--color-green-600:#16a34a;--color-green-700:#15803d;--color-green-800:#166534;--color-green-900:#14532d;--color-green-950:#052e16;--color-emerald-50:#ecfdf5;--color-emerald-100:#d1fae5;--color-emerald-200:#a7f3d0;--color-emerald-300:#6ee7b7;--color-emerald-400:#34d399;--color-emerald-500:#10b981;--color-emerald-600:#059669;--color-emerald-700:#047857;--color-emerald-800:#065f46;--color-emerald-900:#064e3b;--color-emerald-950:#022c22;--color-teal-50:#f0fdfa;--color-teal-100:#ccfbf1;--color-teal-200:#99f6e4;--color-teal-300:#5eead4;--color-teal-400:#2dd4bf;--color-teal-500:#14b8a6;--color-teal-600:#0d9488;--color-teal-700:#0f766e;--color-teal-800:#115e59;--color-teal-900:#134e4a;--color-teal-950:#042f2e;--color-cyan-50:#ecfeff;--color-cyan-100:#cffafe;--color-cyan-200:#a5f3fc;--color-cyan-300:#67e8f9;--color-cyan-400:#22d3ee;--color-cyan-500:#06b6d4;--color-cyan-600:#0891b2;--color-cyan-700:#0e7490;--color-cyan-800:#155e75;--color-cyan-900:#164e63;--color-cyan-950:#083344;--color-sky-50:#f0f9ff;--color-sky-100:#e0f2fe;--color-sky-200:#bae6fd;--color-sky-300:#7dd3fc;--color-sky-400:#38bdf8;--color-sky-500:#0ea5e9;--color-sky-600:#0284c7;--color-sky-700:#0369a1;--color-sky-800:#075985;--color-sky-900:#0c4a6e;--color-sky-950:#082f49;--color-blue-50:#eff6ff;--color-blue-100:#dbeafe;--color-blue-200:#bfdbfe;--color-blue-300:#93c5fd;--color-blue-400:#60a5fa;--color-blue-500:#3b82f6;--color-blue-600:#2563eb;--color-blue-700:#1d4ed8;--color-blue-800:#1e40af;--color-blue-900:#1e3a8a;--color-blue-950:#172554;--color-indigo-50:#eef2ff;--color-indigo-100:#e0e7ff;--color-indigo-200:#c7d2fe;--color-indigo-300:#a5b4fc;--color-indigo-400:#818cf8;--color-indigo-500:#6366f1;--color-indigo-600:#4f46e5;--color-indigo-700:#4338ca;--color-indigo-800:#3730a3;--color-indigo-900:#312e81;--color-indigo-950:#1e1b4b;--color-violet-50:#f5f3ff;--color-violet-100:#ede9fe;--color-violet-200:#ddd6fe;--color-violet-300:#c4b5fd;--color-violet-400:#a78bfa;--color-violet-500:#8b5cf6;--color-violet-600:#7c3aed;--color-violet-700:#6d28d9;--color-violet-800:#5b21b6;--color-violet-900:#4c1d95;--color-violet-950:#2e1065;--color-purple-50:#faf5ff;--color-purple-100:#f3e8ff;--color-purple-200:#e9d5ff;--color-purple-300:#d8b4fe;--color-purple-400:#c084fc;--color-purple-500:#a855f7;--color-purple-600:#9333ea;--color-purple-700:#7e22ce;--color-purple-800:#6b21a8;--color-purple-900:#581c87;--color-purple-950:#3b0764;--color-fuchsia-50:#fdf4ff;--color-fuchsia-100:#fae8ff;--color-fuchsia-200:#f5d0fe;--color-fuchsia-300:#f0abfc;--color-fuchsia-400:#e879f9;--color-fuchsia-500:#d946ef;--color-fuchsia-600:#c026d3;--color-fuchsia-700:#a21caf;--color-fuchsia-800:#86198f;--color-fuchsia-900:#701a75;--color-fuchsia-950:#4a044e;--color-pink-50:#fdf2f8;--color-pink-100:#fce7f3;--color-pink-200:#fbcfe8;--color-pink-300:#f9a8d4;--color-pink-400:#f472b6;--color-pink-500:#ec4899;--color-pink-600:#db2777;--color-pink-700:#be185d;--color-pink-800:#9d174d;--color-pink-900:#831843;--color-pink-950:#500724;--color-rose-50:#fff1f2;--color-rose-100:#ffe4e6;--color-rose-200:#fecdd3;--color-rose-300:#fda4af;--color-rose-400:#fb7185;--color-rose-500:#f43f5e;--color-rose-600:#e11d48;--color-rose-700:#be123c;--color-rose-800:#9f1239;--color-rose-900:#881337;--color-rose-950:#4c0519;--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur:8px;--blur-sm:4px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--radius:.25rem;--radius-sm:.125rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--shadow-xs:0 1px #0000000d;--shadow-sm:0 1px 2px 0 #0000000d;--shadow-md:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--shadow-xl:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--shadow-2xl:0 25px 50px -12px #00000040;--shadow-inner:inset 0 2px 4px 0 #0000000d;--inset-shadow-xs:inset 0 1px #0000000d;--inset-shadow-sm:inset 0 1px 1px #0000000d;--inset-shadow:inset 0 2px 4px #0000000d;--drop-shadow:0 1px 2px #0000001a,0 1px 1px #0000000f;--drop-shadow-sm:0 1px 1px #0000000d;--drop-shadow-md:0 4px 3px #00000012,0 2px 2px #0000000f;--drop-shadow-lg:0 10px 8px #0000000a,0 4px 3px #0000001a;--drop-shadow-xl:0 20px 13px #00000008,0 8px 5px #00000014;--drop-shadow-2xl:0 25px 25px #00000026;--spacing-px:1px;--spacing-0:0px;--spacing-0_5:.125rem;--spacing-1:.25rem;--spacing-1_5:.375rem;--spacing-2:.5rem;--spacing-2_5:.625rem;--spacing-3:.75rem;--spacing-3_5:.875rem;--spacing-4:1rem;--spacing-5:1.25rem;--spacing-6:1.5rem;--spacing-7:1.75rem;--spacing-8:2rem;--spacing-9:2.25rem;--spacing-10:2.5rem;--spacing-11:2.75rem;--spacing-12:3rem;--spacing-14:3.5rem;--spacing-16:4rem;--spacing-20:5rem;--spacing-24:6rem;--spacing-28:7rem;--spacing-32:8rem;--spacing-36:9rem;--spacing-40:10rem;--spacing-44:11rem;--spacing-48:12rem;--spacing-52:13rem;--spacing-56:14rem;--spacing-60:15rem;--spacing-64:16rem;--spacing-72:18rem;--spacing-80:20rem;--spacing-96:24rem;--width-3xs:16rem;--width-2xs:18rem;--width-xs:20rem;--width-sm:24rem;--width-md:28rem;--width-lg:32rem;--width-xl:36rem;--width-2xl:42rem;--width-3xl:48rem;--width-4xl:56rem;--width-5xl:64rem;--width-6xl:72rem;--width-7xl:80rem;--width-prose:65ch;--font-family-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-serif:ui-serif,Georgia,Cambria,"Times New Roman",Times,serif;--font-family-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--font-size-xs:.75rem;--font-size-xs--line-height:1rem;--font-size-sm:.875rem;--font-size-sm--line-height:1.25rem;--font-size-base:1rem;--font-size-base--line-height:1.5rem;--font-size-lg:1.125rem;--font-size-lg--line-height:1.75rem;--font-size-xl:1.25rem;--font-size-xl--line-height:1.75rem;--font-size-2xl:1.5rem;--font-size-2xl--line-height:2rem;--font-size-3xl:1.875rem;--font-size-3xl--line-height:2.25rem;--font-size-4xl:2.25rem;--font-size-4xl--line-height:2.5rem;--font-size-5xl:3rem;--font-size-5xl--line-height:1;--font-size-6xl:3.75rem;--font-size-6xl--line-height:1;--font-size-7xl:4.5rem;--font-size-7xl--line-height:1;--font-size-8xl:6rem;--font-size-8xl--line-height:1;--font-size-9xl:8rem;--font-size-9xl--line-height:1;--letter-spacing-tighter:-.05em;--letter-spacing-tight:-.025em;--letter-spacing-normal:0em;--letter-spacing-wide:.025em;--letter-spacing-wider:.05em;--letter-spacing-widest:.1em;--line-height-none:1;--line-height-tight:1.25;--line-height-snug:1.375;--line-height-normal:1.5;--line-height-relaxed:1.625;--line-height-loose:2;--line-height-3:.75rem;--line-height-4:1rem;--line-height-5:1.25rem;--line-height-6:1.5rem;--line-height-7:1.75rem;--line-height-8:2rem;--line-height-9:2.25rem;--line-height-10:2.5rem;--perspective-dramatic:100px;--perspective-near:300px;--perspective-normal:500px;--perspective-midrange:800px;--perspective-distant:1200px;--transition-timing-function-linear:linear;--transition-timing-function-in:cubic-bezier(.4,0,1,1);--transition-timing-function-out:cubic-bezier(0,0,.2,1);--transition-timing-function-in-out:cubic-bezier(.4,0,.2,1)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{color:inherit;border-top-width:1px;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;background:0 0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;background:0 0}input:where(:not([type=button],[type=reset],[type=submit])),select,textarea{border:1px solid}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}::-webkit-search-decoration{-webkit-appearance:none}summary{display:list-item}ol,ul,menu{list-style:none}textarea{resize:vertical}::placeholder{opacity:1;color:color-mix(in srgb,currentColor 50%,transparent)}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]{display:none!important}}@layer components;@layer utilities{.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static,.static\/Loader,.static\/Toast,.static\/index,.static\/types{position:static}.top-0{top:var(--spacing-0,0px)}.left-0{left:var(--spacing-0,0px)}.z-10{z-index:10}.my-10{margin-top:var(--spacing-10,2.5rem);margin-bottom:var(--spacing-10,2.5rem)}.mr-2{margin-right:var(--spacing-2,.5rem)}.block{display:block}.flex{display:flex}.hidden{display:none}.inline{display:inline}.h-4{height:var(--spacing-4,1rem)}.w-4{width:var(--spacing-4,1rem)}.w-full{width:100%}.transform{transform:var(--tw-rotate-x)var(--tw-rotate-y)var(--tw-rotate-z)var(--tw-skew-x)var(--tw-skew-y)}.items-center{align-items:center}.justify-center{justify-content:center}.rounded{border-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.py-10{padding-top:var(--spacing-10,2.5rem);padding-bottom:var(--spacing-10,2.5rem)}.text-center{text-align:center}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.text-gray-600{color:var(--color-gray-600,#4b5563)}.\!ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentColor)!important;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)!important}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentColor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,-webkit-backdrop-filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-in-out{transition-timing-function:var(--transition-timing-function-in-out,cubic-bezier(.4,0,.2,1))}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@property --tw-rotate-x{syntax:"<transform-function>";inherits:false;initial-value:rotateX(0)}@property --tw-rotate-y{syntax:"<transform-function>";inherits:false;initial-value:rotateY(0)}@property --tw-rotate-z{syntax:"<transform-function>";inherits:false;initial-value:rotate(0)}@property --tw-skew-x{syntax:"<transform-function>";inherits:false;initial-value:skew(0)}@property --tw-skew-y{syntax:"<transform-function>";inherits:false;initial-value:skewY(0)}@property --tw-border-style{syntax:"<custom-ident>";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-colored{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-colored{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}label.svelte-q9tvhg.svelte-q9tvhg{display:block;width:100%}input.svelte-q9tvhg.svelte-q9tvhg{display:block;position:relative;outline:none!important;box-shadow:var(--input-shadow);background:var(--input-background-fill);padding:var(--input-padding);width:100%;color:var(--body-text-color);font-weight:var(--input-text-weight);font-size:var(--input-text-size);line-height:var(--line-sm);border:none}.container.svelte-q9tvhg>input.svelte-q9tvhg{border:var(--input-border-width) solid var(--input-border-color);border-radius:var(--input-radius)}input.svelte-q9tvhg.svelte-q9tvhg:disabled{-webkit-text-fill-color:var(--body-text-color);-webkit-opacity:1;opacity:1}input.svelte-q9tvhg.svelte-q9tvhg:focus{box-shadow:var(--input-shadow-focus);border-color:var(--input-border-color-focus)}input.svelte-q9tvhg.svelte-q9tvhg::placeholder{color:var(--input-placeholder-color)}button.svelte-q9tvhg.svelte-q9tvhg{display:inline-flex;justify-content:center;align-items:center;transition:var(--button-transition);box-shadow:var(--button-shadow);padding:10px 15px;text-align:center;border-radius:10px;border:#eee solid 1px}button.svelte-q9tvhg.svelte-q9tvhg:hover{box-shadow:var(--button-shadow-hover)}button.svelte-q9tvhg.svelte-q9tvhg:active{box-shadow:var(--button-shadow-active)}
src/backend/gradio_molecule2d/templates/example/index.js ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const {
2
+ SvelteComponent: b,
3
+ add_iframe_resize_listener: y,
4
+ add_render_callback: v,
5
+ append: h,
6
+ attr: m,
7
+ binding_callbacks: w,
8
+ detach: z,
9
+ element: k,
10
+ init: p,
11
+ insert: S,
12
+ noop: f,
13
+ safe_not_equal: q,
14
+ set_data: C,
15
+ text: E,
16
+ toggle_class: r
17
+ } = window.__gradio__svelte__internal, { onMount: M } = window.__gradio__svelte__internal;
18
+ function P(t) {
19
+ let e, n = (
20
+ /*value*/
21
+ (t[0] ? (
22
+ /*value*/
23
+ t[0]
24
+ ) : "") + ""
25
+ ), _, d;
26
+ return {
27
+ c() {
28
+ e = k("div"), _ = E(n), m(e, "class", "svelte-84cxb8"), v(() => (
29
+ /*div_elementresize_handler*/
30
+ t[5].call(e)
31
+ )), r(
32
+ e,
33
+ "table",
34
+ /*type*/
35
+ t[1] === "table"
36
+ ), r(
37
+ e,
38
+ "gallery",
39
+ /*type*/
40
+ t[1] === "gallery"
41
+ ), r(
42
+ e,
43
+ "selected",
44
+ /*selected*/
45
+ t[2]
46
+ );
47
+ },
48
+ m(l, s) {
49
+ S(l, e, s), h(e, _), d = y(
50
+ e,
51
+ /*div_elementresize_handler*/
52
+ t[5].bind(e)
53
+ ), t[6](e);
54
+ },
55
+ p(l, [s]) {
56
+ s & /*value*/
57
+ 1 && n !== (n = /*value*/
58
+ (l[0] ? (
59
+ /*value*/
60
+ l[0]
61
+ ) : "") + "") && C(_, n), s & /*type*/
62
+ 2 && r(
63
+ e,
64
+ "table",
65
+ /*type*/
66
+ l[1] === "table"
67
+ ), s & /*type*/
68
+ 2 && r(
69
+ e,
70
+ "gallery",
71
+ /*type*/
72
+ l[1] === "gallery"
73
+ ), s & /*selected*/
74
+ 4 && r(
75
+ e,
76
+ "selected",
77
+ /*selected*/
78
+ l[2]
79
+ );
80
+ },
81
+ i: f,
82
+ o: f,
83
+ d(l) {
84
+ l && z(e), d(), t[6](null);
85
+ }
86
+ };
87
+ }
88
+ function W(t, e, n) {
89
+ let { value: _ } = e, { type: d } = e, { selected: l = !1 } = e, s, a;
90
+ function c(i, u) {
91
+ !i || !u || (a.style.setProperty("--local-text-width", `${u < 150 ? u : 200}px`), n(4, a.style.whiteSpace = "unset", a));
92
+ }
93
+ M(() => {
94
+ c(a, s);
95
+ });
96
+ function o() {
97
+ s = this.clientWidth, n(3, s);
98
+ }
99
+ function g(i) {
100
+ w[i ? "unshift" : "push"](() => {
101
+ a = i, n(4, a);
102
+ });
103
+ }
104
+ return t.$$set = (i) => {
105
+ "value" in i && n(0, _ = i.value), "type" in i && n(1, d = i.type), "selected" in i && n(2, l = i.selected);
106
+ }, [_, d, l, s, a, o, g];
107
+ }
108
+ class j extends b {
109
+ constructor(e) {
110
+ super(), p(this, e, W, P, q, { value: 0, type: 1, selected: 2 });
111
+ }
112
+ }
113
+ export {
114
+ j as default
115
+ };
src/backend/gradio_molecule2d/templates/example/style.css ADDED
@@ -0,0 +1 @@
 
 
1
+ .gallery.svelte-84cxb8{padding:var(--size-1) var(--size-2)}div.svelte-84cxb8{overflow:hidden;min-width:var(--local-text-width);white-space:nowrap}
src/demo/__init__.py ADDED
File without changes
src/demo/app.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from gradio_molecule2d import molecule2d
4
+
5
+
6
+ example = molecule2d().example_value()
7
+
8
+ demo = gr.Interface(
9
+ lambda x:x,
10
+ molecule2d(), # interactive version of your component
11
+ molecule2d(), # static version of your component
12
+ # examples=[[example]], # uncomment this line to view the "example version" of your component
13
+ )
14
+
15
+
16
+ if __name__ == "__main__":
17
+ demo.launch()
src/demo/css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
src/demo/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_molecule2d
src/demo/space.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from app import demo as app
4
+ import os
5
+
6
+ _docs = {'molecule2d': {'description': 'Creates a very simple textbox for user to enter string input or display string output.', 'members': {'__init__': {'value': {'type': 'str | Callable | None', 'default': 'None', 'description': 'default text to provide in textbox. If callable, the function will be called whenever the app loads to set the initial value of the component.'}, 'placeholder': {'type': 'str | None', 'default': 'None', 'description': 'placeholder hint to provide behind textbox.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'component name in interface.'}, 'every': {'type': 'float | None', 'default': 'None', 'description': "If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute."}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'rtl': {'type': 'bool', 'default': 'False', 'description': 'If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}}, 'postprocess': {'value': {'type': 'str | None', 'description': 'Expects a {str} returned from function and sets textarea value to it.'}}, 'preprocess': {'return': {'type': 'str | None', 'description': 'Passes text value as a {str} into the function.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the molecule2d changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'input': {'type': None, 'default': None, 'description': 'This listener is triggered when the user changes the value of the molecule2d.'}, 'submit': {'type': None, 'default': None, 'description': 'This listener is triggered when the user presses the Enter key while the molecule2d is focused.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'molecule2d': []}}}
7
+
8
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
+
10
+ with gr.Blocks(
11
+ css=abs_path,
12
+ theme=gr.themes.Default(
13
+ font_mono=[
14
+ gr.themes.GoogleFont("Inconsolata"),
15
+ "monospace",
16
+ ],
17
+ ),
18
+ ) as demo:
19
+ gr.Markdown(
20
+ """
21
+ # `gradio_molecule2d`
22
+
23
+ <div style="display: flex; gap: 7px;">
24
+ <a href="https://pypi.org/project/gradio_molecule2d/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_molecule2d"></a>
25
+ </div>
26
+
27
+ Input chemical molecules as smiles strings and visualize them
28
+ """, elem_classes=["md-custom"], header_links=True)
29
+ app.render()
30
+ gr.Markdown(
31
+ """
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install gradio_molecule2d
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+
42
+ import gradio as gr
43
+ from gradio_molecule2d import molecule2d
44
+
45
+
46
+ example = molecule2d().example_value()
47
+
48
+ demo = gr.Interface(
49
+ lambda x:x,
50
+ molecule2d(), # interactive version of your component
51
+ molecule2d(), # static version of your component
52
+ # examples=[[example]], # uncomment this line to view the "example version" of your component
53
+ )
54
+
55
+
56
+ if __name__ == "__main__":
57
+ demo.launch()
58
+
59
+ ```
60
+ """, elem_classes=["md-custom"], header_links=True)
61
+
62
+
63
+ gr.Markdown("""
64
+ ## `molecule2d`
65
+
66
+ ### Initialization
67
+ """, elem_classes=["md-custom"], header_links=True)
68
+
69
+ gr.ParamViewer(value=_docs["molecule2d"]["members"]["__init__"], linkify=[])
70
+
71
+
72
+ gr.Markdown("### Events")
73
+ gr.ParamViewer(value=_docs["molecule2d"]["events"], linkify=['Event'])
74
+
75
+
76
+
77
+
78
+ gr.Markdown("""
79
+
80
+ ### User function
81
+
82
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
83
+
84
+ - When used as an Input, the component only impacts the input signature of the user function.
85
+ - When used as an output, the component only impacts the return signature of the user function.
86
+
87
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
88
+
89
+ - **As input:** Is passed, passes text value as a {str} into the function.
90
+ - **As output:** Should return, expects a {str} returned from function and sets textarea value to it.
91
+
92
+ ```python
93
+ def predict(
94
+ value: str | None
95
+ ) -> str | None:
96
+ return value
97
+ ```
98
+ """, elem_classes=["md-custom", "molecule2d-user-fn"], header_links=True)
99
+
100
+
101
+
102
+
103
+ demo.load(None, js=r"""function() {
104
+ const refs = {};
105
+ const user_fn_refs = {
106
+ molecule2d: [], };
107
+ requestAnimationFrame(() => {
108
+
109
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
110
+ if (refs.length > 0) {
111
+ const el = document.querySelector(`.${key}-user-fn`);
112
+ if (!el) return;
113
+ refs.forEach(ref => {
114
+ el.innerHTML = el.innerHTML.replace(
115
+ new RegExp("\\b"+ref+"\\b", "g"),
116
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
117
+ );
118
+ })
119
+ }
120
+ })
121
+
122
+ Object.entries(refs).forEach(([key, refs]) => {
123
+ if (refs.length > 0) {
124
+ const el = document.querySelector(`.${key}`);
125
+ if (!el) return;
126
+ refs.forEach(ref => {
127
+ el.innerHTML = el.innerHTML.replace(
128
+ new RegExp("\\b"+ref+"\\b", "g"),
129
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
130
+ );
131
+ })
132
+ }
133
+ })
134
+ })
135
+ }
136
+
137
+ """)
138
+
139
+ demo.launch()
src/frontend/ClearButton.svelte ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { IconButton } from "@gradio/atoms";
3
+ import { Clear } from "@gradio/icons";
4
+
5
+ import { createEventDispatcher } from "svelte";
6
+
7
+ export let absolute = true;
8
+
9
+ const dispatch = createEventDispatcher<{
10
+ clear?: never;
11
+ }>();
12
+ </script>
13
+
14
+ <div
15
+ class:not-absolute={!absolute}
16
+ style:position={absolute ? "absolute" : "static"}
17
+ >
18
+ <IconButton
19
+ Icon={Clear}
20
+ label="Finish drawing"
21
+ size="large"
22
+ on:click={(event) => {
23
+ dispatch("clear");
24
+ event.stopPropagation();
25
+ }}
26
+ />
27
+ </div>
28
+
29
+ <style>
30
+ div {
31
+ display: flex;
32
+ top: var(--size-2);
33
+ right: var(--size-2);
34
+ justify-content: flex-end;
35
+ gap: var(--spacing-sm);
36
+ z-index: var(--layer-1);
37
+ }
38
+
39
+ .not-absolute {
40
+ margin: var(--size-1);
41
+ }
42
+ </style>
src/frontend/Editor.svelte ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
2
+ // no good unbloated molecule sketch tool.
3
+ // we load ketcher in frame if user want's to draw
4
+
5
+ // create event dispatcher
6
+ import { createEventDispatcher } from "svelte";
7
+ const dispatch = createEventDispatcher();
8
+
9
+ import ClearButton from "./ClearButton.svelte";
10
+
11
+ function getsmiles() {
12
+ const iframe = document.querySelector("iframe");
13
+ const ketcher = iframe.contentWindow.ketcher;
14
+ ketcher.getSmiles().then((smiles) => {
15
+ dispatch("moleculesketched", {
16
+ smiles: smiles,
17
+ });
18
+ });
19
+ }
20
+
21
+ let ketcherDoc = `<!DOCTYPE html>
22
+ <html>
23
+ <head> </head>
24
+ <body style="background:white; padding-top:4rem; height:100%">
25
+ <div
26
+ id="loading"
27
+ style="display: flex; justify-content: center; align-items: center"
28
+ >
29
+ <p style="padding: 0.2rem 1rem 0 0; color: #c1c1c1; font-size: 1rem">
30
+ loading SMILES editor
31
+ </p>
32
+ <svg
33
+ version="1.1"
34
+ id="L4"
35
+ xmlns="http://www.w3.org/2000/svg"
36
+ xmlns:xlink="http://www.w3.org/1999/xlink"
37
+ x="0px"
38
+ y="0px"
39
+ viewBox="0 0 100 100"
40
+ enable-background="new 0 0 0 0"
41
+ xml:space="preserve"
42
+ width="5rem"
43
+ >
44
+ <circle fill="#FF7C00" stroke="none" cx="6" cy="50" r="6">
45
+ <animate
46
+ attributeName="opacity"
47
+ dur="1s"
48
+ values="0;1;0"
49
+ repeatCount="indefinite"
50
+ begin="0.1"
51
+ />
52
+ </circle>
53
+ <circle fill="#FF7C00" stroke="none" cx="26" cy="50" r="6">
54
+ <animate
55
+ attributeName="opacity"
56
+ dur="1s"
57
+ values="0;1;0"
58
+ repeatCount="indefinite"
59
+ begin="0.2"
60
+ />
61
+ </circle>
62
+ <circle fill="#FF7C00" stroke="none" cx="46" cy="50" r="6">
63
+ <animate
64
+ attributeName="opacity"
65
+ dur="1s"
66
+ values="0;1;0"
67
+ repeatCount="indefinite"
68
+ begin="0.3"
69
+ />
70
+ </circle>
71
+ </svg>
72
+ </div>
73
+ <div id="root"></div>
74
+ <script>
75
+ window.onload = function () {
76
+ var loadingDiv = document.getElementById("loading");
77
+ loadingDiv.style.display = "flex";
78
+ //load css
79
+ let url =
80
+ "https://huggingface.co/datasets/simonduerr/ketcher-2.7.2/raw/main/static/css/main.6a646761.css";
81
+ fetch(url)
82
+ .then((res) => res.text())
83
+ .then((text) => {
84
+ const style = document.createElement("style");
85
+ style.textContent = text;
86
+ document.head.appendChild(style);
87
+ });
88
+ //load ketcher
89
+ url =
90
+ "https://huggingface.co/datasets/simonduerr/ketcher-2.7.2/resolve/main/static/js/main.5445f351.js";
91
+ fetch(url)
92
+ .then((res) => res.text())
93
+ .then((text) => {
94
+ const script = document.createElement("script");
95
+ //script.type = "module"
96
+ script.src = URL.createObjectURL(
97
+ new Blob([text], { type: "application/javascript" })
98
+ );
99
+ document.head.appendChild(script);
100
+ loadingDiv.style.display = "none";
101
+ });
102
+ };
103
+
104
+ </\script>
105
+ </body>
106
+ </html>
107
+ `;
108
+ </script>
109
+
110
+ <ClearButton on:clear={getsmiles} />
111
+
112
+ <div class="composerWrapper">
113
+ <iframe srcdoc={ketcherDoc} />
114
+ </div>
115
+
116
+ <style>
117
+ iframe {
118
+ width: 100%;
119
+ height: 65vh;
120
+ }
121
+ </style>
src/frontend/Example.svelte ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+
4
+ export let value: string | null;
5
+ export let type: "gallery" | "table";
6
+ export let selected = false;
7
+
8
+ let size: number;
9
+ let el: HTMLDivElement;
10
+
11
+ function set_styles(element: HTMLElement, el_width: number): void {
12
+ if (!element || !el_width) return;
13
+ el.style.setProperty(
14
+ "--local-text-width",
15
+ `${el_width < 150 ? el_width : 200}px`
16
+ );
17
+ el.style.whiteSpace = "unset";
18
+ }
19
+
20
+ onMount(() => {
21
+ set_styles(el, size);
22
+ });
23
+ </script>
24
+
25
+ <div
26
+ bind:clientWidth={size}
27
+ bind:this={el}
28
+ class:table={type === "table"}
29
+ class:gallery={type === "gallery"}
30
+ class:selected
31
+ >
32
+ {value ? value : ""}
33
+ </div>
34
+
35
+ <style>
36
+ .gallery {
37
+ padding: var(--size-1) var(--size-2);
38
+ }
39
+
40
+ div {
41
+ overflow: hidden;
42
+ min-width: var(--local-text-width);
43
+
44
+ white-space: nowrap;
45
+ }
46
+ </style>
src/frontend/Index.svelte ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <svelte:options accessors={true} />
2
+
3
+ <script lang="ts">
4
+ import type { Gradio } from "@gradio/utils";
5
+ import { BlockTitle } from "@gradio/atoms";
6
+ import { Block } from "@gradio/atoms";
7
+ import { StatusTracker } from "@gradio/statustracker";
8
+ import type { LoadingStatus } from "@gradio/statustracker";
9
+
10
+ import { tick } from "svelte";
11
+ import Viewer from "./Viewer.svelte";
12
+ import Editor from "./Editor.svelte";
13
+
14
+ import { BaseButton } from "@gradio/button";
15
+
16
+ import { Sketch } from "@gradio/icons";
17
+
18
+ import "./style.css";
19
+
20
+ export let gradio: Gradio<{
21
+ change: never;
22
+ submit: never;
23
+ input: never;
24
+ clear_status: LoadingStatus;
25
+ }>;
26
+ export let label = "Textbox";
27
+ export let elem_id = "";
28
+ export let elem_classes: string[] = [];
29
+ export let visible = true;
30
+ export let value = "";
31
+ export let placeholder = "";
32
+ export let show_label: boolean;
33
+ export let scale: number | null = null;
34
+ export let min_width: number | undefined = undefined;
35
+ export let loading_status: LoadingStatus | undefined = undefined;
36
+ export let value_is_output = false;
37
+ export let interactive: boolean;
38
+ export let rtl = false;
39
+
40
+ let retrieveSmiles;
41
+
42
+ let el: HTMLTextAreaElement | HTMLInputElement;
43
+ const container = true;
44
+
45
+ function handle_change(): void {
46
+ gradio.dispatch("change");
47
+ if (!value_is_output) {
48
+ gradio.dispatch("input");
49
+ }
50
+ }
51
+
52
+ async function handle_keypress(e: KeyboardEvent): Promise<void> {
53
+ await tick();
54
+ if (e.key === "Enter") {
55
+ e.preventDefault();
56
+ gradio.dispatch("submit");
57
+ }
58
+ }
59
+
60
+ $: if (value === null) value = "";
61
+
62
+ // When the value changes, dispatch the change event via handle_change()
63
+ // See the docs for an explanation: https://svelte.dev/docs/svelte-components#script-3-$-marks-a-statement-as-reactive
64
+ $: value, handle_change();
65
+
66
+ let showeditor = false;
67
+
68
+ function handleMolecule(event) {
69
+ value = event.detail.smiles;
70
+ showeditor = false;
71
+ }
72
+
73
+ function showEditor() {
74
+ showeditor = true;
75
+ }
76
+ </script>
77
+
78
+ <Block
79
+ {visible}
80
+ {elem_id}
81
+ {elem_classes}
82
+ {scale}
83
+ {min_width}
84
+ allow_overflow={false}
85
+ padding={true}
86
+ >
87
+ <div style="min-height: 70vh;">
88
+ {#if loading_status}
89
+ <StatusTracker
90
+ autoscroll={gradio.autoscroll}
91
+ i18n={gradio.i18n}
92
+ {...loading_status}
93
+ on:clear_status={() => gradio.dispatch("clear_status", loading_status)}
94
+ />
95
+ {/if}
96
+
97
+ <label class:container>
98
+ <BlockTitle {show_label} info={undefined}>{label}</BlockTitle>
99
+
100
+ <input
101
+ data-testid="textbox"
102
+ type="text"
103
+ class="scroll-hide"
104
+ bind:value
105
+ bind:this={el}
106
+ {placeholder}
107
+ disabled={!interactive}
108
+ dir={rtl ? "rtl" : "ltr"}
109
+ on:keypress={handle_keypress}
110
+ />
111
+ </label>
112
+
113
+ <Viewer smiles={value} />
114
+
115
+ <div class="w-full flex justify-center">
116
+ <button on:click={showEditor} class="flex items-center">
117
+ <div class="w-4 h-4 mr-2"><Sketch /></div>
118
+ <span>Draw molecule</span>
119
+ </button>
120
+ </div>
121
+
122
+ {#if showeditor}
123
+ <div class="absolute top-0 left-0 w-full z-10" style="height: 100%">
124
+ <Editor on:moleculesketched={handleMolecule} />
125
+ </div>
126
+ {/if}
127
+ </div>
128
+ </Block>
129
+
130
+ <style>
131
+ label {
132
+ display: block;
133
+ width: 100%;
134
+ }
135
+
136
+ input {
137
+ display: block;
138
+ position: relative;
139
+ outline: none !important;
140
+ box-shadow: var(--input-shadow);
141
+ background: var(--input-background-fill);
142
+ padding: var(--input-padding);
143
+ width: 100%;
144
+ color: var(--body-text-color);
145
+ font-weight: var(--input-text-weight);
146
+ font-size: var(--input-text-size);
147
+ line-height: var(--line-sm);
148
+ border: none;
149
+ }
150
+ .container > input {
151
+ border: var(--input-border-width) solid var(--input-border-color);
152
+ border-radius: var(--input-radius);
153
+ }
154
+ input:disabled {
155
+ -webkit-text-fill-color: var(--body-text-color);
156
+ -webkit-opacity: 1;
157
+ opacity: 1;
158
+ }
159
+
160
+ input:focus {
161
+ box-shadow: var(--input-shadow-focus);
162
+ border-color: var(--input-border-color-focus);
163
+ }
164
+
165
+ input::placeholder {
166
+ color: var(--input-placeholder-color);
167
+ }
168
+ button {
169
+ display: inline-flex;
170
+ justify-content: center;
171
+ align-items: center;
172
+ transition: var(--button-transition);
173
+ box-shadow: var(--button-shadow);
174
+ padding: 10px 15px;
175
+ text-align: center;
176
+ border-radius: 10px;
177
+ border: #eee solid 1px;
178
+ }
179
+
180
+ button:hover,
181
+ button[disabled],
182
+ a:hover,
183
+ a.disabled {
184
+ box-shadow: var(--button-shadow-hover);
185
+ }
186
+
187
+ button:active,
188
+ a:active {
189
+ box-shadow: var(--button-shadow-active);
190
+ }
191
+
192
+ button[disabled],
193
+ a.disabled {
194
+ opacity: 0.5;
195
+ filter: grayscale(30%);
196
+ cursor: not-allowed;
197
+ }
198
+
199
+ .hidden {
200
+ display: none;
201
+ }
202
+
203
+ .primary {
204
+ border: var(--button-border-width) solid var(--button-primary-border-color);
205
+ background: var(--button-primary-background-fill);
206
+ color: var(--button-primary-text-color);
207
+ }
208
+ .primary:hover,
209
+ .primary[disabled] {
210
+ border-color: var(--button-primary-border-color-hover);
211
+ background: var(--button-primary-background-fill-hover);
212
+ color: var(--button-primary-text-color-hover);
213
+ }
214
+
215
+ .secondary {
216
+ border: var(--button-border-width) solid
217
+ var(--button-secondary-border-color);
218
+ background: var(--button-secondary-background-fill);
219
+ color: var(--button-secondary-text-color);
220
+ }
221
+
222
+ .secondary:hover,
223
+ .secondary[disabled] {
224
+ border-color: var(--button-secondary-border-color-hover);
225
+ background: var(--button-secondary-background-fill-hover);
226
+ color: var(--button-secondary-text-color-hover);
227
+ }
228
+
229
+ .stop {
230
+ border: var(--button-border-width) solid var(--button-cancel-border-color);
231
+ background: var(--button-cancel-background-fill);
232
+ color: var(--button-cancel-text-color);
233
+ }
234
+
235
+ .stop:hover,
236
+ .stop[disabled] {
237
+ border-color: var(--button-cancel-border-color-hover);
238
+ background: var(--button-cancel-background-fill-hover);
239
+ color: var(--button-cancel-text-color-hover);
240
+ }
241
+
242
+ .sm {
243
+ border-radius: var(--button-small-radius);
244
+ padding: var(--button-small-padding);
245
+ font-weight: var(--button-small-text-weight);
246
+ font-size: var(--button-small-text-size);
247
+ }
248
+
249
+ .lg {
250
+ border-radius: var(--button-large-radius);
251
+ padding: var(--button-large-padding);
252
+ font-weight: var(--button-large-text-weight);
253
+ font-size: var(--button-large-text-size);
254
+ }
255
+
256
+ .button-icon {
257
+ width: var(--text-xl);
258
+ height: var(--text-xl);
259
+ margin-right: var(--spacing-xl);
260
+ }
261
+ </style>
src/frontend/Viewer.svelte ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!--file:Molecule.svlete-->
2
+ <!--Tested against v2.1.7 of smiles-drawer-->
3
+ <script>
4
+ import { afterUpdate } from "svelte";
5
+ import SmilesDrawer from "smiles-drawer";
6
+
7
+ export let smiles = "";
8
+
9
+ const SETTINGS = {
10
+ width: 300,
11
+ height: 200,
12
+ };
13
+ let drawer = new SmilesDrawer.SvgDrawer(SETTINGS);
14
+ let svgElement;
15
+
16
+ afterUpdate(() => {
17
+ SmilesDrawer.parse(smiles, function (tree) {
18
+ drawer.draw(tree, svgElement, "light");
19
+ });
20
+ });
21
+ </script>
22
+
23
+ {#if smiles.length > 0}
24
+ <div>
25
+ <svg bind:this={svgElement} data-smiles={smiles} />
26
+ </div>
27
+ {:else}
28
+ <div class="text-center my-10 py-10 text-gray-600">
29
+ No molecule to display
30
+ </div>
31
+ {/if}
32
+
33
+ <style>
34
+ svg {
35
+ width: 100%;
36
+ height: 400px;
37
+ }
38
+
39
+ @import "tailwindcss";
40
+ </style>
src/frontend/gradio.config.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tailwindcss from "@tailwindcss/vite";
2
+ import { mdsvex } from "mdsvex";
3
+
4
+ export default {
5
+ plugins: [tailwindcss()],
6
+ svelte: {
7
+ preprocess: [
8
+ mdsvex()
9
+ ]
10
+ }
11
+ };
src/frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
src/frontend/package.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "gradio_molecule2d",
3
+ "version": "0.2.0",
4
+ "description": "Gradio UI packages",
5
+ "type": "module",
6
+ "author": "",
7
+ "license": "ISC",
8
+ "private": false,
9
+ "main_changeset": true,
10
+ "exports": {
11
+ ".": "./Index.svelte",
12
+ "./example": "./Example.svelte",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "dependencies": {
16
+ "@gradio/atoms": "0.7.1",
17
+ "@gradio/button": "^0.2.32",
18
+ "@gradio/icons": "0.4.0",
19
+ "@gradio/statustracker": "0.5.0",
20
+ "@gradio/upload": "^0.9.0",
21
+ "@gradio/utils": "0.4.0",
22
+ "@tailwindcss/oxide-linux-x64-gnu": "^0.0.0-insiders.8fb9ae8f",
23
+ "@tailwindcss/vite": "^4.0.0-alpha.14",
24
+ "kekule": "^1.0.0",
25
+ "mdsvex": "^0.11.0",
26
+ "smiles-drawer": "^2.1.7",
27
+ "tailwindcss": "^4.0.0-alpha.14"
28
+ },
29
+ "devDependencies": {
30
+ "@gradio/preview": "0.8.3"
31
+ }
32
+ }
src/frontend/style.css ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ @import "tailwindcss";
2
+
src/pyproject.toml ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = [
3
+ "hatchling",
4
+ "hatch-requirements-txt",
5
+ "hatch-fancy-pypi-readme>=22.5.0",
6
+ ]
7
+ build-backend = "hatchling.build"
8
+
9
+ [project]
10
+ name = "gradio_molecule2d"
11
+ version = "0.0.1"
12
+ description = "Input chemical molecules as smiles strings and visualize them"
13
+ readme = "README.md"
14
+ license = "mit"
15
+ requires-python = ">=3.8"
16
+ authors = [{ name = "Simon Dürr", email = "dev@simonduerr.eu" }]
17
+ keywords = ["gradio-custom-component", "gradio-template-SimpleTextbox", "chemistry", "smiles"]
18
+ # Add dependencies here
19
+ dependencies = ["gradio>=4.0,<5.0"]
20
+ classifiers = [
21
+ 'Development Status :: 3 - Alpha',
22
+ 'Operating System :: OS Independent',
23
+ 'Programming Language :: Python :: 3',
24
+ 'Programming Language :: Python :: 3 :: Only',
25
+ 'Programming Language :: Python :: 3.8',
26
+ 'Programming Language :: Python :: 3.9',
27
+ 'Programming Language :: Python :: 3.10',
28
+ 'Programming Language :: Python :: 3.11',
29
+ 'Topic :: Scientific/Engineering',
30
+ 'Topic :: Scientific/Engineering :: Artificial Intelligence',
31
+ 'Topic :: Scientific/Engineering :: Visualization',
32
+ ]
33
+
34
+ # The repository and space URLs are optional, but recommended.
35
+ # Adding a repository URL will create a badge in the auto-generated README that links to the repository.
36
+ # Adding a space URL will create a badge in the auto-generated README that links to the space.
37
+ # This will make it easy for people to find your deployed demo or source code when they
38
+ # encounter your project in the wild.
39
+
40
+ # [project.urls]
41
+ # repository = "your github repository"
42
+ # space = "your space url"
43
+
44
+ [project.optional-dependencies]
45
+ dev = ["build", "twine"]
46
+
47
+ [tool.hatch.build]
48
+ artifacts = ["/backend/gradio_molecule2d/templates", "*.pyi"]
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["/backend/gradio_molecule2d"]