Songyou commited on
Commit
99415de
·
verified ·
1 Parent(s): 573b75c

Upload 8 files

Browse files
Files changed (8) hide show
  1. .babelrc +5 -0
  2. clm-frontend-dev.py +233 -0
  3. clm-frontend.py +181 -0
  4. index.html +33 -0
  5. ketcher_embed.html +72 -0
  6. package-lock.json +0 -0
  7. package.json +28 -0
  8. webpack.config.js +55 -0
.babelrc ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "presets": ["@babel/preset-env"],
3
+ "plugins": ["@babel/plugin-transform-runtime"]
4
+ }
5
+
clm-frontend-dev.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from fastapi import FastAPI
3
+ from starlette.staticfiles import StaticFiles
4
+ import uvicorn
5
+ import logging
6
+ from pydantic import BaseModel
7
+ import pandas as pd
8
+ import time
9
+
10
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class SmilesData(BaseModel):
14
+ smiles: str
15
+
16
+ app = FastAPI()
17
+
18
+ # 将 ketcher 目录挂载为静态资源,确保 ketcher/index.html 存在
19
+ app.mount("/ketcher", StaticFiles(directory="ketcher"), name="ketcher")
20
+
21
+ # 接收前端发送的SMILES数据的接口
22
+ @app.post("/update_smiles")
23
+ async def update_smiles(data: SmilesData):
24
+ logger.info(f"Received SMILES from front-end: {data.smiles}")
25
+ print(f"[PRINT-Backend] Received SMILES from front-end: {data.smiles}")
26
+ return {"status": "ok", "received_smiles": data.smiles}
27
+
28
+ # 前端嵌入的HTML和脚本
29
+ KETCHER_HTML = r'''
30
+ <iframe id="ifKetcher" src="/ketcher/index.html" width="100%" height="600px" style="border: 1px solid #ccc;"></iframe>
31
+
32
+ <script>
33
+ console.log("[Front-end] Ketcher-Gradio integration script loaded.");
34
+
35
+ let ketcher = null;
36
+ let lastSmiles = '';
37
+
38
+ function findSmilesInput() {
39
+ const inputContainer = document.getElementById('combined_smiles_input');
40
+ if (!inputContainer) {
41
+ console.warn("[Front-end] combined_smiles_input element not found.");
42
+ return null;
43
+ }
44
+ const input = inputContainer.querySelector('input[type="text"]');
45
+ return input;
46
+ }
47
+
48
+ function updateGradioInput(smiles) {
49
+ const input = findSmilesInput();
50
+ if (input && input.value !== smiles) {
51
+ input.value = smiles;
52
+ // 手动触发input事件让 Gradio 更新状态
53
+ input.dispatchEvent(new Event('input', { bubbles: true }));
54
+ console.log("[Front-end] Updated Gradio input with SMILES:", smiles);
55
+ }
56
+ }
57
+
58
+ async function handleKetcherChange() {
59
+ console.log("[Front-end] handleKetcherChange called, retrieving SMILES...");
60
+ try {
61
+ const smiles = await ketcher.getSmiles({ arom: false });
62
+ console.log("[Front-end] SMILES retrieved from Ketcher:", smiles);
63
+ if (smiles !== lastSmiles) {
64
+ lastSmiles = smiles;
65
+ updateGradioInput(smiles);
66
+
67
+ // 将SMILES发送给后端 (可以保留,但可能不是核心需求)
68
+ console.log("[Front-end] Sending SMILES to backend...");
69
+ fetch('/update_smiles', {
70
+ method: 'POST',
71
+ headers: {'Content-Type': 'application/json'},
72
+ body: JSON.stringify({smiles: smiles})
73
+ })
74
+ .then(res => res.json())
75
+ .then(data => {
76
+ console.log("[Front-end] Backend response:", data);
77
+ })
78
+ .catch(err => console.error("[Front-end] Error sending SMILES to backend:", err));
79
+ }
80
+ } catch (err) {
81
+ console.error("[Front-end] Error getting SMILES from Ketcher:", err);
82
+ }
83
+ }
84
+
85
+ function initKetcher() {
86
+ console.log("[Front-end] initKetcher started.");
87
+ const iframe = document.getElementById('ifKetcher');
88
+ if (!iframe) {
89
+ console.error("[Front-end] iframe not found.");
90
+ setTimeout(initKetcher, 500);
91
+ return;
92
+ }
93
+
94
+ const ketcherWindow = iframe.contentWindow;
95
+ if (!ketcherWindow || !ketcherWindow.ketcher) {
96
+ console.log("[Front-end] ketcher not yet available in iframe, retrying...");
97
+ setTimeout(initKetcher, 500);
98
+ return;
99
+ }
100
+
101
+ ketcher = ketcherWindow.ketcher;
102
+ console.log("[Front-end] Ketcher instance acquired:", ketcher);
103
+
104
+ // 设置初始分子
105
+ ketcher.setMolecule('C').then(() => {
106
+ console.log("[Front-end] Initial molecule set to 'C'.");
107
+ });
108
+
109
+ const editor = ketcher.editor;
110
+ console.log("[Front-end] Editor object:", editor);
111
+
112
+ // 尝试绑定变化事件
113
+ let eventBound = false;
114
+ if (editor && typeof editor.subscribe === 'function') {
115
+ console.log("[Front-end] Using editor.subscribe('change', ...)");
116
+ editor.subscribe('change', handleKetcherChange);
117
+ eventBound = true;
118
+ }
119
+
120
+ if (!eventBound) {
121
+ console.error("[Front-end] No suitable event binding found. Check Ketcher version and event API.");
122
+ }
123
+ }
124
+
125
+ document.getElementById('ifKetcher').addEventListener('load', () => {
126
+ console.log("[Front-end] iframe loaded. Initializing Ketcher in 1s...");
127
+ setTimeout(initKetcher, 1000);
128
+ });
129
+ </script>
130
+ '''
131
+
132
+ # --- 模拟后端功能 (来自第二个版本) ---
133
+ def smiles_to_structure(smiles):
134
+ """Dummy function"""
135
+ time.sleep(1)
136
+ return f"Structure Generated from: {smiles}"
137
+
138
+ def fragment_molecule(smiles):
139
+ """Dummy function"""
140
+ time.sleep(2)
141
+ return "Fragment1", "Fragment2", "1-2"
142
+
143
+ def generate_analogs(main_cls, minor_cls, number, delta_value):
144
+ """Dummy function"""
145
+ time.sleep(3)
146
+ return [
147
+ {"SMILE": "c1cccc1", "MolWt": 100, "TPSA": 20, "SLogP": 1, "SA": 30, "QED": 0.8},
148
+ {"SMILE": "c1ccccc1", "MolWt": 105, "TPSA": 25, "SLogP": 1.2, "SA": 32, "QED": 0.9},
149
+ ]
150
+
151
+ def update_output_table(data):
152
+ df = pd.DataFrame(data)
153
+ return df
154
+
155
+ # --- Gradio 界面 ---
156
+ def create_combined_interface():
157
+ with gr.Blocks() as demo:
158
+ gr.Markdown("# Fragment Optimization Tools with Ketcher")
159
+
160
+ with gr.Row():
161
+ with gr.Column(scale=2):
162
+ gr.HTML(KETCHER_HTML) # 嵌入 Ketcher
163
+
164
+ with gr.Column(scale=1):
165
+ with gr.Group():
166
+ gr.Markdown("### Input SMILES (From Ketcher)")
167
+ combined_smiles_input = gr.Textbox(
168
+ label="",
169
+ value="C",
170
+ placeholder="SMILES from Ketcher will appear here",
171
+ elem_id="combined_smiles_input"
172
+ )
173
+ with gr.Row():
174
+ # 可以保留一个按钮来触发某些操作,例如手动同步
175
+ get_ketcher_smiles_btn = gr.Button("Get SMILES from Ketcher")
176
+ # 示例:将当前输入框的 SMILES 设置到 Ketcher (需要额外的前端 JavaScript)
177
+ # set_ketcher_smiles_btn = gr.Button("Set SMILES to Ketcher")
178
+ fragment_btn = gr.Button("Fragmentize Molecule")
179
+
180
+ with gr.Group():
181
+ with gr.Row():
182
+ constant_frag_input = gr.Textbox(label="Constant Fragment", placeholder="SMILES of constant fragment")
183
+ variable_frag_input = gr.Textbox(label="Variable Fragment", placeholder="SMILES of variable fragment")
184
+ attach_order_input = gr.Textbox(label="Attachment Order", placeholder="Attachment Order of SMILES")
185
+
186
+ with gr.Group():
187
+ gr.Markdown("### Generate analogs")
188
+ with gr.Row():
189
+ main_cls_dropdown = gr.Dropdown(label="Main Cls", choices=["None", "Cl", "Br"])
190
+ minor_cls_dropdown = gr.Dropdown(label="Minor Cls", choices=["None", "Cl", "Br"])
191
+ number_input = gr.Number(label="Number", value=5, step=1)
192
+
193
+ delta_value_slider = gr.Slider(minimum=0, maximum=10, step=1, label="Delta Value", interactive=True)
194
+ generate_analogs_btn = gr.Button("Generate")
195
+
196
+ with gr.Row():
197
+ with gr.Column():
198
+ selected_columns = gr.CheckboxGroup(["SMILE", "MolWt", "TPSA", "SLogP", "SA", "QED"], value=["SMILE", "MolWt", "TPSA", "SLogP"], label="")
199
+
200
+ output_table = gr.Dataframe(headers=["SMILE", "MolWt", "TPSA", "SLogP", "SA", "QED"])
201
+
202
+ with gr.Row():
203
+ download_all_btn = gr.Button("Download All")
204
+ download_selected_btn = gr.Button("Download Selected")
205
+
206
+ # --- 事件处理 ---
207
+ # 当点击按钮时,手动从 Ketcher 获取 SMILES 并更新输入框
208
+ get_ketcher_smiles_btn.click(
209
+ fn=None,
210
+ inputs=None,
211
+ outputs=combined_smiles_input,
212
+ js="async () => { const iframe = document.getElementById('ifKetcher'); if(iframe && iframe.contentWindow && iframe.contentWindow.ketcher) { const smiles = await iframe.contentWindow.ketcher.getSmiles(); return smiles; } else { console.error('Ketcher not ready'); return ''; } }"
213
+ )
214
+
215
+ fragment_btn.click(fragment_molecule, inputs=combined_smiles_input, outputs=[constant_frag_input, variable_frag_input, attach_order_input])
216
+
217
+ def update_table_with_analogs(main_cls, minor_cls, number, delta_value):
218
+ analogs_data = generate_analogs(main_cls, minor_cls, number, delta_value)
219
+ return update_output_table(analogs_data)
220
+
221
+ generate_analogs_btn.click(update_table_with_analogs,
222
+ inputs=[main_cls_dropdown, minor_cls_dropdown, number_input, delta_value_slider],
223
+ outputs=output_table)
224
+
225
+ # TODO: 添加下载功能的回调
226
+
227
+ return demo
228
+
229
+ combined_demo = create_combined_interface()
230
+ app = gr.mount_gradio_app(app, combined_demo, path="/")
231
+
232
+ if __name__ == "__main__":
233
+ uvicorn.run(app, host="127.0.0.1", port=7860)
clm-frontend.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from fastapi import FastAPI
3
+ from starlette.staticfiles import StaticFiles
4
+ import uvicorn
5
+ import logging
6
+ from pydantic import BaseModel
7
+
8
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class SmilesData(BaseModel):
12
+ smiles: str
13
+
14
+ app = FastAPI()
15
+
16
+ # 将 ketcher 目录挂载为静态资源,确保 ketcher/index.html 存在
17
+ app.mount("/ketcher", StaticFiles(directory="ketcher"), name="ketcher")
18
+
19
+ # 接收前端发送的SMILES数据的接口
20
+ @app.post("/update_smiles")
21
+ async def update_smiles(data: SmilesData):
22
+ logger.info(f"Received SMILES from front-end: {data.smiles}")
23
+ print(f"[PRINT] Received SMILES from front-end: {data.smiles}")
24
+ return {"status": "ok", "received_smiles": data.smiles}
25
+
26
+ # 前端嵌入的HTML和脚本
27
+ # 这个HTML中会有一个iframe加载/ketcher/index.html
28
+ # 并在前端监听Ketcher事件,将SMILES更新到Gradio输入框并发送给后端
29
+ KETCHER_HTML = r'''
30
+ <iframe id="ifKetcher" src="/ketcher/index.html" width="100%" height="600px" style="border: 1px solid #ccc;"></iframe>
31
+
32
+ <script>
33
+ console.log("[Front-end] Ketcher-Gradio integration script loaded.");
34
+
35
+ let ketcher = null;
36
+ let lastSmiles = '';
37
+
38
+ function findSmilesInput() {
39
+ const inputContainer = document.getElementById('smiles_input');
40
+ if (!inputContainer) {
41
+ console.warn("[Front-end] smiles_input element not found.");
42
+ return null;
43
+ }
44
+ const input = inputContainer.querySelector('input[type="text"]');
45
+ return input;
46
+ }
47
+
48
+ function updateGradioInput(smiles) {
49
+ const input = findSmilesInput();
50
+ if (input && input.value !== smiles) {
51
+ input.value = smiles;
52
+ // 手动触发input事件让Gradio更新状态
53
+ input.dispatchEvent(new Event('input', { bubbles: true }));
54
+ console.log("[Front-end] Updated Gradio input with SMILES:", smiles);
55
+ }
56
+ }
57
+
58
+ async function handleKetcherChange() {
59
+ console.log("[Front-end] handleKetcherChange called, retrieving SMILES...");
60
+ try {
61
+ const smiles = await ketcher.getSmiles({ arom: false });
62
+ console.log("[Front-end] SMILES retrieved from Ketcher:", smiles);
63
+ if (smiles !== lastSmiles) {
64
+ lastSmiles = smiles;
65
+ updateGradioInput(smiles);
66
+
67
+ // 将SMILES发送给后端
68
+ console.log("[Front-end] Sending SMILES to backend...");
69
+ fetch('/update_smiles', {
70
+ method: 'POST',
71
+ headers: {'Content-Type': 'application/json'},
72
+ body: JSON.stringify({smiles: smiles})
73
+ })
74
+ .then(res => res.json())
75
+ .then(data => {
76
+ console.log("[Front-end] Backend response:", data);
77
+ })
78
+ .catch(err => console.error("[Front-end] Error sending SMILES to backend:", err));
79
+ }
80
+ } catch (err) {
81
+ console.error("[Front-end] Error getting SMILES from Ketcher:", err);
82
+ }
83
+ }
84
+
85
+ function initKetcher() {
86
+ console.log("[Front-end] initKetcher started.");
87
+ const iframe = document.getElementById('ifKetcher');
88
+ if (!iframe) {
89
+ console.error("[Front-end] iframe not found.");
90
+ setTimeout(initKetcher, 500);
91
+ return;
92
+ }
93
+
94
+ const ketcherWindow = iframe.contentWindow;
95
+ if (!ketcherWindow || !ketcherWindow.ketcher) {
96
+ console.log("[Front-end] ketcher not yet available in iframe, retrying...");
97
+ setTimeout(initKetcher, 500);
98
+ return;
99
+ }
100
+
101
+ ketcher = ketcherWindow.ketcher;
102
+ console.log("[Front-end] Ketcher instance acquired:", ketcher);
103
+
104
+ // 设置初始分子
105
+ ketcher.setMolecule('C').then(() => {
106
+ console.log("[Front-end] Initial molecule set to 'C'.");
107
+ });
108
+
109
+ const editor = ketcher.editor;
110
+ console.log("[Front-end] Editor object:", editor);
111
+
112
+ // 尝试绑定变化事件,根据Ketcher版本,可尝试多种方式
113
+ let eventBound = false;
114
+ if (editor && typeof editor.subscribe === 'function') {
115
+ console.log("[Front-end] Using editor.subscribe('change', ...)");
116
+ editor.subscribe('change', handleKetcherChange);
117
+ eventBound = true;
118
+ }
119
+ // 如果其他版本需要 editor.events.change.subscribe(handleKetcherChange);
120
+ // 或 editor.structChanged.add(handleKetcherChange); 可在此添加尝试。
121
+
122
+ if (!eventBound) {
123
+ console.error("[Front-end] No suitable event binding found. Check Ketcher version and event API.");
124
+ }
125
+ }
126
+
127
+ document.getElementById('ifKetcher').addEventListener('load', () => {
128
+ console.log("[Front-end] iframe loaded. Initializing Ketcher in 1s...");
129
+ setTimeout(initKetcher, 1000);
130
+ });
131
+ </script>
132
+ '''
133
+
134
+ em_js = '''
135
+ setTimeout(() => {
136
+ console.log('inited')
137
+ k = document.getElementById('ifKetcher');
138
+ btn = document.getElementById('btn');
139
+ btn.addEventListener('click', () => {
140
+ k.contentWindow.ketcher.getSmiles().then((v) => {
141
+ document.querySelector('#output_smi textarea').value = v;
142
+ })
143
+ });
144
+ }, 2e3)
145
+ '''
146
+
147
+ def create_interface():
148
+ with gr.Blocks(js=em_js) as demo:
149
+ gr.Markdown("# Ketcher 与 Gradio 整合示例")
150
+ with gr.Row():
151
+ with gr.Column(scale=2):
152
+ # 嵌入前面定义的HTML
153
+ gr.HTML(KETCHER_HTML)
154
+
155
+ with gr.Column(scale=1):
156
+ gr.Markdown("### SMILES 输入框 (自动更新)")
157
+ smiles_input = gr.Textbox(
158
+ label="SMILES",
159
+ value="C",
160
+ placeholder="SMILES structure",
161
+ elem_id="smiles_input"
162
+ )
163
+
164
+ get_smiles_btn = gr.Button("手动获取当前SMILES", elem_id='btn')
165
+ smiles_output = gr.Textbox(label="当前 SMILES(按钮点击获取)", elem_id='output_smi')
166
+
167
+ def get_smiles_from_input():
168
+ logger.info("Button clicked: Retrieved SMILES value from input.")
169
+ print("Button clicked: Current SMILES from input:", smiles_input.value)
170
+ return smiles_input.value
171
+
172
+ get_smiles_btn.click(fn=None, inputs=None, outputs=smiles_output, js="async () => { k = document.getElementById('ifKetcher'); a = await k.contentWindow.ketcher.getSmiles(); return a; }")
173
+
174
+ return demo
175
+
176
+ demo = create_interface()
177
+ app = gr.mount_gradio_app(app, demo, path="/")
178
+
179
+ if __name__ == "__main__":
180
+ # 启动命令:python app.py
181
+ uvicorn.run(app, host="127.0.0.1", port=7860)
index.html ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Ketcher 集成测试</title>
6
+ <style>
7
+ /* 自定义 Ketcher 容器的样式 */
8
+ #ketcher-container {
9
+ width: 800px;
10
+ height: 600px;
11
+ border: 1px solid #ccc;
12
+ margin: 20px auto;
13
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
14
+ border-radius: 8px;
15
+ }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <div id="ketcher-container">
20
+ <iframe id="ifKetcher" src="/ketcher/index.html" width="800" height="600"></iframe>
21
+ </div>
22
+ <script>
23
+ setTimeout(() => {
24
+ var iframe = document.getElementById("ifKetcher");
25
+ // 访问iframe的window对象
26
+ var ketcherWindow = iframe.contentWindow;
27
+ var ketcher = ketcherWindow.ketcher;
28
+ ketcher.setMolecule('c1ccccc1');
29
+ console.log('set mol!')
30
+ }, 2e3)
31
+ </script>
32
+ </body>
33
+ </html>
ketcher_embed.html ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Ketcher Embed</title>
7
+ </head>
8
+ <body>
9
+ <iframe id="ifKetcher" src="/static/index.html" width="800" height="600"></iframe>
10
+ <script>
11
+ setTimeout(() => {
12
+ var iframe = document.getElementById("ifKetcher");
13
+ // 访问iframe的window对象
14
+ var ketcherWindow = iframe.contentWindow;
15
+ var ketcher = ketcherWindow.ketcher;
16
+ ketcher.setMolecule('c1ccccc1');
17
+ console.log('set mol!');
18
+ // 监听 Ketcher 的 'change' 事件,当分子结构改变时触发
19
+ ketcher.on('change', () => {
20
+ ketcher.getSmiles().then(smiles => {
21
+ console.log("生成的新 SMILES:", smiles);
22
+ sendDataToBackend(smiles);
23
+ }).catch(err => {
24
+ console.error('获取 SMILES 时出错:', err);
25
+ });
26
+ });
27
+
28
+ // 监听 Ketcher 的 'load' 事件,当分子结构加载时触发
29
+ ketcher.on('load', () => {
30
+ ketcher.getSmiles().then(smiles => {
31
+ console.log("加载的 SMILES:", smiles);
32
+ sendDataToBackend(smiles);
33
+ }).catch(err => {
34
+ console.error('获取 SMILES 时出错:', err);
35
+ });
36
+ });
37
+ /**
38
+ * 发送 SMILES 数据到 Gradio 后端的函数
39
+ * @param {string} smiles - 分子的 SMILES 字符串
40
+ */
41
+ function sendDataToBackend(smiles) {
42
+ gradioApp().then((app) => {
43
+ app.call('process_smiles', smiles).then((response) => {
44
+ console.log("Backend response:", response);
45
+ // 可以在这里处理后端返回的响应,例如更新某个组件
46
+ }).catch(error => {
47
+ console.error("调用后端函数时出错:", error);
48
+ });
49
+ }).catch(error => {
50
+ console.error("获取 Gradio 应用时出错:", error);
51
+ });
52
+ }
53
+ /**
54
+ * 从 Gradio 前端加载 SMILES 到 Ketcher
55
+ * @param {string} smiles - 分子的 SMILES 字符串
56
+ */
57
+ function loadSmiles(smiles){
58
+ if (ketcher){
59
+ console.log("加载 SMILES 到 Ketcher:", smiles);
60
+ ketcher.setMolecule(smiles).then(() => {
61
+ console.log("SMILES 加载成功");
62
+ }).catch(err => {
63
+ console.error("加载 SMILES 时出错:", err);
64
+ });
65
+ }
66
+ }
67
+
68
+ window.loadSmiles = loadSmiles;
69
+ }, 2e3)
70
+ </script>
71
+ </body>
72
+ </html>
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dependencies": {
3
+ "@babel/runtime": "^7.26.0",
4
+ "ketcher-standalone": "^2.26.0"
5
+ },
6
+ "name": "2024-12-14-gradio-clm",
7
+ "version": "1.0.0",
8
+ "main": "index.js",
9
+ "devDependencies": {
10
+ "@babel/core": "^7.26.0",
11
+ "@babel/plugin-transform-runtime": "^7.25.9",
12
+ "@babel/preset-env": "^7.26.0",
13
+ "babel-loader": "^9.2.1",
14
+ "buffer": "^6.0.3",
15
+ "file-loader": "^6.2.0",
16
+ "path-browserify": "^1.0.1",
17
+ "process": "^0.11.10",
18
+ "webpack": "^5.97.1",
19
+ "webpack-cli": "^5.1.4"
20
+ },
21
+ "scripts": {
22
+ "test": "echo \"Error: no test specified\" && exit 1"
23
+ },
24
+ "keywords": [],
25
+ "author": "",
26
+ "license": "ISC",
27
+ "description": ""
28
+ }
webpack.config.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const path = require('path');
2
+ const webpack = require('webpack');
3
+
4
+ module.exports = {
5
+ mode: 'development', // 或 'production'
6
+ entry: './src/index.js', // 入口文件
7
+ output: {
8
+ path: path.resolve(__dirname, 'dist'), // 输出目录
9
+ filename: 'bundle.js',
10
+ library: 'KetcherBundle',
11
+ libraryTarget: 'umd',
12
+ globalObject: 'this',
13
+ },
14
+ module: {
15
+ rules: [
16
+ {
17
+ test: /\.js$/, // 处理所有 .js 文件
18
+ exclude: /node_modules/, // 排除 node_modules 目录
19
+ use: {
20
+ loader: 'babel-loader',
21
+ options: {
22
+ presets: ['@babel/preset-env'], // 使用 env 预设
23
+ plugins: ['@babel/plugin-transform-runtime'], // 使用 transform-runtime 插件
24
+ },
25
+ },
26
+ },
27
+ {
28
+ test: /\.wasm$/, // 处理 .wasm 文件
29
+ type: 'javascript/auto', // 需要设置为 'javascript/auto' 以正确加载 WASM 文件
30
+ loader: 'file-loader',
31
+ options: {
32
+ name: '[name].[hash].wasm',
33
+ outputPath: 'binaryWasm/', // 输出到 binaryWasm/ 目录
34
+ },
35
+ },
36
+ ],
37
+ },
38
+ resolve: {
39
+ fallback: {
40
+ path: require.resolve('path-browserify'), // Polyfill for 'path' module
41
+ buffer: require.resolve('buffer/'), // Polyfill for 'buffer' module
42
+ process: require.resolve('process/browser'), // Polyfill for 'process' module
43
+ fs: false, // 不提供 'fs' 模块的 polyfill
44
+ },
45
+ },
46
+ plugins: [
47
+ new webpack.ProvidePlugin({
48
+ Buffer: ['buffer', 'Buffer'], // 自动提供 Buffer
49
+ process: 'process/browser', // 自动提供 process
50
+ }),
51
+ ],
52
+ experiments: {
53
+ asyncWebAssembly: true, // 启用异步 WASM
54
+ },
55
+ };