ramiiiiiiiiiiiiiiiiiiiiiiiiiiiiii commited on
Commit
cf1ba50
·
1 Parent(s): a9bbc87
Files changed (45) hide show
  1. old_ui/__init__.py +15 -0
  2. old_ui/app.py +61 -0
  3. old_ui/components.py +481 -0
  4. old_ui/config.py +583 -0
  5. old_ui/pages/__init__.py +5 -0
  6. old_ui/pages/__pycache__/__init__.cpython-312.pyc +0 -0
  7. old_ui/pages/__pycache__/data.cpython-312.pyc +0 -0
  8. old_ui/pages/__pycache__/discovery.cpython-312.pyc +0 -0
  9. old_ui/pages/__pycache__/explorer.cpython-312.pyc +0 -0
  10. old_ui/pages/__pycache__/home.cpython-312.pyc +0 -0
  11. old_ui/pages/__pycache__/settings.cpython-312.pyc +0 -0
  12. old_ui/pages/data.py +163 -0
  13. old_ui/pages/discovery.py +165 -0
  14. old_ui/pages/explorer.py +127 -0
  15. old_ui/pages/home.py +213 -0
  16. old_ui/pages/settings.py +192 -0
  17. old_ui/requirements.txt +31 -0
  18. ui/.vscode/mcp.json +11 -0
  19. ui/.vscode/tasks.json +44 -0
  20. ui/README.md +41 -13
  21. ui/app/api/discovery/route.ts +19 -0
  22. ui/app/data/page.tsx +116 -0
  23. ui/app/discovery/page.tsx +182 -0
  24. ui/app/explorer/page.tsx +213 -0
  25. ui/app/globals.css +48 -48
  26. ui/app/layout.tsx +10 -4
  27. ui/app/page.tsx +161 -57
  28. ui/app/settings/page.tsx +192 -0
  29. ui/components/page-header.tsx +37 -0
  30. ui/components/sidebar.tsx +95 -0
  31. ui/components/ui/badge.tsx +37 -0
  32. ui/components/ui/button.tsx +58 -0
  33. ui/components/ui/card.tsx +77 -0
  34. ui/components/ui/input.tsx +21 -0
  35. ui/components/ui/label.tsx +24 -0
  36. ui/components/ui/select.tsx +190 -0
  37. ui/components/ui/separator.tsx +28 -0
  38. ui/components/ui/slider.tsx +63 -0
  39. ui/components/ui/switch.tsx +35 -0
  40. ui/components/ui/table.tsx +116 -0
  41. ui/components/ui/tabs.tsx +91 -0
  42. ui/components/ui/textarea.tsx +25 -0
  43. ui/package.json +23 -12
  44. ui/pnpm-lock.yaml +0 -0
  45. ui/pnpm-workspace.yaml +0 -3
old_ui/__init__.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BioFlow UI Package
3
+ ===================
4
+
5
+ Modern Streamlit-based interface for the BioFlow platform.
6
+
7
+ Pages:
8
+ - Home: Dashboard with key metrics and quick actions
9
+ - Discovery: Drug discovery pipeline interface
10
+ - Explorer: Vector space visualization
11
+ - Data: Data ingestion and management
12
+ - Settings: Configuration and preferences
13
+ """
14
+
15
+ __version__ = "2.0.0"
old_ui/app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BioFlow - AI-Powered Drug Discovery Platform
3
+ ==============================================
4
+ Main application entry point.
5
+ """
6
+
7
+ import streamlit as st
8
+ import sys
9
+ import os
10
+
11
+ # Setup path for imports
12
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
13
+
14
+ from bioflow.ui.config import get_css
15
+ from bioflow.ui.components import side_nav
16
+ from bioflow.ui.pages import home, discovery, explorer, data, settings
17
+
18
+
19
+ def main():
20
+ """Main application."""
21
+
22
+ # Page config
23
+ st.set_page_config(
24
+ page_title="BioFlow",
25
+ page_icon="🧬",
26
+ layout="wide",
27
+ initial_sidebar_state="collapsed",
28
+ )
29
+
30
+ # Inject custom CSS
31
+ st.markdown(get_css(), unsafe_allow_html=True)
32
+
33
+ # Initialize session state
34
+ if "current_page" not in st.session_state:
35
+ st.session_state.current_page = "home"
36
+
37
+ # Layout with left navigation
38
+ nav_col, content_col = st.columns([1, 3.6], gap="large")
39
+
40
+ with nav_col:
41
+ selected = side_nav(active_page=st.session_state.current_page)
42
+
43
+ if selected != st.session_state.current_page:
44
+ st.session_state.current_page = selected
45
+ st.rerun()
46
+
47
+ with content_col:
48
+ page_map = {
49
+ "home": home.render,
50
+ "discovery": discovery.render,
51
+ "explorer": explorer.render,
52
+ "data": data.render,
53
+ "settings": settings.render,
54
+ }
55
+
56
+ render_fn = page_map.get(st.session_state.current_page, home.render)
57
+ render_fn()
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()
old_ui/components.py ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BioFlow UI - Components Library
3
+ ================================
4
+ Reusable, modern UI components for Streamlit.
5
+ """
6
+
7
+ import streamlit as st
8
+ from typing import List, Dict, Any, Optional, Callable
9
+ import plotly.express as px
10
+ import plotly.graph_objects as go
11
+
12
+ # Import colors
13
+ import sys
14
+ import os
15
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
16
+ from bioflow.ui.config import COLORS
17
+
18
+
19
+ # === Navigation ===
20
+
21
+ def side_nav(active_page: str = "home") -> str:
22
+ """Left vertical navigation list. Returns the selected page key."""
23
+
24
+ nav_items = [
25
+ ("home", "🏠", "Home"),
26
+ ("discovery", "🔬", "Discovery"),
27
+ ("explorer", "🧬", "Explorer"),
28
+ ("data", "📊", "Data"),
29
+ ("settings", "⚙️", "Settings"),
30
+ ]
31
+
32
+ st.markdown(
33
+ f"""
34
+ <div class="nav-rail">
35
+ <div class="nav-brand">
36
+ <div class="nav-logo">🧬</div>
37
+ <div class="nav-title">Bio<span>Flow</span></div>
38
+ </div>
39
+ <div class="nav-section">Navigation</div>
40
+ </div>
41
+ """,
42
+ unsafe_allow_html=True,
43
+ )
44
+
45
+ label_map = {key: f"{icon} {label}" for key, icon, label in nav_items}
46
+ options = [item[0] for item in nav_items]
47
+
48
+ selected = st.radio(
49
+ "Navigation",
50
+ options=options,
51
+ index=options.index(active_page),
52
+ format_func=lambda x: label_map.get(x, x),
53
+ key="nav_radio",
54
+ label_visibility="collapsed",
55
+ )
56
+
57
+ return selected
58
+
59
+
60
+ # === Page Structure ===
61
+
62
+ def page_header(title: str, subtitle: str = "", icon: str = ""):
63
+ """Page header with title and optional subtitle."""
64
+ header_html = f"""
65
+ <div style="margin-bottom: 2rem;">
66
+ <h1 style="display: flex; align-items: center; gap: 0.75rem; margin: 0;">
67
+ {f'<span style="font-size: 2rem;">{icon}</span>' if icon else ''}
68
+ {title}
69
+ </h1>
70
+ {f'<p style="margin-top: 0.5rem; font-size: 1rem; color: {COLORS.text_muted};">{subtitle}</p>' if subtitle else ''}
71
+ </div>
72
+ """
73
+ st.markdown(header_html, unsafe_allow_html=True)
74
+
75
+
76
+ def section_header(title: str, icon: str = "", link_text: str = "", link_action: Optional[Callable] = None):
77
+ """Section header with optional action link."""
78
+ col1, col2 = st.columns([4, 1])
79
+
80
+ with col1:
81
+ st.markdown(f"""
82
+ <div class="section-title">
83
+ {f'<span>{icon}</span>' if icon else ''}
84
+ {title}
85
+ </div>
86
+ """, unsafe_allow_html=True)
87
+
88
+ with col2:
89
+ if link_text:
90
+ if st.button(link_text, key=f"section_{title}", use_container_width=True):
91
+ if link_action:
92
+ link_action()
93
+
94
+
95
+ def divider():
96
+ """Visual divider."""
97
+ st.markdown('<div class="divider"></div>', unsafe_allow_html=True)
98
+
99
+
100
+ def spacer(height: str = "1rem"):
101
+ """Vertical spacer."""
102
+ st.markdown(f'<div style="height: {height};"></div>', unsafe_allow_html=True)
103
+
104
+
105
+ # === Metrics ===
106
+
107
+ def metric_card(
108
+ value: str,
109
+ label: str,
110
+ icon: str = "📊",
111
+ change: Optional[str] = None,
112
+ change_type: str = "up",
113
+ color: str = COLORS.primary
114
+ ):
115
+ """Single metric card with icon and optional trend."""
116
+ bg_color = color.replace(")", ", 0.15)").replace("rgb", "rgba") if "rgb" in color else f"{color}22"
117
+ change_html = ""
118
+ if change:
119
+ arrow = "↑" if change_type == "up" else "↓"
120
+ change_html = f'<div class="metric-change {change_type}">{arrow} {change}</div>'
121
+
122
+ st.markdown(f"""
123
+ <div class="metric">
124
+ <div class="metric-icon" style="background: {bg_color}; color: {color};">
125
+ {icon}
126
+ </div>
127
+ <div class="metric-value">{value}</div>
128
+ <div class="metric-label">{label}</div>
129
+ {change_html}
130
+ </div>
131
+ """, unsafe_allow_html=True)
132
+
133
+
134
+ def metric_row(metrics: List[Dict[str, Any]]):
135
+ """Row of metric cards."""
136
+ cols = st.columns(len(metrics))
137
+ for col, metric in zip(cols, metrics):
138
+ with col:
139
+ metric_card(**metric)
140
+
141
+
142
+ # === Quick Actions ===
143
+
144
+ def quick_action(icon: str, title: str, description: str, key: str) -> bool:
145
+ """Single quick action card. Returns True if clicked."""
146
+ clicked = st.button(
147
+ f"{icon} {title}",
148
+ key=key,
149
+ use_container_width=True,
150
+ help=description
151
+ )
152
+ return clicked
153
+
154
+
155
+ def quick_actions_grid(actions: List[Dict[str, Any]], columns: int = 4) -> Optional[str]:
156
+ """Grid of quick action cards. Returns clicked action key or None."""
157
+ cols = st.columns(columns)
158
+ clicked_key = None
159
+
160
+ for i, action in enumerate(actions):
161
+ with cols[i % columns]:
162
+ st.markdown(f"""
163
+ <div class="quick-action">
164
+ <span class="quick-action-icon">{action['icon']}</span>
165
+ <div class="quick-action-title">{action['title']}</div>
166
+ <div class="quick-action-desc">{action.get('description', '')}</div>
167
+ </div>
168
+ """, unsafe_allow_html=True)
169
+
170
+ if st.button("Select", key=action['key'], use_container_width=True):
171
+ clicked_key = action['key']
172
+
173
+ return clicked_key
174
+
175
+
176
+ # === Pipeline Progress ===
177
+
178
+ def pipeline_progress(steps: List[Dict[str, Any]]):
179
+ """Visual pipeline with steps showing progress."""
180
+ html = '<div class="pipeline">'
181
+
182
+ for i, step in enumerate(steps):
183
+ status = step.get('status', 'pending')
184
+ icon = step.get('icon', str(i + 1))
185
+ name = step.get('name', f'Step {i + 1}')
186
+
187
+ # Display icon for completed steps
188
+ if status == 'done':
189
+ display = '✓'
190
+ elif status == 'active':
191
+ display = icon
192
+ else:
193
+ display = str(i + 1)
194
+
195
+ html += f'''
196
+ <div class="step">
197
+ <div class="step-dot {status}">{display}</div>
198
+ <span class="step-name">{name}</span>
199
+ </div>
200
+ '''
201
+
202
+ # Add connecting line (except after last step)
203
+ if i < len(steps) - 1:
204
+ line_status = 'done' if status == 'done' else ''
205
+ html += f'<div class="step-line {line_status}"></div>'
206
+
207
+ html += '</div>'
208
+ st.markdown(html, unsafe_allow_html=True)
209
+
210
+
211
+ # === Results ===
212
+
213
+ def result_card(
214
+ title: str,
215
+ score: float,
216
+ properties: Dict[str, str] = None,
217
+ badges: List[str] = None,
218
+ key: str = ""
219
+ ) -> bool:
220
+ """Result card with score and properties. Returns True if clicked."""
221
+
222
+ # Score color
223
+ if score >= 0.8:
224
+ score_class = "score-high"
225
+ elif score >= 0.5:
226
+ score_class = "score-med"
227
+ else:
228
+ score_class = "score-low"
229
+
230
+ # Properties HTML
231
+ props_html = ""
232
+ if properties:
233
+ props_html = '<div style="display: flex; gap: 1rem; margin-top: 0.75rem; flex-wrap: wrap;">'
234
+ for k, v in properties.items():
235
+ props_html += f'''
236
+ <div style="font-size: 0.8125rem;">
237
+ <span style="color: {COLORS.text_muted};">{k}:</span>
238
+ <span style="color: {COLORS.text_secondary}; margin-left: 0.25rem;">{v}</span>
239
+ </div>
240
+ '''
241
+ props_html += '</div>'
242
+
243
+ # Badges HTML
244
+ badges_html = ""
245
+ if badges:
246
+ badges_html = '<div style="display: flex; gap: 0.5rem; margin-top: 0.75rem;">'
247
+ for b in badges:
248
+ badges_html += f'<span class="badge badge-primary">{b}</span>'
249
+ badges_html += '</div>'
250
+
251
+ st.markdown(f"""
252
+ <div class="result">
253
+ <div style="display: flex; justify-content: space-between; align-items: flex-start;">
254
+ <div style="font-weight: 600; color: {COLORS.text_primary};">{title}</div>
255
+ <div class="{score_class}" style="font-size: 1.25rem; font-weight: 700;">{score:.1%}</div>
256
+ </div>
257
+ {props_html}
258
+ {badges_html}
259
+ </div>
260
+ """, unsafe_allow_html=True)
261
+
262
+ return st.button("View Details", key=key, use_container_width=True) if key else False
263
+
264
+
265
+ def results_list(results: List[Dict[str, Any]], empty_message: str = "No results found"):
266
+ """List of result cards."""
267
+ if not results:
268
+ empty_state(icon="🔍", title="No Results", description=empty_message)
269
+ return
270
+
271
+ for i, result in enumerate(results):
272
+ result_card(
273
+ title=result.get('title', f'Result {i + 1}'),
274
+ score=result.get('score', 0),
275
+ properties=result.get('properties'),
276
+ badges=result.get('badges'),
277
+ key=f"result_{i}"
278
+ )
279
+ spacer("0.75rem")
280
+
281
+
282
+ # === Charts ===
283
+
284
+ def bar_chart(data: Dict[str, float], title: str = "", height: int = 300):
285
+ """Styled bar chart."""
286
+ fig = go.Figure(data=[
287
+ go.Bar(
288
+ x=list(data.keys()),
289
+ y=list(data.values()),
290
+ marker_color=COLORS.primary,
291
+ marker_line_width=0,
292
+ )
293
+ ])
294
+
295
+ fig.update_layout(
296
+ title=title,
297
+ paper_bgcolor='rgba(0,0,0,0)',
298
+ plot_bgcolor='rgba(0,0,0,0)',
299
+ font=dict(family="Inter", color=COLORS.text_secondary),
300
+ height=height,
301
+ margin=dict(l=40, r=20, t=40, b=40),
302
+ xaxis=dict(
303
+ showgrid=False,
304
+ showline=True,
305
+ linecolor=COLORS.border,
306
+ ),
307
+ yaxis=dict(
308
+ showgrid=True,
309
+ gridcolor=COLORS.border,
310
+ showline=False,
311
+ ),
312
+ )
313
+
314
+ st.plotly_chart(fig, use_container_width=True)
315
+
316
+
317
+ def scatter_chart(x: List, y: List, labels: List = None, title: str = "", height: int = 400):
318
+ """Styled scatter plot."""
319
+ fig = go.Figure(data=[
320
+ go.Scatter(
321
+ x=x,
322
+ y=y,
323
+ mode='markers',
324
+ marker=dict(
325
+ size=10,
326
+ color=COLORS.primary,
327
+ opacity=0.7,
328
+ ),
329
+ text=labels,
330
+ hovertemplate='<b>%{text}</b><br>X: %{x}<br>Y: %{y}<extra></extra>' if labels else None,
331
+ )
332
+ ])
333
+
334
+ fig.update_layout(
335
+ title=title,
336
+ paper_bgcolor='rgba(0,0,0,0)',
337
+ plot_bgcolor='rgba(0,0,0,0)',
338
+ font=dict(family="Inter", color=COLORS.text_secondary),
339
+ height=height,
340
+ margin=dict(l=40, r=20, t=40, b=40),
341
+ xaxis=dict(
342
+ showgrid=True,
343
+ gridcolor=COLORS.border,
344
+ showline=True,
345
+ linecolor=COLORS.border,
346
+ ),
347
+ yaxis=dict(
348
+ showgrid=True,
349
+ gridcolor=COLORS.border,
350
+ showline=True,
351
+ linecolor=COLORS.border,
352
+ ),
353
+ )
354
+
355
+ st.plotly_chart(fig, use_container_width=True)
356
+
357
+
358
+ def heatmap(data: List[List[float]], x_labels: List[str], y_labels: List[str], title: str = "", height: int = 400):
359
+ """Styled heatmap."""
360
+ fig = go.Figure(data=[
361
+ go.Heatmap(
362
+ z=data,
363
+ x=x_labels,
364
+ y=y_labels,
365
+ colorscale=[
366
+ [0, COLORS.bg_hover],
367
+ [0.5, COLORS.primary],
368
+ [1, COLORS.cyan],
369
+ ],
370
+ )
371
+ ])
372
+
373
+ fig.update_layout(
374
+ title=title,
375
+ paper_bgcolor='rgba(0,0,0,0)',
376
+ plot_bgcolor='rgba(0,0,0,0)',
377
+ font=dict(family="Inter", color=COLORS.text_secondary),
378
+ height=height,
379
+ margin=dict(l=80, r=20, t=40, b=60),
380
+ )
381
+
382
+ st.plotly_chart(fig, use_container_width=True)
383
+
384
+
385
+ # === Data Display ===
386
+
387
+ def data_table(data: List[Dict], columns: List[str] = None):
388
+ """Styled data table."""
389
+ import pandas as pd
390
+ df = pd.DataFrame(data)
391
+ if columns:
392
+ df = df[columns]
393
+ st.dataframe(df, use_container_width=True, hide_index=True)
394
+
395
+
396
+ # === States ===
397
+
398
+ def empty_state(icon: str = "📭", title: str = "No Data", description: str = ""):
399
+ """Empty state placeholder."""
400
+ st.markdown(f"""
401
+ <div class="empty">
402
+ <div class="empty-icon">{icon}</div>
403
+ <div class="empty-title">{title}</div>
404
+ <div class="empty-desc">{description}</div>
405
+ </div>
406
+ """, unsafe_allow_html=True)
407
+
408
+
409
+ def loading_state(message: str = "Loading..."):
410
+ """Loading state with spinner."""
411
+ st.markdown(f"""
412
+ <div class="loading">
413
+ <div class="spinner"></div>
414
+ <div class="loading-text">{message}</div>
415
+ </div>
416
+ """, unsafe_allow_html=True)
417
+
418
+
419
+ # === Molecule Display ===
420
+
421
+ def molecule_2d(smiles: str, size: int = 200):
422
+ """Display 2D molecule structure from SMILES."""
423
+ try:
424
+ from rdkit import Chem
425
+ from rdkit.Chem import Draw
426
+ import base64
427
+ from io import BytesIO
428
+
429
+ mol = Chem.MolFromSmiles(smiles)
430
+ if mol:
431
+ img = Draw.MolToImage(mol, size=(size, size))
432
+ buffered = BytesIO()
433
+ img.save(buffered, format="PNG")
434
+ img_str = base64.b64encode(buffered.getvalue()).decode()
435
+
436
+ st.markdown(f"""
437
+ <div class="mol-container">
438
+ <img src="data:image/png;base64,{img_str}" alt="Molecule" style="max-width: 100%; height: auto;">
439
+ </div>
440
+ """, unsafe_allow_html=True)
441
+ else:
442
+ st.warning("Invalid SMILES")
443
+ except ImportError:
444
+ st.info(f"SMILES: `{smiles}`")
445
+
446
+
447
+ # === Evidence & Links ===
448
+
449
+ def evidence_row(items: List[Dict[str, str]]):
450
+ """Row of evidence/source links."""
451
+ html = '<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.75rem;">'
452
+ for item in items:
453
+ icon = item.get('icon', '📄')
454
+ label = item.get('label', 'Source')
455
+ url = item.get('url', '#')
456
+ html += f'''
457
+ <a href="{url}" target="_blank" class="evidence">
458
+ <span>{icon}</span>
459
+ <span>{label}</span>
460
+ </a>
461
+ '''
462
+ html += '</div>'
463
+ st.markdown(html, unsafe_allow_html=True)
464
+
465
+
466
+ # === Badges ===
467
+
468
+ def badge(text: str, variant: str = "primary"):
469
+ """Inline badge component."""
470
+ st.markdown(f'<span class="badge badge-{variant}">{text}</span>', unsafe_allow_html=True)
471
+
472
+
473
+ def badge_row(badges: List[Dict[str, str]]):
474
+ """Row of badges."""
475
+ html = '<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">'
476
+ for b in badges:
477
+ text = b.get('text', '')
478
+ variant = b.get('variant', 'primary')
479
+ html += f'<span class="badge badge-{variant}">{text}</span>'
480
+ html += '</div>'
481
+ st.markdown(html, unsafe_allow_html=True)
old_ui/config.py ADDED
@@ -0,0 +1,583 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BioFlow UI - Modern Design System
3
+ ==================================
4
+ Clean, minimal, and highly usable interface.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+
9
+
10
+ @dataclass
11
+ class Colors:
12
+ """Color palette - Modern dark theme."""
13
+ # Primary
14
+ primary: str = "#8B5CF6"
15
+ primary_hover: str = "#A78BFA"
16
+ primary_muted: str = "rgba(139, 92, 246, 0.15)"
17
+
18
+ # Accents
19
+ cyan: str = "#22D3EE"
20
+ emerald: str = "#34D399"
21
+ amber: str = "#FBBF24"
22
+ rose: str = "#FB7185"
23
+
24
+ # Backgrounds
25
+ bg_app: str = "#0C0E14"
26
+ bg_surface: str = "#14161E"
27
+ bg_elevated: str = "#1C1F2B"
28
+ bg_hover: str = "#252836"
29
+
30
+ # Text
31
+ text_primary: str = "#F8FAFC"
32
+ text_secondary: str = "#A1A7BB"
33
+ text_muted: str = "#6B7280"
34
+
35
+ # Borders
36
+ border: str = "#2A2D3A"
37
+ border_hover: str = "#3F4354"
38
+
39
+ # Status
40
+ success: str = "#10B981"
41
+ warning: str = "#F59E0B"
42
+ error: str = "#EF4444"
43
+ info: str = "#3B82F6"
44
+
45
+
46
+ COLORS = Colors()
47
+
48
+
49
+ def get_css() -> str:
50
+ """Minimalist, professional CSS using string concatenation to avoid f-string issues."""
51
+
52
+ css = """
53
+ <style>
54
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
55
+
56
+ :root {
57
+ --primary: """ + COLORS.primary + """;
58
+ --bg-app: """ + COLORS.bg_app + """;
59
+ --bg-surface: """ + COLORS.bg_surface + """;
60
+ --text: """ + COLORS.text_primary + """;
61
+ --text-muted: """ + COLORS.text_muted + """;
62
+ --border: """ + COLORS.border + """;
63
+ --radius: 12px;
64
+ --transition: 150ms ease;
65
+ }
66
+
67
+ .stApp {
68
+ background: """ + COLORS.bg_app + """;
69
+ font-family: 'Inter', sans-serif;
70
+ }
71
+
72
+ #MainMenu, footer, header { visibility: hidden; }
73
+ .stDeployButton { display: none; }
74
+
75
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
76
+ ::-webkit-scrollbar-track { background: transparent; }
77
+ ::-webkit-scrollbar-thumb { background: """ + COLORS.border + """; border-radius: 3px; }
78
+ ::-webkit-scrollbar-thumb:hover { background: """ + COLORS.border_hover + """; }
79
+
80
+ section[data-testid="stSidebar"] { display: none !important; }
81
+
82
+ h1, h2, h3 {
83
+ font-weight: 600;
84
+ color: """ + COLORS.text_primary + """;
85
+ letter-spacing: -0.025em;
86
+ }
87
+
88
+ h1 { font-size: 1.875rem; margin-bottom: 0.5rem; }
89
+ h2 { font-size: 1.5rem; }
90
+ h3 { font-size: 1.125rem; }
91
+
92
+ p { color: """ + COLORS.text_secondary + """; line-height: 1.6; }
93
+
94
+ .card {
95
+ background: """ + COLORS.bg_surface + """;
96
+ border: 1px solid """ + COLORS.border + """;
97
+ border-radius: var(--radius);
98
+ padding: 1.25rem;
99
+ }
100
+
101
+ .metric {
102
+ background: """ + COLORS.bg_surface + """;
103
+ border: 1px solid """ + COLORS.border + """;
104
+ border-radius: var(--radius);
105
+ padding: 1.25rem;
106
+ transition: border-color var(--transition);
107
+ }
108
+
109
+ .metric:hover { border-color: """ + COLORS.primary + """; }
110
+
111
+ .metric-icon {
112
+ width: 44px;
113
+ height: 44px;
114
+ border-radius: 10px;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ font-size: 1.375rem;
119
+ margin-bottom: 1rem;
120
+ }
121
+
122
+ .metric-value {
123
+ font-size: 2rem;
124
+ font-weight: 700;
125
+ color: """ + COLORS.text_primary + """;
126
+ line-height: 1;
127
+ }
128
+
129
+ .metric-label {
130
+ font-size: 0.875rem;
131
+ color: """ + COLORS.text_muted + """;
132
+ margin-top: 0.375rem;
133
+ }
134
+
135
+ .metric-change {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ font-size: 0.75rem;
139
+ font-weight: 500;
140
+ padding: 0.25rem 0.5rem;
141
+ border-radius: 6px;
142
+ margin-top: 0.5rem;
143
+ }
144
+
145
+ .metric-change.up { background: rgba(16, 185, 129, 0.15); color: """ + COLORS.success + """; }
146
+ .metric-change.down { background: rgba(239, 68, 68, 0.15); color: """ + COLORS.error + """; }
147
+
148
+ .stButton > button {
149
+ font-family: 'Inter', sans-serif;
150
+ font-weight: 500;
151
+ font-size: 0.875rem;
152
+ border-radius: 8px;
153
+ padding: 0.625rem 1.25rem;
154
+ transition: all var(--transition);
155
+ border: none;
156
+ }
157
+
158
+ .stTextInput input,
159
+ .stTextArea textarea,
160
+ .stSelectbox > div > div {
161
+ background: """ + COLORS.bg_app + """ !important;
162
+ border: 1px solid """ + COLORS.border + """ !important;
163
+ border-radius: 10px !important;
164
+ color: """ + COLORS.text_primary + """ !important;
165
+ font-family: 'Inter', sans-serif !important;
166
+ }
167
+
168
+ .stTextInput input:focus,
169
+ .stTextArea textarea:focus {
170
+ border-color: """ + COLORS.primary + """ !important;
171
+ box-shadow: 0 0 0 3px """ + COLORS.primary_muted + """ !important;
172
+ }
173
+
174
+ .stTabs [data-baseweb="tab-list"] {
175
+ gap: 0;
176
+ background: """ + COLORS.bg_surface + """;
177
+ border-radius: 10px;
178
+ padding: 4px;
179
+ border: 1px solid """ + COLORS.border + """;
180
+ }
181
+
182
+ .stTabs [data-baseweb="tab"] {
183
+ height: auto;
184
+ padding: 0.625rem 1.25rem;
185
+ border-radius: 8px;
186
+ font-weight: 500;
187
+ font-size: 0.875rem;
188
+ color: """ + COLORS.text_muted + """;
189
+ background: transparent;
190
+ }
191
+
192
+ .stTabs [aria-selected="true"] {
193
+ background: """ + COLORS.primary + """ !important;
194
+ color: white !important;
195
+ }
196
+
197
+ .stTabs [data-baseweb="tab-highlight"],
198
+ .stTabs [data-baseweb="tab-border"] { display: none; }
199
+
200
+ .pipeline {
201
+ display: flex;
202
+ align-items: center;
203
+ background: """ + COLORS.bg_surface + """;
204
+ border: 1px solid """ + COLORS.border + """;
205
+ border-radius: var(--radius);
206
+ padding: 1.5rem;
207
+ gap: 0;
208
+ }
209
+
210
+ .step {
211
+ display: flex;
212
+ flex-direction: column;
213
+ align-items: center;
214
+ gap: 0.5rem;
215
+ flex: 1;
216
+ }
217
+
218
+ .step-dot {
219
+ width: 44px;
220
+ height: 44px;
221
+ border-radius: 50%;
222
+ display: flex;
223
+ align-items: center;
224
+ justify-content: center;
225
+ font-size: 1.125rem;
226
+ font-weight: 600;
227
+ transition: all var(--transition);
228
+ }
229
+
230
+ .step-dot.pending {
231
+ background: """ + COLORS.bg_hover + """;
232
+ color: """ + COLORS.text_muted + """;
233
+ border: 2px dashed """ + COLORS.border_hover + """;
234
+ }
235
+
236
+ .step-dot.active {
237
+ background: """ + COLORS.primary + """;
238
+ color: white;
239
+ box-shadow: 0 0 24px rgba(139, 92, 246, 0.5);
240
+ }
241
+
242
+ .step-dot.done {
243
+ background: """ + COLORS.emerald + """;
244
+ color: white;
245
+ }
246
+
247
+ .step-name {
248
+ font-size: 0.75rem;
249
+ font-weight: 500;
250
+ color: """ + COLORS.text_muted + """;
251
+ }
252
+
253
+ .step-line {
254
+ flex: 0.6;
255
+ height: 2px;
256
+ background: """ + COLORS.border + """;
257
+ }
258
+
259
+ .step-line.done { background: """ + COLORS.emerald + """; }
260
+
261
+ .result {
262
+ background: """ + COLORS.bg_surface + """;
263
+ border: 1px solid """ + COLORS.border + """;
264
+ border-radius: var(--radius);
265
+ padding: 1.25rem;
266
+ transition: all var(--transition);
267
+ cursor: pointer;
268
+ }
269
+
270
+ .result:hover {
271
+ border-color: """ + COLORS.primary + """;
272
+ transform: translateY(-2px);
273
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
274
+ }
275
+
276
+ .score-high { color: """ + COLORS.emerald + """; }
277
+ .score-med { color: """ + COLORS.amber + """; }
278
+ .score-low { color: """ + COLORS.rose + """; }
279
+
280
+ .badge {
281
+ display: inline-flex;
282
+ align-items: center;
283
+ padding: 0.25rem 0.625rem;
284
+ border-radius: 6px;
285
+ font-size: 0.6875rem;
286
+ font-weight: 600;
287
+ text-transform: uppercase;
288
+ }
289
+
290
+ .badge-primary { background: """ + COLORS.primary_muted + """; color: """ + COLORS.primary + """; }
291
+ .badge-success { background: rgba(16, 185, 129, 0.15); color: """ + COLORS.success + """; }
292
+ .badge-warning { background: rgba(245, 158, 11, 0.15); color: """ + COLORS.warning + """; }
293
+ .badge-error { background: rgba(239, 68, 68, 0.15); color: """ + COLORS.error + """; }
294
+
295
+ .quick-action {
296
+ background: """ + COLORS.bg_surface + """;
297
+ border: 1px solid """ + COLORS.border + """;
298
+ border-radius: var(--radius);
299
+ padding: 1.5rem;
300
+ text-align: center;
301
+ cursor: pointer;
302
+ transition: all var(--transition);
303
+ }
304
+
305
+ .quick-action:hover {
306
+ border-color: """ + COLORS.primary + """;
307
+ transform: translateY(-4px);
308
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
309
+ }
310
+
311
+ .quick-action-icon {
312
+ font-size: 2.5rem;
313
+ margin-bottom: 0.75rem;
314
+ display: block;
315
+ }
316
+
317
+ .quick-action-title {
318
+ font-size: 0.9375rem;
319
+ font-weight: 600;
320
+ color: """ + COLORS.text_primary + """;
321
+ }
322
+
323
+ .quick-action-desc {
324
+ font-size: 0.8125rem;
325
+ color: """ + COLORS.text_muted + """;
326
+ margin-top: 0.25rem;
327
+ }
328
+
329
+ .section-header {
330
+ display: flex;
331
+ align-items: center;
332
+ justify-content: space-between;
333
+ margin-bottom: 1rem;
334
+ }
335
+
336
+ .section-title {
337
+ font-size: 1rem;
338
+ font-weight: 600;
339
+ color: """ + COLORS.text_primary + """;
340
+ display: flex;
341
+ align-items: center;
342
+ gap: 0.5rem;
343
+ }
344
+
345
+ .section-link {
346
+ font-size: 0.8125rem;
347
+ color: """ + COLORS.primary + """;
348
+ cursor: pointer;
349
+ }
350
+
351
+ .section-link:hover { text-decoration: underline; }
352
+
353
+ .empty {
354
+ display: flex;
355
+ flex-direction: column;
356
+ align-items: center;
357
+ justify-content: center;
358
+ padding: 4rem 2rem;
359
+ text-align: center;
360
+ }
361
+
362
+ .empty-icon { font-size: 3.5rem; margin-bottom: 1rem; opacity: 0.4; }
363
+ .empty-title { font-size: 1.125rem; font-weight: 600; color: """ + COLORS.text_primary + """; }
364
+ .empty-desc { font-size: 0.9375rem; color: """ + COLORS.text_muted + """; max-width: 320px; margin-top: 0.5rem; }
365
+
366
+ .loading {
367
+ display: flex;
368
+ flex-direction: column;
369
+ align-items: center;
370
+ padding: 3rem;
371
+ }
372
+
373
+ .spinner {
374
+ width: 40px;
375
+ height: 40px;
376
+ border: 3px solid """ + COLORS.border + """;
377
+ border-top-color: """ + COLORS.primary + """;
378
+ border-radius: 50%;
379
+ animation: spin 0.8s linear infinite;
380
+ }
381
+
382
+ @keyframes spin { to { transform: rotate(360deg); } }
383
+
384
+ .loading-text {
385
+ margin-top: 1rem;
386
+ color: """ + COLORS.text_muted + """;
387
+ font-size: 0.875rem;
388
+ }
389
+
390
+ .stProgress > div > div > div {
391
+ background: linear-gradient(90deg, """ + COLORS.primary + """ 0%, """ + COLORS.cyan + """ 100%);
392
+ border-radius: 4px;
393
+ }
394
+
395
+ .stProgress > div > div {
396
+ background: """ + COLORS.bg_hover + """;
397
+ border-radius: 4px;
398
+ }
399
+
400
+ .divider {
401
+ height: 1px;
402
+ background: """ + COLORS.border + """;
403
+ margin: 1.5rem 0;
404
+ }
405
+
406
+ .mol-container {
407
+ background: white;
408
+ border-radius: 10px;
409
+ padding: 0.75rem;
410
+ display: flex;
411
+ align-items: center;
412
+ justify-content: center;
413
+ }
414
+
415
+ .evidence {
416
+ display: inline-flex;
417
+ align-items: center;
418
+ gap: 0.375rem;
419
+ padding: 0.5rem 0.75rem;
420
+ background: """ + COLORS.bg_app + """;
421
+ border: 1px solid """ + COLORS.border + """;
422
+ border-radius: 8px;
423
+ font-size: 0.8125rem;
424
+ color: """ + COLORS.text_secondary + """;
425
+ transition: all var(--transition);
426
+ text-decoration: none;
427
+ }
428
+
429
+ .evidence:hover {
430
+ border-color: """ + COLORS.primary + """;
431
+ color: """ + COLORS.primary + """;
432
+ }
433
+
434
+ .stAlert { border-radius: 10px; border: none; }
435
+
436
+ .stDataFrame {
437
+ border-radius: var(--radius);
438
+ overflow: hidden;
439
+ border: 1px solid """ + COLORS.border + """;
440
+ }
441
+
442
+ .block-container {
443
+ padding-top: 1.25rem;
444
+ }
445
+
446
+ .nav-rail {
447
+ position: sticky;
448
+ top: 1rem;
449
+ display: flex;
450
+ flex-direction: column;
451
+ gap: 0.75rem;
452
+ padding: 1rem;
453
+ background: """ + COLORS.bg_surface + """;
454
+ border: 1px solid """ + COLORS.border + """;
455
+ border-radius: 16px;
456
+ margin-bottom: 1rem;
457
+ }
458
+
459
+ .nav-brand {
460
+ display: flex;
461
+ align-items: center;
462
+ gap: 0.75rem;
463
+ padding-bottom: 0.5rem;
464
+ border-bottom: 1px solid """ + COLORS.border + """;
465
+ }
466
+
467
+ .nav-logo { font-size: 1.5rem; }
468
+
469
+ .nav-title {
470
+ font-size: 1.1rem;
471
+ font-weight: 700;
472
+ color: """ + COLORS.text_primary + """;
473
+ }
474
+
475
+ .nav-title span {
476
+ background: linear-gradient(135deg, """ + COLORS.primary + """ 0%, """ + COLORS.cyan + """ 100%);
477
+ -webkit-background-clip: text;
478
+ -webkit-text-fill-color: transparent;
479
+ }
480
+
481
+ .nav-section {
482
+ font-size: 0.75rem;
483
+ text-transform: uppercase;
484
+ letter-spacing: 0.08em;
485
+ color: """ + COLORS.text_muted + """;
486
+ }
487
+
488
+ div[data-testid="stRadio"] {
489
+ background: """ + COLORS.bg_surface + """;
490
+ border: 1px solid """ + COLORS.border + """;
491
+ border-radius: 16px;
492
+ padding: 0.75rem;
493
+ }
494
+
495
+ div[data-testid="stRadio"] div[role="radiogroup"] {
496
+ display: flex;
497
+ flex-direction: column;
498
+ gap: 0.5rem;
499
+ margin-top: 0.25rem;
500
+ }
501
+
502
+ div[data-testid="stRadio"] input {
503
+ display: none !important;
504
+ }
505
+
506
+ div[data-testid="stRadio"] label {
507
+ background: """ + COLORS.bg_app + """;
508
+ border: 1px solid """ + COLORS.border + """;
509
+ border-radius: 12px;
510
+ padding: 0.65rem 0.9rem;
511
+ font-weight: 500;
512
+ color: """ + COLORS.text_secondary + """;
513
+ transition: all var(--transition);
514
+ margin: 0 !important;
515
+ }
516
+
517
+ div[data-testid="stRadio"] label:hover {
518
+ border-color: """ + COLORS.primary + """;
519
+ color: """ + COLORS.text_primary + """;
520
+ }
521
+
522
+ div[data-testid="stRadio"] label:has(input:checked) {
523
+ background: """ + COLORS.primary + """;
524
+ border-color: """ + COLORS.primary + """;
525
+ color: white;
526
+ box-shadow: 0 8px 20px rgba(139, 92, 246, 0.25);
527
+ }
528
+
529
+ .hero {
530
+ position: relative;
531
+ background: linear-gradient(135deg, rgba(139, 92, 246, 0.12) 0%, rgba(34, 211, 238, 0.08) 100%);
532
+ border: 1px solid """ + COLORS.border + """;
533
+ border-radius: 20px;
534
+ padding: 2.75rem;
535
+ overflow: hidden;
536
+ }
537
+
538
+ .hero-badge {
539
+ display: inline-flex;
540
+ align-items: center;
541
+ gap: 0.5rem;
542
+ padding: 0.35rem 0.75rem;
543
+ border-radius: 999px;
544
+ background: """ + COLORS.primary_muted + """;
545
+ color: """ + COLORS.primary + """;
546
+ font-size: 0.75rem;
547
+ font-weight: 600;
548
+ text-transform: uppercase;
549
+ letter-spacing: 0.08em;
550
+ }
551
+
552
+ .hero-title {
553
+ font-size: 2.25rem;
554
+ font-weight: 700;
555
+ color: """ + COLORS.text_primary + """;
556
+ margin-top: 1rem;
557
+ line-height: 1.1;
558
+ }
559
+
560
+ .hero-subtitle {
561
+ font-size: 1rem;
562
+ color: """ + COLORS.text_muted + """;
563
+ margin-top: 0.75rem;
564
+ max-width: 560px;
565
+ }
566
+
567
+ .hero-actions {
568
+ display: flex;
569
+ gap: 0.75rem;
570
+ margin-top: 1.5rem;
571
+ flex-wrap: wrap;
572
+ }
573
+
574
+ .hero-card {
575
+ background: """ + COLORS.bg_surface + """;
576
+ border: 1px solid """ + COLORS.border + """;
577
+ border-radius: 16px;
578
+ padding: 1.5rem;
579
+ }
580
+ </style>
581
+ """
582
+
583
+ return css
old_ui/pages/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """Page exports."""
2
+
3
+ from bioflow.ui.pages import home, discovery, explorer, data, settings
4
+
5
+ __all__ = ["home", "discovery", "explorer", "data", "settings"]
old_ui/pages/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (351 Bytes). View file
 
old_ui/pages/__pycache__/data.cpython-312.pyc ADDED
Binary file (9.69 kB). View file
 
old_ui/pages/__pycache__/discovery.cpython-312.pyc ADDED
Binary file (8.66 kB). View file
 
old_ui/pages/__pycache__/explorer.cpython-312.pyc ADDED
Binary file (7.02 kB). View file
 
old_ui/pages/__pycache__/home.cpython-312.pyc ADDED
Binary file (10.7 kB). View file
 
old_ui/pages/__pycache__/settings.cpython-312.pyc ADDED
Binary file (9.34 kB). View file
 
old_ui/pages/data.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BioFlow - Data Page
3
+ ===================
4
+ Data management and upload.
5
+ """
6
+
7
+ import streamlit as st
8
+ import sys
9
+ import os
10
+ import pandas as pd
11
+ import numpy as np
12
+
13
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
14
+
15
+ from bioflow.ui.components import (
16
+ page_header, section_header, divider, spacer,
17
+ metric_card, data_table, empty_state
18
+ )
19
+ from bioflow.ui.config import COLORS
20
+
21
+
22
+ def render():
23
+ """Render data page."""
24
+
25
+ page_header("Data Management", "Upload, manage, and organize your datasets", "📊")
26
+
27
+ # Stats Row
28
+ cols = st.columns(4)
29
+
30
+ with cols[0]:
31
+ metric_card("5", "Datasets", "📁", color=COLORS.primary)
32
+ with cols[1]:
33
+ metric_card("24.5K", "Molecules", "🧪", color=COLORS.cyan)
34
+ with cols[2]:
35
+ metric_card("1.2K", "Proteins", "🧬", color=COLORS.emerald)
36
+ with cols[3]:
37
+ metric_card("156 MB", "Storage Used", "💾", color=COLORS.amber)
38
+
39
+ spacer("2rem")
40
+
41
+ # Tabs
42
+ tabs = st.tabs(["📁 Datasets", "📤 Upload", "🔧 Processing"])
43
+
44
+ with tabs[0]:
45
+ section_header("Your Datasets", "📁")
46
+
47
+ # Dataset list
48
+ datasets = [
49
+ {"name": "DrugBank Compounds", "type": "Molecules", "count": "12,450", "size": "45.2 MB", "updated": "2024-01-15"},
50
+ {"name": "ChEMBL Kinase Inhibitors", "type": "Molecules", "count": "8,234", "size": "32.1 MB", "updated": "2024-01-10"},
51
+ {"name": "Custom Protein Targets", "type": "Proteins", "count": "1,245", "size": "78.5 MB", "updated": "2024-01-08"},
52
+ ]
53
+
54
+ for ds in datasets:
55
+ st.markdown(f"""
56
+ <div class="card" style="margin-bottom: 0.75rem;">
57
+ <div style="display: flex; justify-content: space-between; align-items: center;">
58
+ <div>
59
+ <div style="font-weight: 600; color: {COLORS.text_primary};">{ds["name"]}</div>
60
+ <div style="display: flex; gap: 1.5rem; margin-top: 0.5rem;">
61
+ <span style="font-size: 0.8125rem; color: {COLORS.text_muted};">
62
+ <span style="color: {COLORS.primary};">●</span> {ds["type"]}
63
+ </span>
64
+ <span style="font-size: 0.8125rem; color: {COLORS.text_muted};">{ds["count"]} items</span>
65
+ <span style="font-size: 0.8125rem; color: {COLORS.text_muted};">{ds["size"]}</span>
66
+ <span style="font-size: 0.8125rem; color: {COLORS.text_muted};">Updated: {ds["updated"]}</span>
67
+ </div>
68
+ </div>
69
+ <div style="display: flex; gap: 0.5rem;">
70
+ <span class="badge badge-primary">{ds["type"]}</span>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ """, unsafe_allow_html=True)
75
+
76
+ # Action buttons
77
+ btn_cols = st.columns([1, 1, 1, 4])
78
+ with btn_cols[0]:
79
+ st.button("View", key=f"view_{ds['name']}", use_container_width=True)
80
+ with btn_cols[1]:
81
+ st.button("Export", key=f"export_{ds['name']}", use_container_width=True)
82
+ with btn_cols[2]:
83
+ st.button("Delete", key=f"delete_{ds['name']}", use_container_width=True)
84
+
85
+ spacer("0.5rem")
86
+
87
+ with tabs[1]:
88
+ section_header("Upload New Data", "📤")
89
+
90
+ # Upload area
91
+ st.markdown(f"""
92
+ <div style="
93
+ border: 2px dashed {COLORS.border};
94
+ border-radius: 16px;
95
+ padding: 3rem;
96
+ text-align: center;
97
+ background: {COLORS.bg_surface};
98
+ ">
99
+ <div style="font-size: 3rem; margin-bottom: 1rem;">📁</div>
100
+ <div style="font-size: 1.125rem; font-weight: 600; color: {COLORS.text_primary};">
101
+ Drag & drop files here
102
+ </div>
103
+ <div style="font-size: 0.875rem; color: {COLORS.text_muted}; margin-top: 0.5rem;">
104
+ or click to browse
105
+ </div>
106
+ <div style="font-size: 0.75rem; color: {COLORS.text_muted}; margin-top: 1rem;">
107
+ Supports: CSV, SDF, FASTA, PDB, JSON
108
+ </div>
109
+ </div>
110
+ """, unsafe_allow_html=True)
111
+
112
+ uploaded_file = st.file_uploader(
113
+ "Upload file",
114
+ type=["csv", "sdf", "fasta", "pdb", "json"],
115
+ label_visibility="collapsed"
116
+ )
117
+
118
+ if uploaded_file:
119
+ st.success(f"✓ File uploaded: {uploaded_file.name}")
120
+
121
+ col1, col2 = st.columns(2)
122
+ with col1:
123
+ dataset_name = st.text_input("Dataset Name", value=uploaded_file.name.split('.')[0])
124
+ with col2:
125
+ data_type = st.selectbox("Data Type", ["Molecules", "Proteins", "Text"])
126
+
127
+ if st.button("Process & Import", type="primary", use_container_width=True):
128
+ with st.spinner("Processing..."):
129
+ import time
130
+ time.sleep(2)
131
+ st.success("✓ Dataset imported successfully!")
132
+
133
+ with tabs[2]:
134
+ section_header("Data Processing", "🔧")
135
+
136
+ st.markdown(f"""
137
+ <div class="card">
138
+ <div style="font-weight: 600; color: {COLORS.text_primary}; margin-bottom: 0.75rem;">
139
+ Available Operations
140
+ </div>
141
+ </div>
142
+ """, unsafe_allow_html=True)
143
+
144
+ operations = [
145
+ {"icon": "🧹", "name": "Clean & Validate", "desc": "Remove duplicates, fix invalid structures"},
146
+ {"icon": "🔢", "name": "Compute Descriptors", "desc": "Calculate molecular properties and fingerprints"},
147
+ {"icon": "🧠", "name": "Generate Embeddings", "desc": "Create vector representations using AI models"},
148
+ {"icon": "🔗", "name": "Merge Datasets", "desc": "Combine multiple datasets with deduplication"},
149
+ ]
150
+
151
+ for op in operations:
152
+ st.markdown(f"""
153
+ <div class="quick-action" style="margin-bottom: 0.75rem; text-align: left;">
154
+ <div style="display: flex; align-items: center; gap: 1rem;">
155
+ <span style="font-size: 1.5rem;">{op["icon"]}</span>
156
+ <div>
157
+ <div style="font-weight: 600; color: {COLORS.text_primary};">{op["name"]}</div>
158
+ <div style="font-size: 0.8125rem; color: {COLORS.text_muted};">{op["desc"]}</div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ """, unsafe_allow_html=True)
163
+ st.button(f"Run {op['name']}", key=f"op_{op['name']}", use_container_width=True)
old_ui/pages/discovery.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BioFlow - Discovery Page
3
+ ========================
4
+ Drug discovery pipeline interface.
5
+ """
6
+
7
+ import streamlit as st
8
+ import sys
9
+ import os
10
+
11
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
12
+
13
+ from bioflow.ui.components import (
14
+ page_header, section_header, divider, spacer,
15
+ pipeline_progress, bar_chart, empty_state, loading_state
16
+ )
17
+ from bioflow.ui.config import COLORS
18
+
19
+
20
+ def render():
21
+ """Render discovery page."""
22
+
23
+ page_header("Drug Discovery", "Search for drug candidates with AI-powered analysis", "🔬")
24
+
25
+ # Query Input Section
26
+ st.markdown(f"""
27
+ <div class="card" style="margin-bottom: 1.5rem;">
28
+ <div style="font-size: 0.875rem; font-weight: 600; color: {COLORS.text_primary}; margin-bottom: 0.75rem;">
29
+ Search Query
30
+ </div>
31
+ </div>
32
+ """, unsafe_allow_html=True)
33
+
34
+ col1, col2 = st.columns([3, 1])
35
+
36
+ with col1:
37
+ query = st.text_area(
38
+ "Query",
39
+ placeholder="Enter a natural language query, SMILES string, or FASTA sequence...",
40
+ height=100,
41
+ label_visibility="collapsed"
42
+ )
43
+
44
+ with col2:
45
+ st.selectbox("Search Type", ["Similarity", "Binding Affinity", "Properties"], label_visibility="collapsed")
46
+ st.selectbox("Database", ["All", "DrugBank", "ChEMBL", "ZINC"], label_visibility="collapsed")
47
+ search_clicked = st.button("🔍 Search", type="primary", use_container_width=True)
48
+
49
+ spacer("1.5rem")
50
+
51
+ # Pipeline Progress
52
+ section_header("Pipeline Status", "🔄")
53
+
54
+ if "discovery_step" not in st.session_state:
55
+ st.session_state.discovery_step = 0
56
+
57
+ steps = [
58
+ {"name": "Input", "status": "done" if st.session_state.discovery_step > 0 else "active"},
59
+ {"name": "Encode", "status": "done" if st.session_state.discovery_step > 1 else ("active" if st.session_state.discovery_step == 1 else "pending")},
60
+ {"name": "Search", "status": "done" if st.session_state.discovery_step > 2 else ("active" if st.session_state.discovery_step == 2 else "pending")},
61
+ {"name": "Predict", "status": "done" if st.session_state.discovery_step > 3 else ("active" if st.session_state.discovery_step == 3 else "pending")},
62
+ {"name": "Results", "status": "active" if st.session_state.discovery_step == 4 else "pending"},
63
+ ]
64
+
65
+ pipeline_progress(steps)
66
+
67
+ spacer("2rem")
68
+ divider()
69
+ spacer("2rem")
70
+
71
+ # Results Section
72
+ section_header("Results", "🎯")
73
+
74
+ if search_clicked and query:
75
+ st.session_state.discovery_step = 4
76
+ st.session_state.discovery_query = query
77
+
78
+ if st.session_state.discovery_step >= 4:
79
+ # Show results
80
+ tabs = st.tabs(["Top Candidates", "Property Analysis", "Evidence"])
81
+
82
+ with tabs[0]:
83
+ # Results list
84
+ results = [
85
+ {"name": "Candidate A", "score": 0.95, "mw": "342.4", "logp": "2.1", "hbd": "2"},
86
+ {"name": "Candidate B", "score": 0.89, "mw": "298.3", "logp": "1.8", "hbd": "3"},
87
+ {"name": "Candidate C", "score": 0.82, "mw": "415.5", "logp": "3.2", "hbd": "1"},
88
+ {"name": "Candidate D", "score": 0.76, "mw": "267.3", "logp": "1.5", "hbd": "2"},
89
+ {"name": "Candidate E", "score": 0.71, "mw": "389.4", "logp": "2.8", "hbd": "2"},
90
+ ]
91
+
92
+ for r in results:
93
+ score_color = COLORS.emerald if r["score"] >= 0.8 else (COLORS.amber if r["score"] >= 0.5 else COLORS.rose)
94
+ st.markdown(f"""
95
+ <div class="result">
96
+ <div style="display: flex; justify-content: space-between; align-items: flex-start;">
97
+ <div>
98
+ <div style="font-weight: 600; color: {COLORS.text_primary};">{r["name"]}</div>
99
+ <div style="display: flex; gap: 1rem; margin-top: 0.5rem;">
100
+ <span style="font-size: 0.8125rem; color: {COLORS.text_muted};">MW: {r["mw"]}</span>
101
+ <span style="font-size: 0.8125rem; color: {COLORS.text_muted};">LogP: {r["logp"]}</span>
102
+ <span style="font-size: 0.8125rem; color: {COLORS.text_muted};">HBD: {r["hbd"]}</span>
103
+ </div>
104
+ </div>
105
+ <div style="font-size: 1.5rem; font-weight: 700; color: {score_color};">{r["score"]:.0%}</div>
106
+ </div>
107
+ </div>
108
+ """, unsafe_allow_html=True)
109
+ spacer("0.75rem")
110
+
111
+ with tabs[1]:
112
+ # Property distribution
113
+ col1, col2 = st.columns(2)
114
+
115
+ with col1:
116
+ bar_chart(
117
+ {"<200": 5, "200-300": 12, "300-400": 8, "400-500": 3, ">500": 2},
118
+ title="Molecular Weight Distribution",
119
+ height=250
120
+ )
121
+
122
+ with col2:
123
+ bar_chart(
124
+ {"<1": 4, "1-2": 10, "2-3": 8, "3-4": 5, ">4": 3},
125
+ title="LogP Distribution",
126
+ height=250
127
+ )
128
+
129
+ with tabs[2]:
130
+ # Evidence
131
+ st.markdown(f"""
132
+ <div class="card">
133
+ <div style="font-weight: 600; color: {COLORS.text_primary}; margin-bottom: 1rem;">
134
+ Related Literature
135
+ </div>
136
+ </div>
137
+ """, unsafe_allow_html=True)
138
+
139
+ papers = [
140
+ {"title": "Novel therapeutic targets for cancer treatment", "year": "2024", "journal": "Nature Medicine"},
141
+ {"title": "Molecular docking studies of kinase inhibitors", "year": "2023", "journal": "J. Med. Chem."},
142
+ {"title": "AI-driven drug discovery approaches", "year": "2024", "journal": "Drug Discovery Today"},
143
+ ]
144
+
145
+ for p in papers:
146
+ st.markdown(f"""
147
+ <div style="
148
+ padding: 1rem;
149
+ border: 1px solid {COLORS.border};
150
+ border-radius: 8px;
151
+ margin-bottom: 0.75rem;
152
+ ">
153
+ <div style="font-weight: 500; color: {COLORS.text_primary};">{p["title"]}</div>
154
+ <div style="font-size: 0.8125rem; color: {COLORS.text_muted}; margin-top: 0.25rem;">
155
+ {p["journal"]} • {p["year"]}
156
+ </div>
157
+ </div>
158
+ """, unsafe_allow_html=True)
159
+
160
+ else:
161
+ empty_state(
162
+ "🔍",
163
+ "No Results Yet",
164
+ "Enter a query and click Search to find drug candidates"
165
+ )
old_ui/pages/explorer.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BioFlow - Explorer Page
3
+ =======================
4
+ Data exploration and visualization.
5
+ """
6
+
7
+ import streamlit as st
8
+ import sys
9
+ import os
10
+ import numpy as np
11
+
12
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
13
+
14
+ from bioflow.ui.components import (
15
+ page_header, section_header, divider, spacer,
16
+ scatter_chart, heatmap, metric_card, empty_state
17
+ )
18
+ from bioflow.ui.config import COLORS
19
+
20
+
21
+ def render():
22
+ """Render explorer page."""
23
+
24
+ page_header("Data Explorer", "Visualize molecular embeddings and relationships", "🧬")
25
+
26
+ # Controls
27
+ col1, col2, col3, col4 = st.columns(4)
28
+
29
+ with col1:
30
+ dataset = st.selectbox("Dataset", ["DrugBank", "ChEMBL", "ZINC", "Custom"])
31
+ with col2:
32
+ viz_type = st.selectbox("Visualization", ["UMAP", "t-SNE", "PCA"])
33
+ with col3:
34
+ color_by = st.selectbox("Color by", ["Activity", "MW", "LogP", "Cluster"])
35
+ with col4:
36
+ st.write("") # Spacing
37
+ st.write("")
38
+ if st.button("🔄 Refresh", use_container_width=True):
39
+ st.rerun()
40
+
41
+ spacer("1.5rem")
42
+
43
+ # Main visualization area
44
+ col_viz, col_details = st.columns([2, 1])
45
+
46
+ with col_viz:
47
+ section_header("Embedding Space", "🗺️")
48
+
49
+ # Generate sample data
50
+ np.random.seed(42)
51
+ n_points = 200
52
+
53
+ # Create clusters
54
+ cluster1_x = np.random.normal(2, 0.8, n_points // 4)
55
+ cluster1_y = np.random.normal(3, 0.8, n_points // 4)
56
+
57
+ cluster2_x = np.random.normal(-2, 1, n_points // 4)
58
+ cluster2_y = np.random.normal(-1, 1, n_points // 4)
59
+
60
+ cluster3_x = np.random.normal(4, 0.6, n_points // 4)
61
+ cluster3_y = np.random.normal(-2, 0.6, n_points // 4)
62
+
63
+ cluster4_x = np.random.normal(-1, 0.9, n_points // 4)
64
+ cluster4_y = np.random.normal(4, 0.9, n_points // 4)
65
+
66
+ x = list(cluster1_x) + list(cluster2_x) + list(cluster3_x) + list(cluster4_x)
67
+ y = list(cluster1_y) + list(cluster2_y) + list(cluster3_y) + list(cluster4_y)
68
+ labels = [f"Mol_{i}" for i in range(n_points)]
69
+
70
+ scatter_chart(x, y, labels, title=f"{viz_type} Projection - {dataset}", height=450)
71
+
72
+ with col_details:
73
+ section_header("Statistics", "📊")
74
+
75
+ metric_card("12,450", "Total Molecules", "🧪", color=COLORS.primary)
76
+ spacer("0.75rem")
77
+ metric_card("4", "Clusters Found", "🎯", color=COLORS.cyan)
78
+ spacer("0.75rem")
79
+ metric_card("0.89", "Silhouette Score", "📈", color=COLORS.emerald)
80
+ spacer("0.75rem")
81
+ metric_card("85%", "Coverage", "✓", color=COLORS.amber)
82
+
83
+ spacer("2rem")
84
+ divider()
85
+ spacer("2rem")
86
+
87
+ # Similarity Heatmap
88
+ section_header("Similarity Matrix", "🔥")
89
+
90
+ # Sample similarity matrix
91
+ np.random.seed(123)
92
+ labels_short = ["Cluster A", "Cluster B", "Cluster C", "Cluster D", "Cluster E"]
93
+ similarity = np.random.uniform(0.3, 1.0, (5, 5))
94
+ similarity = (similarity + similarity.T) / 2 # Make symmetric
95
+ np.fill_diagonal(similarity, 1.0)
96
+
97
+ heatmap(
98
+ similarity.tolist(),
99
+ labels_short,
100
+ labels_short,
101
+ title="Inter-cluster Similarity",
102
+ height=350
103
+ )
104
+
105
+ spacer("2rem")
106
+
107
+ # Export options
108
+ st.markdown(f"""
109
+ <div class="card">
110
+ <div style="display: flex; justify-content: space-between; align-items: center;">
111
+ <div>
112
+ <div style="font-weight: 600; color: {COLORS.text_primary};">Export Data</div>
113
+ <div style="font-size: 0.8125rem; color: {COLORS.text_muted}; margin-top: 0.25rem;">
114
+ Download embeddings, clusters, or full dataset
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ """, unsafe_allow_html=True)
120
+
121
+ exp_cols = st.columns(3)
122
+ with exp_cols[0]:
123
+ st.button("📥 Embeddings (CSV)", use_container_width=True)
124
+ with exp_cols[1]:
125
+ st.button("📥 Clusters (JSON)", use_container_width=True)
126
+ with exp_cols[2]:
127
+ st.button("📥 Full Dataset", use_container_width=True)
old_ui/pages/home.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BioFlow - Home Page
3
+ ====================
4
+ Clean dashboard with key metrics and quick actions.
5
+ """
6
+
7
+ import streamlit as st
8
+ import sys
9
+ import os
10
+
11
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
12
+
13
+ from bioflow.ui.components import (
14
+ section_header, divider, spacer,
15
+ metric_card, pipeline_progress, bar_chart
16
+ )
17
+ from bioflow.ui.config import COLORS
18
+
19
+
20
+ def render():
21
+ """Render home page."""
22
+
23
+ # Hero Section (Tailark-inspired)
24
+ hero_col, hero_side = st.columns([3, 1.4])
25
+
26
+ with hero_col:
27
+ st.markdown(f"""
28
+ <div class="hero">
29
+ <div class="hero-badge">New • BioFlow 2.0</div>
30
+ <div class="hero-title">AI-Powered Drug Discovery</div>
31
+ <div class="hero-subtitle">
32
+ Run discovery pipelines, predict binding, and surface evidence in one streamlined workspace.
33
+ </div>
34
+ <div class="hero-actions">
35
+ <span class="badge badge-primary">Model-aware search</span>
36
+ <span class="badge badge-success">Evidence-linked</span>
37
+ <span class="badge badge-warning">Fast iteration</span>
38
+ </div>
39
+ </div>
40
+ """, unsafe_allow_html=True)
41
+
42
+ spacer("0.75rem")
43
+ btn1, btn2 = st.columns(2)
44
+ with btn1:
45
+ if st.button("Start Discovery", type="primary", use_container_width=True):
46
+ st.session_state.current_page = "discovery"
47
+ st.rerun()
48
+ with btn2:
49
+ if st.button("Explore Data", use_container_width=True):
50
+ st.session_state.current_page = "explorer"
51
+ st.rerun()
52
+
53
+ with hero_side:
54
+ st.markdown(f"""
55
+ <div class="hero-card">
56
+ <div style="font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; color: {COLORS.text_muted};">
57
+ Today
58
+ </div>
59
+ <div style="font-size: 1.75rem; font-weight: 700; color: {COLORS.text_primary}; margin-top: 0.5rem;">
60
+ 156 Discoveries
61
+ </div>
62
+ <div style="font-size: 0.875rem; color: {COLORS.text_muted}; margin-top: 0.5rem;">
63
+ +12% vs last week
64
+ </div>
65
+ <div class="divider" style="margin: 1rem 0;"></div>
66
+ <div style="display: flex; flex-direction: column; gap: 0.5rem;">
67
+ <span class="badge badge-primary">Discovery</span>
68
+ <span class="badge badge-success">Prediction</span>
69
+ <span class="badge badge-warning">Evidence</span>
70
+ </div>
71
+ </div>
72
+ """, unsafe_allow_html=True)
73
+
74
+ # Metrics Row
75
+ cols = st.columns(4)
76
+
77
+ with cols[0]:
78
+ metric_card("12.5M", "Molecules", "🧪", "+2.3%", "up", COLORS.primary)
79
+ with cols[1]:
80
+ metric_card("847K", "Proteins", "🧬", "+1.8%", "up", COLORS.cyan)
81
+ with cols[2]:
82
+ metric_card("1.2M", "Papers", "📚", "+5.2%", "up", COLORS.emerald)
83
+ with cols[3]:
84
+ metric_card("156", "Discoveries", "✨", "+12%", "up", COLORS.amber)
85
+
86
+ spacer("2rem")
87
+
88
+ # Quick Actions
89
+ section_header("Quick Actions", "⚡")
90
+
91
+ action_cols = st.columns(4)
92
+
93
+ with action_cols[0]:
94
+ st.markdown(f"""
95
+ <div class="quick-action">
96
+ <span class="quick-action-icon">🔍</span>
97
+ <div class="quick-action-title">New Discovery</div>
98
+ <div class="quick-action-desc">Start a pipeline</div>
99
+ </div>
100
+ """, unsafe_allow_html=True)
101
+ if st.button("Start", key="qa_discovery", use_container_width=True):
102
+ st.session_state.current_page = "discovery"
103
+ st.rerun()
104
+
105
+ with action_cols[1]:
106
+ st.markdown(f"""
107
+ <div class="quick-action">
108
+ <span class="quick-action-icon">📊</span>
109
+ <div class="quick-action-title">Explore Data</div>
110
+ <div class="quick-action-desc">Visualize embeddings</div>
111
+ </div>
112
+ """, unsafe_allow_html=True)
113
+ if st.button("Explore", key="qa_explorer", use_container_width=True):
114
+ st.session_state.current_page = "explorer"
115
+ st.rerun()
116
+
117
+ with action_cols[2]:
118
+ st.markdown(f"""
119
+ <div class="quick-action">
120
+ <span class="quick-action-icon">📁</span>
121
+ <div class="quick-action-title">Upload Data</div>
122
+ <div class="quick-action-desc">Add molecules</div>
123
+ </div>
124
+ """, unsafe_allow_html=True)
125
+ if st.button("Upload", key="qa_data", use_container_width=True):
126
+ st.session_state.current_page = "data"
127
+ st.rerun()
128
+
129
+ with action_cols[3]:
130
+ st.markdown(f"""
131
+ <div class="quick-action">
132
+ <span class="quick-action-icon">⚙️</span>
133
+ <div class="quick-action-title">Settings</div>
134
+ <div class="quick-action-desc">Configure models</div>
135
+ </div>
136
+ """, unsafe_allow_html=True)
137
+ if st.button("Configure", key="qa_settings", use_container_width=True):
138
+ st.session_state.current_page = "settings"
139
+ st.rerun()
140
+
141
+ spacer("2rem")
142
+ divider()
143
+ spacer("2rem")
144
+
145
+ # Two Column Layout
146
+ col1, col2 = st.columns([3, 2])
147
+
148
+ with col1:
149
+ section_header("Recent Discoveries", "🎯")
150
+
151
+ # Sample results
152
+ results = [
153
+ {"name": "Aspirin analog", "score": 0.94, "mw": "180.16"},
154
+ {"name": "Novel kinase inhibitor", "score": 0.87, "mw": "331.39"},
155
+ {"name": "EGFR binder candidate", "score": 0.72, "mw": "311.38"},
156
+ ]
157
+
158
+ for r in results:
159
+ score_color = COLORS.emerald if r["score"] >= 0.8 else (COLORS.amber if r["score"] >= 0.5 else COLORS.rose)
160
+ st.markdown(f"""
161
+ <div class="result">
162
+ <div style="display: flex; justify-content: space-between; align-items: center;">
163
+ <div style="font-weight: 600; color: {COLORS.text_primary};">{r["name"]}</div>
164
+ <div style="font-size: 1.25rem; font-weight: 700; color: {score_color};">{r["score"]:.0%}</div>
165
+ </div>
166
+ <div style="font-size: 0.8125rem; color: {COLORS.text_muted}; margin-top: 0.5rem;">
167
+ MW: {r["mw"]}
168
+ </div>
169
+ </div>
170
+ """, unsafe_allow_html=True)
171
+ spacer("0.75rem")
172
+
173
+ with col2:
174
+ section_header("Pipeline Activity", "📈")
175
+
176
+ bar_chart(
177
+ {"Mon": 23, "Tue": 31, "Wed": 28, "Thu": 45, "Fri": 38, "Sat": 12, "Sun": 8},
178
+ title="",
179
+ height=250
180
+ )
181
+
182
+ spacer("1rem")
183
+ section_header("Active Pipeline", "🔄")
184
+
185
+ pipeline_progress([
186
+ {"name": "Encode", "status": "done"},
187
+ {"name": "Search", "status": "active"},
188
+ {"name": "Predict", "status": "pending"},
189
+ {"name": "Verify", "status": "pending"},
190
+ ])
191
+
192
+ spacer("2rem")
193
+
194
+ # Tip
195
+ st.markdown(f"""
196
+ <div style="
197
+ background: {COLORS.bg_surface};
198
+ border: 1px solid {COLORS.border};
199
+ border-radius: 12px;
200
+ padding: 1.25rem;
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 1rem;
204
+ ">
205
+ <span style="font-size: 1.5rem;">💡</span>
206
+ <div>
207
+ <div style="font-size: 0.9375rem; color: {COLORS.text_primary}; font-weight: 500;">Pro Tip</div>
208
+ <div style="font-size: 0.8125rem; color: {COLORS.text_muted};">
209
+ Use natural language like "Find molecules similar to aspirin that can cross the blood-brain barrier"
210
+ </div>
211
+ </div>
212
+ </div>
213
+ """, unsafe_allow_html=True)
old_ui/pages/settings.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BioFlow - Settings Page
3
+ ========================
4
+ Configuration and preferences.
5
+ """
6
+
7
+ import streamlit as st
8
+ import sys
9
+ import os
10
+
11
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
12
+
13
+ from bioflow.ui.components import page_header, section_header, divider, spacer
14
+ from bioflow.ui.config import COLORS
15
+
16
+
17
+ def render():
18
+ """Render settings page."""
19
+
20
+ page_header("Settings", "Configure models, databases, and preferences", "⚙️")
21
+
22
+ # Tabs for different settings sections
23
+ tabs = st.tabs(["🧠 Models", "🗄️ Database", "🔌 API Keys", "🎨 Appearance"])
24
+
25
+ with tabs[0]:
26
+ section_header("Model Configuration", "🧠")
27
+
28
+ st.markdown(f"""
29
+ <div class="card" style="margin-bottom: 1rem;">
30
+ <div style="font-weight: 600; color: {COLORS.text_primary}; margin-bottom: 0.5rem;">
31
+ Embedding Models
32
+ </div>
33
+ <div style="font-size: 0.8125rem; color: {COLORS.text_muted};">
34
+ Configure models used for molecular and protein embeddings
35
+ </div>
36
+ </div>
37
+ """, unsafe_allow_html=True)
38
+
39
+ col1, col2 = st.columns(2)
40
+
41
+ with col1:
42
+ st.selectbox(
43
+ "Molecule Encoder",
44
+ ["MolCLR (Recommended)", "ChemBERTa", "GraphMVP", "MolBERT"],
45
+ help="Model for generating molecular embeddings"
46
+ )
47
+
48
+ st.selectbox(
49
+ "Protein Encoder",
50
+ ["ESM-2 (Recommended)", "ProtTrans", "UniRep", "SeqVec"],
51
+ help="Model for generating protein embeddings"
52
+ )
53
+
54
+ with col2:
55
+ st.selectbox(
56
+ "Binding Predictor",
57
+ ["DrugBAN (Recommended)", "DeepDTA", "GraphDTA", "Custom"],
58
+ help="Model for predicting drug-target binding"
59
+ )
60
+
61
+ st.selectbox(
62
+ "Property Predictor",
63
+ ["ADMET-AI (Recommended)", "ChemProp", "Custom"],
64
+ help="Model for ADMET property prediction"
65
+ )
66
+
67
+ spacer("1rem")
68
+
69
+ st.markdown(f"""
70
+ <div class="card" style="margin-bottom: 1rem;">
71
+ <div style="font-weight: 600; color: {COLORS.text_primary}; margin-bottom: 0.5rem;">
72
+ LLM Settings
73
+ </div>
74
+ <div style="font-size: 0.8125rem; color: {COLORS.text_muted};">
75
+ Configure language models for evidence retrieval and reasoning
76
+ </div>
77
+ </div>
78
+ """, unsafe_allow_html=True)
79
+
80
+ col1, col2 = st.columns(2)
81
+
82
+ with col1:
83
+ st.selectbox(
84
+ "LLM Provider",
85
+ ["OpenAI", "Anthropic", "Local (Ollama)", "Azure OpenAI"]
86
+ )
87
+
88
+ with col2:
89
+ st.selectbox(
90
+ "Model",
91
+ ["GPT-4o", "GPT-4-turbo", "Claude 3.5 Sonnet", "Llama 3.1 70B"]
92
+ )
93
+
94
+ st.slider("Temperature", 0.0, 1.0, 0.7, 0.1)
95
+ st.number_input("Max Tokens", 100, 4096, 2048, 100)
96
+
97
+ with tabs[1]:
98
+ section_header("Database Configuration", "🗄️")
99
+
100
+ st.markdown(f"""
101
+ <div class="card" style="margin-bottom: 1rem;">
102
+ <div style="font-weight: 600; color: {COLORS.text_primary}; margin-bottom: 0.5rem;">
103
+ Vector Database
104
+ </div>
105
+ <div style="font-size: 0.8125rem; color: {COLORS.text_muted};">
106
+ Configure the vector store for similarity search
107
+ </div>
108
+ </div>
109
+ """, unsafe_allow_html=True)
110
+
111
+ col1, col2 = st.columns(2)
112
+
113
+ with col1:
114
+ st.selectbox("Vector Store", ["Qdrant (Recommended)", "Milvus", "Pinecone", "Weaviate", "ChromaDB"])
115
+ st.text_input("Host", value="localhost")
116
+
117
+ with col2:
118
+ st.number_input("Port", 1, 65535, 6333)
119
+ st.text_input("Collection Name", value="bioflow_embeddings")
120
+
121
+ spacer("1rem")
122
+
123
+ st.markdown(f"""
124
+ <div class="card" style="margin-bottom: 1rem;">
125
+ <div style="font-weight: 600; color: {COLORS.text_primary}; margin-bottom: 0.5rem;">
126
+ Knowledge Sources
127
+ </div>
128
+ <div style="font-size: 0.8125rem; color: {COLORS.text_muted};">
129
+ External databases for evidence retrieval
130
+ </div>
131
+ </div>
132
+ """, unsafe_allow_html=True)
133
+
134
+ col1, col2 = st.columns(2)
135
+
136
+ with col1:
137
+ st.checkbox("PubMed", value=True)
138
+ st.checkbox("DrugBank", value=True)
139
+ st.checkbox("ChEMBL", value=True)
140
+
141
+ with col2:
142
+ st.checkbox("UniProt", value=True)
143
+ st.checkbox("KEGG", value=False)
144
+ st.checkbox("Reactome", value=False)
145
+
146
+ with tabs[2]:
147
+ section_header("API Keys", "🔌")
148
+
149
+ st.warning("⚠️ API keys are stored locally and never sent to external servers.")
150
+
151
+ st.text_input("OpenAI API Key", type="password", placeholder="sk-...")
152
+ st.text_input("Anthropic API Key", type="password", placeholder="sk-ant-...")
153
+ st.text_input("PubMed API Key", type="password", placeholder="Optional - for higher rate limits")
154
+ st.text_input("ChEMBL API Key", type="password", placeholder="Optional")
155
+
156
+ spacer("1rem")
157
+
158
+ if st.button("💾 Save API Keys", type="primary"):
159
+ st.success("✓ API keys saved securely")
160
+
161
+ with tabs[3]:
162
+ section_header("Appearance", "🎨")
163
+
164
+ st.selectbox("Theme", ["Dark (Default)", "Light", "System"])
165
+ st.selectbox("Accent Color", ["Purple", "Blue", "Green", "Cyan", "Pink"])
166
+ st.checkbox("Enable animations", value=True)
167
+ st.checkbox("Compact mode", value=False)
168
+ st.slider("Font size", 12, 18, 14)
169
+
170
+ spacer("2rem")
171
+ divider()
172
+ spacer("1rem")
173
+
174
+ # Save buttons
175
+ col1, col2, col3 = st.columns([1, 1, 2])
176
+
177
+ with col1:
178
+ if st.button("💾 Save Settings", type="primary", use_container_width=True):
179
+ st.success("✓ Settings saved successfully!")
180
+
181
+ with col2:
182
+ if st.button("🔄 Reset to Defaults", use_container_width=True):
183
+ st.info("Settings reset to defaults")
184
+
185
+ spacer("2rem")
186
+
187
+ # Version info
188
+ st.markdown(f"""
189
+ <div style="text-align: center; padding: 1rem; color: {COLORS.text_muted}; font-size: 0.75rem;">
190
+ BioFlow v0.1.0 • Built with OpenBioMed
191
+ </div>
192
+ """, unsafe_allow_html=True)
old_ui/requirements.txt ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # BioFlow UI Dependencies
2
+ # =======================
3
+
4
+ # Core Streamlit
5
+ streamlit>=1.29.0
6
+
7
+ # Visualization
8
+ plotly>=5.18.0
9
+ altair>=5.2.0
10
+
11
+ # Data handling
12
+ pandas>=2.0.0
13
+ numpy>=1.24.0
14
+
15
+ # Molecular visualization (optional)
16
+ rdkit>=2023.9.1
17
+ py3Dmol>=2.0.0
18
+
19
+ # Machine Learning (optional, for real encoders)
20
+ # torch>=2.0.0
21
+ # transformers>=4.35.0
22
+
23
+ # Vector database
24
+ qdrant-client>=1.7.0
25
+
26
+ # Image processing
27
+ Pillow>=10.0.0
28
+
29
+ # Utilities
30
+ python-dotenv>=1.0.0
31
+ pyyaml>=6.0.1
ui/.vscode/mcp.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "servers": {
3
+ "shadcn": {
4
+ "command": "npx",
5
+ "args": [
6
+ "shadcn@latest",
7
+ "mcp"
8
+ ]
9
+ }
10
+ }
11
+ }
ui/.vscode/tasks.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "Type Check",
6
+ "type": "shell",
7
+ "command": "pnpm",
8
+ "args": [
9
+ "run",
10
+ "type-check"
11
+ ],
12
+ "problemMatcher": [
13
+ "$tsc"
14
+ ],
15
+ "group": "test"
16
+ },
17
+ {
18
+ "label": "Lint",
19
+ "type": "shell",
20
+ "command": "pnpm",
21
+ "args": [
22
+ "run",
23
+ "lint"
24
+ ],
25
+ "problemMatcher": [
26
+ "$eslint-stylish"
27
+ ],
28
+ "group": "test"
29
+ },
30
+ {
31
+ "label": "Lint",
32
+ "type": "shell",
33
+ "command": "npm",
34
+ "args": [
35
+ "run",
36
+ "lint"
37
+ ],
38
+ "problemMatcher": [
39
+ "$eslint-stylish"
40
+ ],
41
+ "group": "test"
42
+ }
43
+ ]
44
+ }
ui/README.md CHANGED
@@ -1,22 +1,50 @@
1
- This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
 
 
2
 
3
  ## Getting Started
4
 
5
- First, run the development server:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- ```bash
8
- npm run dev
9
- # or
10
- yarn dev
11
- # or
12
- pnpm dev
13
- # or
14
- bun dev
15
- ```
 
 
16
 
17
- Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
 
19
- You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
 
 
 
 
 
20
 
21
  This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
 
 
1
+ # BioFlow UI (Next.js)
2
+
3
+ This project is a migration of the Streamlit "old_ui" to Next.js 16, Shadcn UI, Framer Motion, and Tailwind CSS v4.
4
 
5
  ## Getting Started
6
 
7
+ 1. **Install dependencies:**
8
+
9
+ ```bash
10
+ cd ui
11
+ pnpm install
12
+ # or
13
+ npm install
14
+ ```
15
+
16
+ 2. **Run the development server:**
17
+
18
+ ```bash
19
+ pnpm dev
20
+ # or
21
+ npm run dev
22
+ ```
23
+
24
+ 3. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
25
+
26
+ ## Structure
27
 
28
+ - `app/`: App Router pages and layout.
29
+ - `page.tsx`: Home Dashboard.
30
+ - `discovery/`: Drug Discovery Pipeline interface.
31
+ - `explorer/`: Data Explorer with Charts.
32
+ - `data/`: Data Management.
33
+ - `settings/`: Configuration.
34
+ - `api/`: API Route handlers.
35
+ - `components/`: Reusable UI components.
36
+ - `ui/`: Shadcn-like primitive components (Button, Card, etc.).
37
+ - `sidebar.tsx`: Main Navigation.
38
+ - `page-header.tsx`: Standard page headers.
39
 
40
+ ## Tech Stack
41
 
42
+ - **Framework:** Next.js 16 (App Router)
43
+ - **Styling:** Tailwind CSS v4
44
+ - **UI Library:** Custom components inspired by Shadcn UI
45
+ - **Icons:** Lucide React
46
+ - **Charts:** Recharts
47
+ - **Animations:** Framer Motion (basic animations included, Framer Motion ready)
48
 
49
  This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
50
 
ui/app/api/discovery/route.ts ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse } from 'next/server';
2
+
3
+ export async function POST(request: Request) {
4
+ const body = await request.json();
5
+ const { query } = body;
6
+
7
+ console.log("Starting discovery for:", query);
8
+
9
+ // Here you would typically call your Python backend
10
+ // e.g., await fetch('http://localhost:8000/api/discovery', { ... })
11
+
12
+ // Mock response
13
+ return NextResponse.json({
14
+ success: true,
15
+ jobId: "job_" + Date.now(),
16
+ status: "processing",
17
+ message: "Pipeline started successfully"
18
+ });
19
+ }
ui/app/data/page.tsx ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { PageHeader, SectionHeader } from "@/components/page-header"
4
+ import { Card, CardContent } from "@/components/ui/card"
5
+ import { Button } from "@/components/ui/button"
6
+ import { Badge } from "@/components/ui/badge"
7
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
8
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
9
+ import { Folder, FileText, Database, HardDrive, Upload, CloudUpload, Eye, Download, Trash2 } from "lucide-react"
10
+
11
+ export default function DataPage() {
12
+ const datasets = [
13
+ { name: "DrugBank Compounds", type: "Molecules", count: "12,450", size: "45.2 MB", updated: "2024-01-15" },
14
+ { name: "ChEMBL Kinase Inhibitors", type: "Molecules", count: "8,234", size: "32.1 MB", updated: "2024-01-10" },
15
+ { name: "Custom Protein Targets", type: "Proteins", count: "1,245", size: "78.5 MB", updated: "2024-01-08" },
16
+ ]
17
+
18
+ return (
19
+ <div className="space-y-8 animate-in fade-in duration-500">
20
+ <PageHeader
21
+ title="Data Management"
22
+ subtitle="Upload, manage, and organize your datasets"
23
+ icon={<Database className="h-8 w-8" />}
24
+ />
25
+
26
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
27
+ {[
28
+ { label: "Datasets", value: "5", icon: Folder, color: "text-blue-500" },
29
+ { label: "Molecules", value: "24.5K", icon: FileText, color: "text-cyan-500" },
30
+ { label: "Proteins", value: "1.2K", icon: Database, color: "text-emerald-500" },
31
+ { label: "Storage Used", value: "156 MB", icon: HardDrive, color: "text-amber-500" },
32
+ ].map((stat, i) => (
33
+ <Card key={i}>
34
+ <CardContent className="p-6">
35
+ <div className="flex justify-between items-start mb-2">
36
+ <div className="text-sm font-medium text-muted-foreground">{stat.label}</div>
37
+ <div className={`text-lg ${stat.color}`}><stat.icon className="h-5 w-5" /></div>
38
+ </div>
39
+ <div className="text-2xl font-bold">{stat.value}</div>
40
+ </CardContent>
41
+ </Card>
42
+ ))}
43
+ </div>
44
+
45
+ <Tabs defaultValue="datasets">
46
+ <TabsList>
47
+ <TabsTrigger value="datasets">Datasets</TabsTrigger>
48
+ <TabsTrigger value="upload">Upload</TabsTrigger>
49
+ <TabsTrigger value="processing">Processing</TabsTrigger>
50
+ </TabsList>
51
+ <TabsContent value="datasets" className="space-y-4">
52
+ <SectionHeader title="Your Datasets" icon={<Folder className="h-5 w-5 text-primary" />} />
53
+ <Card>
54
+ <Table>
55
+ <TableHeader>
56
+ <TableRow>
57
+ <TableHead>Name</TableHead>
58
+ <TableHead>Type</TableHead>
59
+ <TableHead>Items</TableHead>
60
+ <TableHead>Size</TableHead>
61
+ <TableHead>Updated</TableHead>
62
+ <TableHead className="text-right">Actions</TableHead>
63
+ </TableRow>
64
+ </TableHeader>
65
+ <TableBody>
66
+ {datasets.map((ds, i) => (
67
+ <TableRow key={i}>
68
+ <TableCell className="font-medium">{ds.name}</TableCell>
69
+ <TableCell>
70
+ <div className="flex items-center gap-2">
71
+ <Badge variant={ds.type === 'Molecules' ? 'default' : 'secondary'}>{ds.type}</Badge>
72
+ </div>
73
+ </TableCell>
74
+ <TableCell>{ds.count}</TableCell>
75
+ <TableCell>{ds.size}</TableCell>
76
+ <TableCell>{ds.updated}</TableCell>
77
+ <TableCell className="text-right">
78
+ <div className="flex justify-end gap-2">
79
+ <Button size="icon" variant="ghost" className="h-8 w-8"><Eye className="h-4 w-4" /></Button>
80
+ <Button size="icon" variant="ghost" className="h-8 w-8"><Download className="h-4 w-4" /></Button>
81
+ <Button size="icon" variant="ghost" className="h-8 w-8 text-destructive hover:text-destructive"><Trash2 className="h-4 w-4" /></Button>
82
+ </div>
83
+ </TableCell>
84
+ </TableRow>
85
+ ))}
86
+ </TableBody>
87
+ </Table>
88
+ </Card>
89
+ </TabsContent>
90
+ <TabsContent value="upload">
91
+ <SectionHeader title="Upload New Data" icon={<Upload className="h-5 w-5 text-primary" />} />
92
+ <Card>
93
+ <CardContent className="p-12">
94
+ <div className="border-2 border-dashed rounded-lg p-12 flex flex-col items-center justify-center text-center space-y-4 hover:bg-accent/50 transition-colors cursor-pointer">
95
+ <div className="h-16 w-16 rounded-full bg-primary/10 flex items-center justify-center text-primary">
96
+ <CloudUpload className="h-8 w-8" />
97
+ </div>
98
+ <div>
99
+ <div className="text-lg font-semibold">Click to upload or drag and drop</div>
100
+ <div className="text-sm text-muted-foreground">CSV, SDF, FASTA, or JSON (max 50MB)</div>
101
+ </div>
102
+ </div>
103
+ </CardContent>
104
+ </Card>
105
+ </TabsContent>
106
+ <TabsContent value="processing">
107
+ <Card>
108
+ <CardContent className="p-12 text-center text-muted-foreground">
109
+ No active processing tasks.
110
+ </CardContent>
111
+ </Card>
112
+ </TabsContent>
113
+ </Tabs>
114
+ </div>
115
+ )
116
+ }
ui/app/discovery/page.tsx ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { PageHeader, SectionHeader } from "@/components/page-header"
5
+ import { Card, CardContent } from "@/components/ui/card"
6
+ import { Textarea } from "@/components/ui/textarea"
7
+ import { Button } from "@/components/ui/button"
8
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
10
+ import { Label } from "@/components/ui/label"
11
+ import { Microscope, Search, CheckCircle2, Circle, Loader2, ArrowRight } from "lucide-react"
12
+
13
+ export default function DiscoveryPage() {
14
+ const [query, setQuery] = React.useState("")
15
+ const [isSearching, setIsSearching] = React.useState(false)
16
+ const [step, setStep] = React.useState(0)
17
+
18
+ const handleSearch = () => {
19
+ setIsSearching(true)
20
+ setStep(1)
21
+
22
+ // Simulate pipeline
23
+ setTimeout(() => setStep(2), 1500)
24
+ setTimeout(() => setStep(3), 3000)
25
+ setTimeout(() => {
26
+ setStep(4)
27
+ setIsSearching(false)
28
+ }, 4500)
29
+ }
30
+
31
+ const steps = [
32
+ { name: "Input", status: step > 0 ? "done" : "active" },
33
+ { name: "Encode", status: step > 1 ? "done" : (step === 1 ? "active" : "pending") },
34
+ { name: "Search", status: step > 2 ? "done" : (step === 2 ? "active" : "pending") },
35
+ { name: "Predict", status: step > 3 ? "done" : (step === 3 ? "active" : "pending") },
36
+ { name: "Results", status: step === 4 ? "active" : "pending" },
37
+ ]
38
+
39
+ return (
40
+ <div className="space-y-8 animate-in fade-in duration-500">
41
+ <PageHeader
42
+ title="Drug Discovery"
43
+ subtitle="Search for drug candidates with AI-powered analysis"
44
+ icon={<Microscope className="h-8 w-8" />}
45
+ />
46
+
47
+ <Card>
48
+ <div className="p-4 border-b font-semibold">Search Query</div>
49
+ <CardContent className="p-6">
50
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-6">
51
+ <div className="md:col-span-3">
52
+ <Textarea
53
+ placeholder="Enter a natural language query, SMILES string, or FASTA sequence..."
54
+ className="min-h-[120px] font-mono"
55
+ value={query}
56
+ onChange={(e) => setQuery(e.target.value)}
57
+ />
58
+ </div>
59
+ <div className="space-y-4">
60
+ <div className="space-y-2">
61
+ <Label>Search Type</Label>
62
+ <Select defaultValue="Similarity">
63
+ <SelectTrigger>
64
+ <SelectValue placeholder="Select type" />
65
+ </SelectTrigger>
66
+ <SelectContent>
67
+ <SelectItem value="Similarity">Similarity</SelectItem>
68
+ <SelectItem value="Binding Affinity">Binding Affinity</SelectItem>
69
+ <SelectItem value="Properties">Properties</SelectItem>
70
+ </SelectContent>
71
+ </Select>
72
+ </div>
73
+ <div className="space-y-2">
74
+ <Label>Database</Label>
75
+ <Select defaultValue="All">
76
+ <SelectTrigger>
77
+ <SelectValue placeholder="Select database" />
78
+ </SelectTrigger>
79
+ <SelectContent>
80
+ <SelectItem value="All">All</SelectItem>
81
+ <SelectItem value="DrugBank">DrugBank</SelectItem>
82
+ <SelectItem value="ChEMBL">ChEMBL</SelectItem>
83
+ <SelectItem value="ZINC">ZINC</SelectItem>
84
+ </SelectContent>
85
+ </Select>
86
+ </div>
87
+ <Button
88
+ className="w-full"
89
+ onClick={handleSearch}
90
+ disabled={isSearching || !query}
91
+ >
92
+ {isSearching ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Search className="mr-2 h-4 w-4" />}
93
+ {isSearching ? "Running..." : "Search"}
94
+ </Button>
95
+ </div>
96
+ </div>
97
+ </CardContent>
98
+ </Card>
99
+
100
+ <div className="space-y-4">
101
+ <SectionHeader title="Pipeline Status" icon={<ArrowRight className="h-5 w-5 text-muted-foreground" />} />
102
+
103
+ <div className="relative">
104
+ <div className="absolute left-0 top-1/2 w-full h-0.5 bg-muted -z-10 transform -translate-y-1/2"></div>
105
+ <div className="flex justify-between items-center w-full px-4">
106
+ {steps.map((s, i) => (
107
+ <div key={i} className="flex flex-col items-center gap-2 bg-background px-2">
108
+ <div className={`h-8 w-8 rounded-full flex items-center justify-center border-2 transition-colors ${
109
+ s.status === 'done' ? 'bg-primary border-primary text-primary-foreground' :
110
+ s.status === 'active' ? 'border-primary text-primary animate-pulse' : 'border-muted text-muted-foreground bg-background'
111
+ }`}>
112
+ {s.status === 'done' ? <CheckCircle2 className="h-5 w-5" /> : <Circle className="h-5 w-5" />}
113
+ </div>
114
+ <span className={`text-sm font-medium ${s.status === 'pending' ? 'text-muted-foreground' : 'text-foreground'}`}>
115
+ {s.name}
116
+ </span>
117
+ </div>
118
+ ))}
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ {step === 4 && (
124
+ <div className="space-y-4 animate-in slide-in-from-bottom-4 duration-500">
125
+ <SectionHeader title="Results" icon={<CheckCircle2 className="h-5 w-5 text-green-500" />} />
126
+
127
+ <Tabs defaultValue="candidates">
128
+ <TabsList>
129
+ <TabsTrigger value="candidates">Top Candidates</TabsTrigger>
130
+ <TabsTrigger value="analysis">Property Analysis</TabsTrigger>
131
+ <TabsTrigger value="evidence">Evidence</TabsTrigger>
132
+ </TabsList>
133
+ <TabsContent value="candidates" className="space-y-4">
134
+ {[
135
+ { name: "Candidate A", score: 0.95, mw: "342.4", logp: "2.1" },
136
+ { name: "Candidate B", score: 0.89, mw: "298.3", logp: "1.8" },
137
+ { name: "Candidate C", score: 0.82, mw: "415.5", logp: "3.2" },
138
+ { name: "Candidate D", score: 0.76, mw: "267.3", logp: "1.5" },
139
+ { name: "Candidate E", score: 0.71, mw: "389.4", logp: "2.8" },
140
+ ].map((candidate, i) => (
141
+ <Card key={i}>
142
+ <CardContent className="p-4 flex items-center justify-between">
143
+ <div>
144
+ <div className="font-bold text-lg">{candidate.name}</div>
145
+ <div className="flex gap-4 text-sm text-muted-foreground">
146
+ <span>MW: {candidate.mw}</span>
147
+ <span>LogP: {candidate.logp}</span>
148
+ </div>
149
+ </div>
150
+ <div className="text-right">
151
+ <div className="text-sm text-muted-foreground">Score</div>
152
+ <div className={`text-xl font-bold ${
153
+ candidate.score >= 0.9 ? 'text-green-600' :
154
+ candidate.score >= 0.8 ? 'text-green-500' : 'text-amber-500'
155
+ }`}>
156
+ {candidate.score}
157
+ </div>
158
+ </div>
159
+ </CardContent>
160
+ </Card>
161
+ ))}
162
+ </TabsContent>
163
+ <TabsContent value="analysis">
164
+ <Card>
165
+ <CardContent className="p-12 text-center text-muted-foreground">
166
+ Chart visualization would go here (using Recharts).
167
+ </CardContent>
168
+ </Card>
169
+ </TabsContent>
170
+ <TabsContent value="evidence">
171
+ <Card>
172
+ <CardContent className="p-12 text-center text-muted-foreground">
173
+ Evidence graph visualization would go here.
174
+ </CardContent>
175
+ </Card>
176
+ </TabsContent>
177
+ </Tabs>
178
+ </div>
179
+ )}
180
+ </div>
181
+ )
182
+ }
ui/app/explorer/page.tsx ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { PageHeader, SectionHeader } from "@/components/page-header"
5
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
6
+ import { Button } from "@/components/ui/button"
7
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
8
+ import { Label } from "@/components/ui/label"
9
+ import { Dna, RefreshCw, Map as MapIcon, BarChart2, Activity, Zap, Grid3X3 } from "lucide-react"
10
+ import { ResponsiveContainer, ScatterChart, Scatter, XAxis, YAxis, ZAxis, Tooltip, Cell } from "recharts"
11
+
12
+ interface DataPoint {
13
+ x: number;
14
+ y: number;
15
+ z: number;
16
+ color: string;
17
+ name: string;
18
+ affinity: number;
19
+ }
20
+
21
+ export default function ExplorerPage() {
22
+ const [dataset, setDataset] = React.useState("DrugBank")
23
+ const [visualization, setVisualization] = React.useState("UMAP")
24
+ const [colorBy, setColorBy] = React.useState("Activity")
25
+ const [data, setData] = React.useState<DataPoint[]>([])
26
+
27
+ // Sample Data Generation
28
+ React.useEffect(() => {
29
+ const points: DataPoint[] = []
30
+ const clusters = [
31
+ { cx: 2, cy: 3, color: "var(--color-chart-1)" },
32
+ { cx: -2, cy: -1, color: "var(--color-chart-2)" },
33
+ { cx: 4, cy: -2, color: "var(--color-chart-3)" },
34
+ { cx: -1, cy: 4, color: "var(--color-chart-4)" }
35
+ ]
36
+
37
+ for (let i = 0; i < 200; i++) {
38
+ const cluster = clusters[Math.floor(i / 50)]
39
+ points.push({
40
+ x: cluster.cx + (Math.random() - 0.5) * 2,
41
+ y: cluster.cy + (Math.random() - 0.5) * 2,
42
+ z: Math.random() * 100,
43
+ color: cluster.color,
44
+ name: `Mol_${i}`,
45
+ affinity: Math.random() * 100
46
+ })
47
+ }
48
+ setData(points)
49
+ }, [dataset])
50
+
51
+ return (
52
+ <div className="space-y-8 animate-in fade-in duration-500">
53
+ <PageHeader
54
+ title="Data Explorer"
55
+ subtitle="Visualize molecular embeddings and relationships"
56
+ icon={<Dna className="h-8 w-8 text-primary" />}
57
+ />
58
+
59
+ <Card className="border-l-4 border-l-primary/50">
60
+ <CardHeader>
61
+ <CardTitle className="flex items-center gap-2">
62
+ <Activity className="h-5 w-5" />
63
+ Visualization Controls
64
+ </CardTitle>
65
+ <CardDescription>Adjust projection parameters and visual styles</CardDescription>
66
+ </CardHeader>
67
+ <CardContent className="grid grid-cols-1 md:grid-cols-4 gap-6">
68
+ <div className="space-y-2">
69
+ <Label htmlFor="dataset">Dataset</Label>
70
+ <Select value={dataset} onValueChange={setDataset}>
71
+ <SelectTrigger id="dataset">
72
+ <SelectValue placeholder="Select dataset" />
73
+ </SelectTrigger>
74
+ <SelectContent>
75
+ <SelectItem value="DrugBank">DrugBank</SelectItem>
76
+ <SelectItem value="ChEMBL">ChEMBL</SelectItem>
77
+ <SelectItem value="ZINC">ZINC</SelectItem>
78
+ </SelectContent>
79
+ </Select>
80
+ </div>
81
+ <div className="space-y-2">
82
+ <Label htmlFor="visualization">Algorithm</Label>
83
+ <Select value={visualization} onValueChange={setVisualization}>
84
+ <SelectTrigger id="visualization">
85
+ <SelectValue placeholder="Select algorithm" />
86
+ </SelectTrigger>
87
+ <SelectContent>
88
+ <SelectItem value="UMAP">UMAP</SelectItem>
89
+ <SelectItem value="t-SNE">t-SNE</SelectItem>
90
+ <SelectItem value="PCA">PCA</SelectItem>
91
+ </SelectContent>
92
+ </Select>
93
+ </div>
94
+ <div className="space-y-2">
95
+ <Label htmlFor="colorBy">Color Mapping</Label>
96
+ <Select value={colorBy} onValueChange={setColorBy}>
97
+ <SelectTrigger id="colorBy">
98
+ <SelectValue placeholder="Select color metric" />
99
+ </SelectTrigger>
100
+ <SelectContent>
101
+ <SelectItem value="Activity">Binding Affinity</SelectItem>
102
+ <SelectItem value="MW">Molecular Weight</SelectItem>
103
+ <SelectItem value="LogP">LogP</SelectItem>
104
+ <SelectItem value="Cluster">Cluster ID</SelectItem>
105
+ </SelectContent>
106
+ </Select>
107
+ </div>
108
+ <div className="flex items-end">
109
+ <Button variant="secondary" className="w-full" onClick={() => setDataset(d => d === "DrugBank" ? "ChEMBL" : "DrugBank")}>
110
+ <RefreshCw className="mr-2 h-4 w-4" />
111
+ Regenerate View
112
+ </Button>
113
+ </div>
114
+ </CardContent>
115
+ </Card>
116
+
117
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
118
+ <div className="lg:col-span-2 space-y-4">
119
+ <SectionHeader title="Embedding Space" icon={<MapIcon className="h-5 w-5 text-primary" />} />
120
+ <Card className="h-[500px] overflow-hidden bg-gradient-to-br from-card to-secondary/30">
121
+ <CardContent className="p-4 h-full">
122
+ <ResponsiveContainer width="100%" height="100%">
123
+ <ScatterChart margin={{ top: 20, right: 20, bottom: 20, left: 20 }}>
124
+ <XAxis type="number" dataKey="x" name="PC1" stroke="currentColor" fontSize={12} tickLine={false} axisLine={{ strokeOpacity: 0.2 }} />
125
+ <YAxis type="number" dataKey="y" name="PC2" stroke="currentColor" fontSize={12} tickLine={false} axisLine={{ strokeOpacity: 0.2 }} />
126
+ <ZAxis type="number" dataKey="z" range={[50, 400]} />
127
+ <Tooltip
128
+ cursor={{ strokeDasharray: '3 3' }}
129
+ content={({ active, payload }) => {
130
+ if (active && payload && payload.length) {
131
+ const data = payload[0].payload;
132
+ return (
133
+ <div className="bg-popover border border-border p-3 rounded-lg shadow-xl text-sm">
134
+ <p className="font-bold mb-1 text-primary">{data.name}</p>
135
+ <div className="grid grid-cols-2 gap-x-4 gap-y-1 text-xs text-muted-foreground">
136
+ <span>X:</span> <span className="text-foreground">{Number(data.x).toFixed(2)}</span>
137
+ <span>Y:</span> <span className="text-foreground">{Number(data.y).toFixed(2)}</span>
138
+ <span>Affinity:</span> <span className="text-foreground">{Number(data.affinity).toFixed(2)}</span>
139
+ </div>
140
+ </div>
141
+ )
142
+ }
143
+ return null;
144
+ }}
145
+ />
146
+ <Scatter name="Molecules" data={data} fill="#8884d8">
147
+ {data.map((entry, index) => (
148
+ <Cell key={`cell-${index}`} fill={entry.color} fillOpacity={0.7} className="hover:opacity-100 transition-opacity duration-200" />
149
+ ))}
150
+ </Scatter>
151
+ </ScatterChart>
152
+ </ResponsiveContainer>
153
+ </CardContent>
154
+ </Card>
155
+ </div>
156
+
157
+ <div className="space-y-6">
158
+ <SectionHeader title="Data Intelligence" icon={<Grid3X3 className="h-5 w-5 text-primary" />} />
159
+
160
+ <div className="grid grid-cols-1 gap-4">
161
+ <Card>
162
+ <CardHeader className="pb-2">
163
+ <CardTitle className="text-sm font-medium text-muted-foreground flex items-center justify-between">
164
+ Active Molecules
165
+ <Zap className="h-4 w-4 text-yellow-500" />
166
+ </CardTitle>
167
+ </CardHeader>
168
+ <CardContent>
169
+ <div className="text-2xl font-bold">12,450</div>
170
+ <p className="text-xs text-muted-foreground mt-1">+2.5% from last month</p>
171
+ </CardContent>
172
+ </Card>
173
+ <Card>
174
+ <CardHeader className="pb-2">
175
+ <CardTitle className="text-sm font-medium text-muted-foreground flex items-center justify-between">
176
+ Clusters Identified
177
+ <Grid3X3 className="h-4 w-4 text-blue-500" />
178
+ </CardTitle>
179
+ </CardHeader>
180
+ <CardContent>
181
+ <div className="text-2xl font-bold text-chart-2">4</div>
182
+ <p className="text-xs text-muted-foreground mt-1">Distinct chemical series</p>
183
+ </CardContent>
184
+ </Card>
185
+ <Card>
186
+ <CardHeader className="pb-2">
187
+ <CardTitle className="text-sm font-medium text-muted-foreground flex items-center justify-between">
188
+ Avg Confidence
189
+ <BarChart2 className="h-4 w-4 text-green-500" />
190
+ </CardTitle>
191
+ </CardHeader>
192
+ <CardContent>
193
+ <div className="text-2xl font-bold text-chart-3">0.89</div>
194
+ <p className="text-xs text-muted-foreground mt-1">Across all predictions</p>
195
+ </CardContent>
196
+ </Card>
197
+ </div>
198
+
199
+ <div className="p-4 rounded-lg bg-secondary/50 border border-secondary">
200
+ <h4 className="font-semibold mb-2 text-sm flex items-center gap-2">
201
+ <Zap className="h-3 w-3 text-primary" />
202
+ Quick Actions
203
+ </h4>
204
+ <div className="space-y-2">
205
+ <Button variant="outline" size="sm" className="w-full justify-start text-xs">Exort Selection as CSV</Button>
206
+ <Button variant="outline" size="sm" className="w-full justify-start text-xs">Run Clustering Analysis</Button>
207
+ </div>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ )
213
+ }
ui/app/globals.css CHANGED
@@ -48,71 +48,71 @@
48
 
49
  :root {
50
  --radius: 0.625rem;
51
- --background: oklch(1 0 0);
52
- --foreground: oklch(0.129 0.042 264.695);
53
  --card: oklch(1 0 0);
54
- --card-foreground: oklch(0.129 0.042 264.695);
55
  --popover: oklch(1 0 0);
56
- --popover-foreground: oklch(0.129 0.042 264.695);
57
- --primary: oklch(0.208 0.042 265.755);
58
- --primary-foreground: oklch(0.984 0.003 247.858);
59
- --secondary: oklch(0.968 0.007 247.896);
60
- --secondary-foreground: oklch(0.208 0.042 265.755);
61
- --muted: oklch(0.968 0.007 247.896);
62
- --muted-foreground: oklch(0.554 0.046 257.417);
63
- --accent: oklch(0.968 0.007 247.896);
64
- --accent-foreground: oklch(0.208 0.042 265.755);
65
  --destructive: oklch(0.577 0.245 27.325);
66
- --border: oklch(0.929 0.013 255.508);
67
- --input: oklch(0.929 0.013 255.508);
68
- --ring: oklch(0.704 0.04 256.788);
69
  --chart-1: oklch(0.646 0.222 41.116);
70
  --chart-2: oklch(0.6 0.118 184.704);
71
  --chart-3: oklch(0.398 0.07 227.392);
72
  --chart-4: oklch(0.828 0.189 84.429);
73
  --chart-5: oklch(0.769 0.188 70.08);
74
- --sidebar: oklch(0.984 0.003 247.858);
75
- --sidebar-foreground: oklch(0.129 0.042 264.695);
76
- --sidebar-primary: oklch(0.208 0.042 265.755);
77
- --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
78
- --sidebar-accent: oklch(0.968 0.007 247.896);
79
- --sidebar-accent-foreground: oklch(0.208 0.042 265.755);
80
- --sidebar-border: oklch(0.929 0.013 255.508);
81
- --sidebar-ring: oklch(0.704 0.04 256.788);
82
  }
83
 
84
  .dark {
85
- --background: oklch(0.129 0.042 264.695);
86
- --foreground: oklch(0.984 0.003 247.858);
87
- --card: oklch(0.208 0.042 265.755);
88
- --card-foreground: oklch(0.984 0.003 247.858);
89
- --popover: oklch(0.208 0.042 265.755);
90
- --popover-foreground: oklch(0.984 0.003 247.858);
91
- --primary: oklch(0.929 0.013 255.508);
92
- --primary-foreground: oklch(0.208 0.042 265.755);
93
- --secondary: oklch(0.279 0.041 260.031);
94
- --secondary-foreground: oklch(0.984 0.003 247.858);
95
- --muted: oklch(0.279 0.041 260.031);
96
- --muted-foreground: oklch(0.704 0.04 256.788);
97
- --accent: oklch(0.279 0.041 260.031);
98
- --accent-foreground: oklch(0.984 0.003 247.858);
99
  --destructive: oklch(0.704 0.191 22.216);
100
- --border: oklch(1 0 0 / 10%);
101
- --input: oklch(1 0 0 / 15%);
102
- --ring: oklch(0.551 0.027 264.364);
103
  --chart-1: oklch(0.488 0.243 264.376);
104
  --chart-2: oklch(0.696 0.17 162.48);
105
  --chart-3: oklch(0.769 0.188 70.08);
106
  --chart-4: oklch(0.627 0.265 303.9);
107
  --chart-5: oklch(0.645 0.246 16.439);
108
- --sidebar: oklch(0.208 0.042 265.755);
109
- --sidebar-foreground: oklch(0.984 0.003 247.858);
110
- --sidebar-primary: oklch(0.488 0.243 264.376);
111
- --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
112
- --sidebar-accent: oklch(0.279 0.041 260.031);
113
- --sidebar-accent-foreground: oklch(0.984 0.003 247.858);
114
- --sidebar-border: oklch(1 0 0 / 10%);
115
- --sidebar-ring: oklch(0.551 0.027 264.364);
116
  }
117
 
118
  @layer base {
 
48
 
49
  :root {
50
  --radius: 0.625rem;
51
+ --background: oklch(0.985 0 0);
52
+ --foreground: oklch(0.145 0.02 260);
53
  --card: oklch(1 0 0);
54
+ --card-foreground: oklch(0.145 0.02 260);
55
  --popover: oklch(1 0 0);
56
+ --popover-foreground: oklch(0.145 0.02 260);
57
+ --primary: oklch(0.55 0.2 280);
58
+ --primary-foreground: oklch(0.985 0 0);
59
+ --secondary: oklch(0.97 0.01 260);
60
+ --secondary-foreground: oklch(0.55 0.2 280);
61
+ --muted: oklch(0.97 0.01 260);
62
+ --muted-foreground: oklch(0.556 0.03 260);
63
+ --accent: oklch(0.97 0.01 260);
64
+ --accent-foreground: oklch(0.55 0.2 280);
65
  --destructive: oklch(0.577 0.245 27.325);
66
+ --border: oklch(0.922 0.01 260);
67
+ --input: oklch(0.922 0.01 260);
68
+ --ring: oklch(0.55 0.2 280);
69
  --chart-1: oklch(0.646 0.222 41.116);
70
  --chart-2: oklch(0.6 0.118 184.704);
71
  --chart-3: oklch(0.398 0.07 227.392);
72
  --chart-4: oklch(0.828 0.189 84.429);
73
  --chart-5: oklch(0.769 0.188 70.08);
74
+ --sidebar: oklch(0.985 0 0);
75
+ --sidebar-foreground: oklch(0.145 0 0);
76
+ --sidebar-primary: oklch(0.205 0 0);
77
+ --sidebar-primary-foreground: oklch(0.985 0 0);
78
+ --sidebar-accent: oklch(0.97 0 0);
79
+ --sidebar-accent-foreground: oklch(0.205 0 0);
80
+ --sidebar-border: oklch(0.922 0 0);
81
+ --sidebar-ring: oklch(0.705 0 0);
82
  }
83
 
84
  .dark {
85
+ --background: oklch(0.12 0.03 260);
86
+ --foreground: oklch(0.95 0.01 260);
87
+ --card: oklch(0.15 0.03 260);
88
+ --card-foreground: oklch(0.95 0.01 260);
89
+ --popover: oklch(0.15 0.03 260);
90
+ --popover-foreground: oklch(0.95 0.01 260);
91
+ --primary: oklch(0.65 0.2 280);
92
+ --primary-foreground: oklch(0.12 0.03 260);
93
+ --secondary: oklch(0.2 0.04 260);
94
+ --secondary-foreground: oklch(0.95 0.01 260);
95
+ --muted: oklch(0.2 0.04 260);
96
+ --muted-foreground: oklch(0.7 0.04 260);
97
+ --accent: oklch(0.2 0.04 260);
98
+ --accent-foreground: oklch(0.95 0.01 260);
99
  --destructive: oklch(0.704 0.191 22.216);
100
+ --border: oklch(0.25 0.04 260);
101
+ --input: oklch(0.25 0.04 260);
102
+ --ring: oklch(0.65 0.2 280);
103
  --chart-1: oklch(0.488 0.243 264.376);
104
  --chart-2: oklch(0.696 0.17 162.48);
105
  --chart-3: oklch(0.769 0.188 70.08);
106
  --chart-4: oklch(0.627 0.265 303.9);
107
  --chart-5: oklch(0.645 0.246 16.439);
108
+ --sidebar: oklch(0.15 0.03 260);
109
+ --sidebar-foreground: oklch(0.95 0.01 260);
110
+ --sidebar-primary: oklch(0.65 0.2 280);
111
+ --sidebar-primary-foreground: oklch(0.985 0 0);
112
+ --sidebar-accent: oklch(0.2 0.04 260);
113
+ --sidebar-accent-foreground: oklch(0.95 0.01 260);
114
+ --sidebar-border: oklch(0.25 0.04 260);
115
+ --sidebar-ring: oklch(0.65 0.2 280);
116
  }
117
 
118
  @layer base {
ui/app/layout.tsx CHANGED
@@ -1,6 +1,7 @@
1
  import type { Metadata } from "next";
2
  import { Geist, Geist_Mono } from "next/font/google";
3
  import "./globals.css";
 
4
 
5
  const geistSans = Geist({
6
  variable: "--font-geist-sans",
@@ -13,8 +14,8 @@ const geistMono = Geist_Mono({
13
  });
14
 
15
  export const metadata: Metadata = {
16
- title: "Create Next App",
17
- description: "Generated by create next app",
18
  };
19
 
20
  export default function RootLayout({
@@ -25,9 +26,14 @@ export default function RootLayout({
25
  return (
26
  <html lang="en">
27
  <body
28
- className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29
  >
30
- {children}
 
 
 
 
 
31
  </body>
32
  </html>
33
  );
 
1
  import type { Metadata } from "next";
2
  import { Geist, Geist_Mono } from "next/font/google";
3
  import "./globals.css";
4
+ import { Sidebar } from "@/components/sidebar";
5
 
6
  const geistSans = Geist({
7
  variable: "--font-geist-sans",
 
14
  });
15
 
16
  export const metadata: Metadata = {
17
+ title: "BioFlow",
18
+ description: "AI-Powered Drug Discovery Platform",
19
  };
20
 
21
  export default function RootLayout({
 
26
  return (
27
  <html lang="en">
28
  <body
29
+ className={`${geistSans.variable} ${geistMono.variable} antialiased flex h-screen overflow-hidden`}
30
  >
31
+ <Sidebar />
32
+ <main className="flex-1 overflow-y-auto bg-background p-8">
33
+ <div className="mx-auto max-w-7xl">
34
+ {children}
35
+ </div>
36
+ </main>
37
  </body>
38
  </html>
39
  );
ui/app/page.tsx CHANGED
@@ -1,65 +1,169 @@
1
- import Image from "next/image";
 
 
 
 
 
 
 
 
2
 
3
  export default function Home() {
4
  return (
5
- <div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
6
- <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
7
- <Image
8
- className="dark:invert"
9
- src="/next.svg"
10
- alt="Next.js logo"
11
- width={100}
12
- height={20}
13
- priority
14
- />
15
- <div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
16
- <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
17
- To get started, edit the page.tsx file.
18
- </h1>
19
- <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
20
- Looking for a starting point or more instructions? Head over to{" "}
21
- <a
22
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
23
- className="font-medium text-zinc-950 dark:text-zinc-50"
24
- >
25
- Templates
26
- </a>{" "}
27
- or the{" "}
28
- <a
29
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
30
- className="font-medium text-zinc-950 dark:text-zinc-50"
31
- >
32
- Learning
33
- </a>{" "}
34
- center.
35
  </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  </div>
37
- <div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
38
- <a
39
- className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
40
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
41
- target="_blank"
42
- rel="noopener noreferrer"
43
- >
44
- <Image
45
- className="dark:invert"
46
- src="/vercel.svg"
47
- alt="Vercel logomark"
48
- width={16}
49
- height={16}
50
- />
51
- Deploy Now
52
- </a>
53
- <a
54
- className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
55
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
56
- target="_blank"
57
- rel="noopener noreferrer"
58
- >
59
- Documentation
60
- </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  </div>
62
- </main>
63
  </div>
64
- );
65
  }
 
1
+ "use client"
2
+
3
+ import { Button } from "@/components/ui/button"
4
+ import { Badge } from "@/components/ui/badge"
5
+ import { Card, CardContent } from "@/components/ui/card"
6
+ import { Separator } from "@/components/ui/separator"
7
+ import { SectionHeader } from "@/components/page-header"
8
+ import { ArrowUp, Zap, Search, Database, FileText, Sparkles } from "lucide-react"
9
+ import Link from "next/link"
10
 
11
  export default function Home() {
12
  return (
13
+ <div className="space-y-8 animate-in fade-in duration-500">
14
+ {/* Hero Section */}
15
+ <div className="flex flex-col lg:flex-row gap-6">
16
+ <div className="flex-1 rounded-2xl bg-gradient-to-br from-primary/10 via-background to-background p-8 border">
17
+ <Badge variant="secondary" className="mb-4">New • BioFlow 2.0</Badge>
18
+ <h1 className="text-4xl font-bold tracking-tight mb-4">AI-Powered Drug Discovery</h1>
19
+ <p className="text-lg text-muted-foreground mb-6 max-w-xl">
20
+ Run discovery pipelines, predict binding, and surface evidence in one streamlined workspace.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  </p>
22
+ <div className="flex gap-2 mb-6">
23
+ <Badge variant="outline" className="bg-primary/5 border-primary/20 text-primary">Model-aware search</Badge>
24
+ <Badge variant="outline" className="bg-green-500/10 border-green-500/20 text-green-700 dark:text-green-400">Evidence-linked</Badge>
25
+ <Badge variant="outline" className="bg-amber-500/10 border-amber-500/20 text-amber-700 dark:text-amber-400">Fast iteration</Badge>
26
+ </div>
27
+
28
+ <div className="flex gap-4">
29
+ <Link href="/discovery">
30
+ <Button size="lg" className="font-semibold">
31
+ Start Discovery
32
+ </Button>
33
+ </Link>
34
+ <Link href="/explorer">
35
+ <Button size="lg" variant="outline">
36
+ Explore Data
37
+ </Button>
38
+ </Link>
39
+ </div>
40
+ </div>
41
+
42
+ <div className="lg:w-[350px]">
43
+ <Card className="h-full">
44
+ <CardContent className="p-6 flex flex-col justify-between h-full">
45
+ <div>
46
+ <div className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-2">Today</div>
47
+ <div className="text-4xl font-bold mb-2">156 Discoveries</div>
48
+ <div className="text-sm text-green-600 font-medium flex items-center gap-1">
49
+ <ArrowUp className="h-4 w-4" />
50
+ +12% vs last week
51
+ </div>
52
+ </div>
53
+
54
+ <Separator className="my-4" />
55
+
56
+ <div className="space-y-2">
57
+ <div className="flex items-center justify-between text-sm">
58
+ <span className="flex items-center gap-2">
59
+ <span className="h-2 w-2 rounded-full bg-primary"></span>
60
+ Discovery
61
+ </span>
62
+ <span className="font-mono font-medium">64</span>
63
+ </div>
64
+ <div className="flex items-center justify-between text-sm">
65
+ <span className="flex items-center gap-2">
66
+ <span className="h-2 w-2 rounded-full bg-green-500"></span>
67
+ Prediction
68
+ </span>
69
+ <span className="font-mono font-medium">42</span>
70
+ </div>
71
+ <div className="flex items-center justify-between text-sm">
72
+ <span className="flex items-center gap-2">
73
+ <span className="h-2 w-2 rounded-full bg-amber-500"></span>
74
+ Evidence
75
+ </span>
76
+ <span className="font-mono font-medium">50</span>
77
+ </div>
78
+ </div>
79
+ </CardContent>
80
+ </Card>
81
  </div>
82
+ </div>
83
+
84
+ {/* Metrics Row */}
85
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
86
+ {[
87
+ { label: "Molecules", value: "12.5M", icon: "🧪", change: "+2.3%", color: "text-blue-500" },
88
+ { label: "Proteins", value: "847K", icon: "🧬", change: "+1.8%", color: "text-cyan-500" },
89
+ { label: "Papers", value: "1.2M", icon: "📚", change: "+5.2%", color: "text-emerald-500" },
90
+ { label: "Discoveries", value: "156", icon: "✨", change: "+12%", color: "text-amber-500" }
91
+ ].map((metric, i) => (
92
+ <Card key={i}>
93
+ <CardContent className="p-6">
94
+ <div className="flex justify-between items-start mb-2">
95
+ <div className="text-sm font-medium text-muted-foreground">{metric.label}</div>
96
+ <div className="text-lg">{metric.icon}</div>
97
+ </div>
98
+ <div className="text-2xl font-bold mb-1">{metric.value}</div>
99
+ <div className="text-xs font-medium flex items-center gap-1 text-green-500">
100
+ <ArrowUp className="h-3 w-3" />
101
+ {metric.change}
102
+ </div>
103
+ </CardContent>
104
+ </Card>
105
+ ))}
106
+ </div>
107
+
108
+ {/* Quick Actions */}
109
+ <div className="pt-4">
110
+ <SectionHeader title="Quick Actions" icon={<Zap className="h-5 w-5 text-amber-500" />} />
111
+
112
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
113
+ <Link href="/discovery" className="block">
114
+ <Card className="hover:bg-accent/50 transition-colors cursor-pointer h-full">
115
+ <CardContent className="p-6 flex flex-col items-center text-center gap-3">
116
+ <div className="h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center text-primary">
117
+ <Search className="h-5 w-5" />
118
+ </div>
119
+ <div>
120
+ <div className="font-semibold">New Discovery</div>
121
+ <div className="text-sm text-muted-foreground">Start a pipeline</div>
122
+ </div>
123
+ </CardContent>
124
+ </Card>
125
+ </Link>
126
+ <Link href="/explorer" className="block">
127
+ <Card className="hover:bg-accent/50 transition-colors cursor-pointer h-full">
128
+ <CardContent className="p-6 flex flex-col items-center text-center gap-3">
129
+ <div className="h-10 w-10 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-500">
130
+ <Database className="h-5 w-5" />
131
+ </div>
132
+ <div>
133
+ <div className="font-semibold">Browse Data</div>
134
+ <div className="text-sm text-muted-foreground">Explore datasets</div>
135
+ </div>
136
+ </CardContent>
137
+ </Card>
138
+ </Link>
139
+ <Link href="/data" className="block">
140
+ <Card className="hover:bg-accent/50 transition-colors cursor-pointer h-full">
141
+ <CardContent className="p-6 flex flex-col items-center text-center gap-3">
142
+ <div className="h-10 w-10 rounded-full bg-purple-500/10 flex items-center justify-center text-purple-500">
143
+ <FileText className="h-5 w-5" />
144
+ </div>
145
+ <div>
146
+ <div className="font-semibold">Training</div>
147
+ <div className="text-sm text-muted-foreground">Train new models</div>
148
+ </div>
149
+ </CardContent>
150
+ </Card>
151
+ </Link>
152
+ <Link href="/settings" className="block">
153
+ <Card className="hover:bg-accent/50 transition-colors cursor-pointer h-full">
154
+ <CardContent className="p-6 flex flex-col items-center text-center gap-3">
155
+ <div className="h-10 w-10 rounded-full bg-slate-500/10 flex items-center justify-center text-slate-500">
156
+ <Sparkles className="h-5 w-5" />
157
+ </div>
158
+ <div>
159
+ <div className="font-semibold">View Insights</div>
160
+ <div className="text-sm text-muted-foreground">Check predictions</div>
161
+ </div>
162
+ </CardContent>
163
+ </Card>
164
+ </Link>
165
  </div>
166
+ </div>
167
  </div>
168
+ )
169
  }
ui/app/settings/page.tsx ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { PageHeader, SectionHeader } from "@/components/page-header"
4
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
5
+ import { Button } from "@/components/ui/button"
6
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
7
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
8
+ import { Label } from "@/components/ui/label"
9
+ import { Slider } from "@/components/ui/slider"
10
+ import { Switch } from "@/components/ui/switch"
11
+ import { Settings, Brain, Save } from "lucide-react"
12
+
13
+ export default function SettingsPage() {
14
+ return (
15
+ <div className="space-y-8 animate-in fade-in duration-500">
16
+ <PageHeader
17
+ title="Settings"
18
+ subtitle="Configure models, databases, and preferences"
19
+ icon={<Settings className="h-8 w-8 text-primary" />}
20
+ />
21
+
22
+ <Tabs defaultValue="models" className="w-full">
23
+ <TabsList className="w-full justify-start overflow-x-auto">
24
+ <TabsTrigger value="models">Models</TabsTrigger>
25
+ <TabsTrigger value="database">Database</TabsTrigger>
26
+ <TabsTrigger value="api">API Keys</TabsTrigger>
27
+ <TabsTrigger value="appearance">Appearance</TabsTrigger>
28
+ <TabsTrigger value="system">System</TabsTrigger>
29
+ </TabsList>
30
+ <TabsContent value="models" className="space-y-6 mt-6">
31
+ <SectionHeader title="Model Configuration" icon={<Brain className="h-5 w-5 text-primary" />} />
32
+
33
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
34
+ <Card>
35
+ <CardHeader>
36
+ <CardTitle>Embedding Models</CardTitle>
37
+ <CardDescription>Configure models used for molecular and protein embeddings</CardDescription>
38
+ </CardHeader>
39
+ <CardContent className="space-y-4">
40
+ <div className="space-y-2">
41
+ <Label htmlFor="mol-encoder">Molecule Encoder</Label>
42
+ <Select defaultValue="MolCLR">
43
+ <SelectTrigger id="mol-encoder">
44
+ <SelectValue placeholder="Select molecule encoder" />
45
+ </SelectTrigger>
46
+ <SelectContent>
47
+ <SelectItem value="MolCLR">MolCLR (Recommended)</SelectItem>
48
+ <SelectItem value="ChemBERTa">ChemBERTa</SelectItem>
49
+ <SelectItem value="GraphMVP">GraphMVP</SelectItem>
50
+ <SelectItem value="MolBERT">MolBERT</SelectItem>
51
+ </SelectContent>
52
+ </Select>
53
+ </div>
54
+ <div className="space-y-2">
55
+ <Label htmlFor="prot-encoder">Protein Encoder</Label>
56
+ <Select defaultValue="ESM-2">
57
+ <SelectTrigger id="prot-encoder">
58
+ <SelectValue placeholder="Select protein encoder" />
59
+ </SelectTrigger>
60
+ <SelectContent>
61
+ <SelectItem value="ESM-2">ESM-2 (Recommended)</SelectItem>
62
+ <SelectItem value="ProtTrans">ProtTrans</SelectItem>
63
+ <SelectItem value="UniRep">UniRep</SelectItem>
64
+ <SelectItem value="SeqVec">SeqVec</SelectItem>
65
+ </SelectContent>
66
+ </Select>
67
+ </div>
68
+ </CardContent>
69
+ </Card>
70
+
71
+ <Card>
72
+ <CardHeader>
73
+ <CardTitle>Prediction Heads</CardTitle>
74
+ <CardDescription>Configure downstream task predictors</CardDescription>
75
+ </CardHeader>
76
+ <CardContent className="space-y-4">
77
+ <div className="space-y-2">
78
+ <Label htmlFor="binding">Binding Predictor</Label>
79
+ <Select defaultValue="DrugBAN">
80
+ <SelectTrigger id="binding">
81
+ <SelectValue placeholder="Select predictor" />
82
+ </SelectTrigger>
83
+ <SelectContent>
84
+ <SelectItem value="DrugBAN">DrugBAN (Recommended)</SelectItem>
85
+ <SelectItem value="DeepDTA">DeepDTA</SelectItem>
86
+ <SelectItem value="GraphDTA">GraphDTA</SelectItem>
87
+ <SelectItem value="Custom">Custom</SelectItem>
88
+ </SelectContent>
89
+ </Select>
90
+ </div>
91
+ <div className="space-y-2">
92
+ <Label htmlFor="property">Property Predictor</Label>
93
+ <Select defaultValue="ADMET-AI">
94
+ <SelectTrigger id="property">
95
+ <SelectValue placeholder="Select predictor" />
96
+ </SelectTrigger>
97
+ <SelectContent>
98
+ <SelectItem value="ADMET-AI">ADMET-AI (Recommended)</SelectItem>
99
+ <SelectItem value="ChemProp">ChemProp</SelectItem>
100
+ <SelectItem value="Custom">Custom</SelectItem>
101
+ </SelectContent>
102
+ </Select>
103
+ </div>
104
+ </CardContent>
105
+ </Card>
106
+ </div>
107
+
108
+ <Card>
109
+ <CardHeader>
110
+ <CardTitle>LLM Settings</CardTitle>
111
+ <CardDescription>Configure language models for evidence retrieval and reasoning</CardDescription>
112
+ </CardHeader>
113
+ <CardContent className="grid grid-cols-1 md:grid-cols-2 gap-6">
114
+ <div className="space-y-2">
115
+ <Label htmlFor="llm-provider">LLM Provider</Label>
116
+ <Select defaultValue="OpenAI">
117
+ <SelectTrigger id="llm-provider">
118
+ <SelectValue placeholder="Select provider" />
119
+ </SelectTrigger>
120
+ <SelectContent>
121
+ <SelectItem value="OpenAI">OpenAI</SelectItem>
122
+ <SelectItem value="Anthropic">Anthropic</SelectItem>
123
+ <SelectItem value="Local">Local (Ollama)</SelectItem>
124
+ <SelectItem value="Azure">Azure OpenAI</SelectItem>
125
+ </SelectContent>
126
+ </Select>
127
+ </div>
128
+ <div className="space-y-2">
129
+ <Label htmlFor="llm-model">Model</Label>
130
+ <Select defaultValue="GPT-4o">
131
+ <SelectTrigger id="llm-model">
132
+ <SelectValue placeholder="Select model" />
133
+ </SelectTrigger>
134
+ <SelectContent>
135
+ <SelectItem value="GPT-4o">GPT-4o</SelectItem>
136
+ <SelectItem value="GPT-4-turbo">GPT-4-turbo</SelectItem>
137
+ <SelectItem value="Claude 3.5">Claude 3.5 Sonnet</SelectItem>
138
+ <SelectItem value="Llama 3">Llama 3.1 70B</SelectItem>
139
+ </SelectContent>
140
+ </Select>
141
+ </div>
142
+ <div className="col-span-1 md:col-span-2 space-y-4">
143
+ <div className="space-y-2">
144
+ <div className="flex items-center justify-between">
145
+ <Label>Temperature: 0.7</Label>
146
+ <span className="text-xs text-muted-foreground">Creativity vs Precision</span>
147
+ </div>
148
+ <Slider defaultValue={[0.7]} max={1} step={0.1} />
149
+ </div>
150
+ <div className="flex items-center space-x-2">
151
+ <Switch id="stream" defaultChecked />
152
+ <Label htmlFor="stream">Stream Responses</Label>
153
+ </div>
154
+ </div>
155
+ </CardContent>
156
+ </Card>
157
+ </TabsContent>
158
+
159
+ <TabsContent value="appearance">
160
+ <Card>
161
+ <CardContent className="p-12 text-center text-muted-foreground">
162
+ Theme settings coming soon.
163
+ </CardContent>
164
+ </Card>
165
+ </TabsContent>
166
+
167
+ <TabsContent value="database">
168
+ <Card>
169
+ <CardContent className="p-12 text-center text-muted-foreground">
170
+ Database connection settings.
171
+ </CardContent>
172
+ </Card>
173
+ </TabsContent>
174
+
175
+ <TabsContent value="api">
176
+ <Card>
177
+ <CardContent className="p-12 text-center text-muted-foreground">
178
+ API Key configuration.
179
+ </CardContent>
180
+ </Card>
181
+ </TabsContent>
182
+ </Tabs>
183
+
184
+ <div className="fixed bottom-6 right-6">
185
+ <Button size="lg" className="shadow-2xl">
186
+ <Save className="mr-2 h-4 w-4" />
187
+ Save Changes
188
+ </Button>
189
+ </div>
190
+ </div>
191
+ )
192
+ }
ui/components/page-header.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ui/components/page-header.tsx
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface PageHeaderProps {
5
+ title: string
6
+ subtitle?: string
7
+ icon?: React.ReactNode
8
+ className?: string
9
+ }
10
+
11
+ export function PageHeader({ title, subtitle, icon, className }: PageHeaderProps) {
12
+ return (
13
+ <div className={cn("mb-8 space-y-2", className)}>
14
+ <h1 className="flex items-center gap-3 text-3xl font-bold tracking-tight">
15
+ {icon && <span className="text-primary">{icon}</span>}
16
+ {title}
17
+ </h1>
18
+ {subtitle && (
19
+ <p className="text-lg text-muted-foreground">
20
+ {subtitle}
21
+ </p>
22
+ )}
23
+ </div>
24
+ )
25
+ }
26
+
27
+ export function SectionHeader({ title, icon, action }: { title: string, icon?: React.ReactNode, action?: React.ReactNode }) {
28
+ return (
29
+ <div className="flex items-center justify-between mb-6">
30
+ <h2 className="text-xl font-semibold flex items-center gap-2">
31
+ {icon}
32
+ {title}
33
+ </h2>
34
+ {action}
35
+ </div>
36
+ )
37
+ }
ui/components/sidebar.tsx ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ui/components/sidebar.tsx
2
+ "use client"
3
+
4
+ import Link from "next/link"
5
+ import { usePathname } from "next/navigation"
6
+ import { Home, Microscope, Dna, BarChart2, Settings, Terminal } from "lucide-react"
7
+
8
+ import { cn } from "@/lib/utils"
9
+ import { Button } from "@/components/ui/button"
10
+
11
+ const sidebarItems = [
12
+ {
13
+ title: "Home",
14
+ href: "/",
15
+ icon: Home,
16
+ },
17
+ {
18
+ title: "Discovery",
19
+ href: "/discovery",
20
+ icon: Microscope,
21
+ },
22
+ {
23
+ title: "Explorer",
24
+ href: "/explorer",
25
+ icon: Dna,
26
+ },
27
+ {
28
+ title: "Data",
29
+ href: "/data",
30
+ icon: BarChart2,
31
+ },
32
+ {
33
+ title: "Settings",
34
+ href: "/settings",
35
+ icon: Settings,
36
+ },
37
+ ]
38
+
39
+ export function Sidebar() {
40
+ const pathname = usePathname()
41
+
42
+ return (
43
+ <div className="flex h-screen w-[250px] flex-col border-r bg-card pb-4 pt-6">
44
+ <div className="px-6 mb-8 flex items-center gap-2">
45
+ <div className="h-8 w-8 rounded-lg bg-primary/20 flex items-center justify-center text-primary">
46
+ <Dna className="h-5 w-5" />
47
+ </div>
48
+ <div className="font-bold text-xl tracking-tight">
49
+ Bio<span className="text-primary">Flow</span>
50
+ </div>
51
+ </div>
52
+
53
+ <div className="px-4 py-2">
54
+ <div className="text-xs font-semibold text-muted-foreground mb-4 px-2 uppercase tracking-wider">
55
+ Navigation
56
+ </div>
57
+ <nav className="space-y-1">
58
+ {sidebarItems.map((item) => (
59
+ <Link
60
+ key={item.href}
61
+ href={item.href}
62
+ >
63
+ <Button
64
+ variant={pathname === item.href ? "secondary" : "ghost"}
65
+ className={cn(
66
+ "w-full justify-start gap-3",
67
+ pathname === item.href && "bg-secondary font-medium"
68
+ )}
69
+ >
70
+ <item.icon className="h-4 w-4" />
71
+ {item.title}
72
+ </Button>
73
+ </Link>
74
+ ))}
75
+ </nav>
76
+ </div>
77
+
78
+ <div className="mt-auto px-4">
79
+ <div className="rounded-lg border bg-muted/50 p-4">
80
+ <div className="flex items-center gap-2 mb-2">
81
+ <Terminal className="h-4 w-4 text-muted-foreground" />
82
+ <span className="text-xs font-medium">Status</span>
83
+ </div>
84
+ <div className="flex items-center gap-2">
85
+ <span className="relative flex h-2 w-2">
86
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
87
+ <span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
88
+ </span>
89
+ <span className="text-xs text-muted-foreground">System Online</span>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ )
95
+ }
ui/components/ui/badge.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ui/components/ui/badge.tsx
2
+ import * as React from "react"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const badgeVariants = cva(
8
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
14
+ secondary:
15
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
16
+ destructive:
17
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
18
+ outline: "text-foreground",
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ variant: "default",
23
+ },
24
+ }
25
+ )
26
+
27
+ export interface BadgeProps
28
+ extends React.HTMLAttributes<HTMLDivElement>,
29
+ VariantProps<typeof badgeVariants> {}
30
+
31
+ function Badge({ className, variant, ...props }: BadgeProps) {
32
+ return (
33
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
34
+ )
35
+ }
36
+
37
+ export { Badge, badgeVariants }
ui/components/ui/button.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ui/components/ui/button.tsx
2
+ import * as React from "react"
3
+ import { Slot } from "@radix-ui/react-slot"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const buttonVariants = cva(
9
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default:
14
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
15
+ destructive:
16
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
17
+ outline:
18
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
19
+ secondary:
20
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
21
+ ghost: "hover:bg-accent hover:text-accent-foreground",
22
+ link: "text-primary underline-offset-4 hover:underline",
23
+ },
24
+ size: {
25
+ default: "h-9 px-4 py-2",
26
+ sm: "h-8 rounded-md px-3 text-xs",
27
+ lg: "h-10 rounded-md px-8",
28
+ icon: "h-9 w-9",
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default",
34
+ },
35
+ }
36
+ )
37
+
38
+ export interface ButtonProps
39
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
40
+ VariantProps<typeof buttonVariants> {
41
+ asChild?: boolean
42
+ }
43
+
44
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
45
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
46
+ const Comp = asChild ? Slot : "button"
47
+ return (
48
+ <Comp
49
+ className={cn(buttonVariants({ variant, size, className }))}
50
+ ref={ref}
51
+ {...props}
52
+ />
53
+ )
54
+ }
55
+ )
56
+ Button.displayName = "Button"
57
+
58
+ export { Button, buttonVariants }
ui/components/ui/card.tsx ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ui/components/ui/card.tsx
2
+ import * as React from "react"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Card = React.forwardRef<
7
+ HTMLDivElement,
8
+ React.HTMLAttributes<HTMLDivElement>
9
+ >(({ className, ...props }, ref) => (
10
+ <div
11
+ ref={ref}
12
+ className={cn(
13
+ "rounded-xl border bg-card text-card-foreground shadow",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ ))
19
+ Card.displayName = "Card"
20
+
21
+ const CardHeader = React.forwardRef<
22
+ HTMLDivElement,
23
+ React.HTMLAttributes<HTMLDivElement>
24
+ >(({ className, ...props }, ref) => (
25
+ <div
26
+ ref={ref}
27
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
28
+ {...props}
29
+ />
30
+ ))
31
+ CardHeader.displayName = "CardHeader"
32
+
33
+ const CardTitle = React.forwardRef<
34
+ HTMLDivElement,
35
+ React.HTMLAttributes<HTMLDivElement>
36
+ >(({ className, ...props }, ref) => (
37
+ <div
38
+ ref={ref}
39
+ className={cn("font-semibold leading-none tracking-tight", className)}
40
+ {...props}
41
+ />
42
+ ))
43
+ CardTitle.displayName = "CardTitle"
44
+
45
+ const CardDescription = React.forwardRef<
46
+ HTMLDivElement,
47
+ React.HTMLAttributes<HTMLDivElement>
48
+ >(({ className, ...props }, ref) => (
49
+ <div
50
+ ref={ref}
51
+ className={cn("text-sm text-muted-foreground", className)}
52
+ {...props}
53
+ />
54
+ ))
55
+ CardDescription.displayName = "CardDescription"
56
+
57
+ const CardContent = React.forwardRef<
58
+ HTMLDivElement,
59
+ React.HTMLAttributes<HTMLDivElement>
60
+ >(({ className, ...props }, ref) => (
61
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
62
+ ))
63
+ CardContent.displayName = "CardContent"
64
+
65
+ const CardFooter = React.forwardRef<
66
+ HTMLDivElement,
67
+ React.HTMLAttributes<HTMLDivElement>
68
+ >(({ className, ...props }, ref) => (
69
+ <div
70
+ ref={ref}
71
+ className={cn("flex items-center p-6 pt-0", className)}
72
+ {...props}
73
+ />
74
+ ))
75
+ CardFooter.displayName = "CardFooter"
76
+
77
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
ui/components/ui/input.tsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ export { Input }
ui/components/ui/label.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Label({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
12
+ return (
13
+ <LabelPrimitive.Root
14
+ data-slot="label"
15
+ className={cn(
16
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ export { Label }
ui/components/ui/select.tsx ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function Select({
10
+ ...props
11
+ }: React.ComponentProps<typeof SelectPrimitive.Root>) {
12
+ return <SelectPrimitive.Root data-slot="select" {...props} />
13
+ }
14
+
15
+ function SelectGroup({
16
+ ...props
17
+ }: React.ComponentProps<typeof SelectPrimitive.Group>) {
18
+ return <SelectPrimitive.Group data-slot="select-group" {...props} />
19
+ }
20
+
21
+ function SelectValue({
22
+ ...props
23
+ }: React.ComponentProps<typeof SelectPrimitive.Value>) {
24
+ return <SelectPrimitive.Value data-slot="select-value" {...props} />
25
+ }
26
+
27
+ function SelectTrigger({
28
+ className,
29
+ size = "default",
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
33
+ size?: "sm" | "default"
34
+ }) {
35
+ return (
36
+ <SelectPrimitive.Trigger
37
+ data-slot="select-trigger"
38
+ data-size={size}
39
+ className={cn(
40
+ "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
41
+ className
42
+ )}
43
+ {...props}
44
+ >
45
+ {children}
46
+ <SelectPrimitive.Icon asChild>
47
+ <ChevronDownIcon className="size-4 opacity-50" />
48
+ </SelectPrimitive.Icon>
49
+ </SelectPrimitive.Trigger>
50
+ )
51
+ }
52
+
53
+ function SelectContent({
54
+ className,
55
+ children,
56
+ position = "item-aligned",
57
+ align = "center",
58
+ ...props
59
+ }: React.ComponentProps<typeof SelectPrimitive.Content>) {
60
+ return (
61
+ <SelectPrimitive.Portal>
62
+ <SelectPrimitive.Content
63
+ data-slot="select-content"
64
+ className={cn(
65
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
66
+ position === "popper" &&
67
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
68
+ className
69
+ )}
70
+ position={position}
71
+ align={align}
72
+ {...props}
73
+ >
74
+ <SelectScrollUpButton />
75
+ <SelectPrimitive.Viewport
76
+ className={cn(
77
+ "p-1",
78
+ position === "popper" &&
79
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
80
+ )}
81
+ >
82
+ {children}
83
+ </SelectPrimitive.Viewport>
84
+ <SelectScrollDownButton />
85
+ </SelectPrimitive.Content>
86
+ </SelectPrimitive.Portal>
87
+ )
88
+ }
89
+
90
+ function SelectLabel({
91
+ className,
92
+ ...props
93
+ }: React.ComponentProps<typeof SelectPrimitive.Label>) {
94
+ return (
95
+ <SelectPrimitive.Label
96
+ data-slot="select-label"
97
+ className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
98
+ {...props}
99
+ />
100
+ )
101
+ }
102
+
103
+ function SelectItem({
104
+ className,
105
+ children,
106
+ ...props
107
+ }: React.ComponentProps<typeof SelectPrimitive.Item>) {
108
+ return (
109
+ <SelectPrimitive.Item
110
+ data-slot="select-item"
111
+ className={cn(
112
+ "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
113
+ className
114
+ )}
115
+ {...props}
116
+ >
117
+ <span
118
+ data-slot="select-item-indicator"
119
+ className="absolute right-2 flex size-3.5 items-center justify-center"
120
+ >
121
+ <SelectPrimitive.ItemIndicator>
122
+ <CheckIcon className="size-4" />
123
+ </SelectPrimitive.ItemIndicator>
124
+ </span>
125
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
126
+ </SelectPrimitive.Item>
127
+ )
128
+ }
129
+
130
+ function SelectSeparator({
131
+ className,
132
+ ...props
133
+ }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
134
+ return (
135
+ <SelectPrimitive.Separator
136
+ data-slot="select-separator"
137
+ className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
138
+ {...props}
139
+ />
140
+ )
141
+ }
142
+
143
+ function SelectScrollUpButton({
144
+ className,
145
+ ...props
146
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
147
+ return (
148
+ <SelectPrimitive.ScrollUpButton
149
+ data-slot="select-scroll-up-button"
150
+ className={cn(
151
+ "flex cursor-default items-center justify-center py-1",
152
+ className
153
+ )}
154
+ {...props}
155
+ >
156
+ <ChevronUpIcon className="size-4" />
157
+ </SelectPrimitive.ScrollUpButton>
158
+ )
159
+ }
160
+
161
+ function SelectScrollDownButton({
162
+ className,
163
+ ...props
164
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
165
+ return (
166
+ <SelectPrimitive.ScrollDownButton
167
+ data-slot="select-scroll-down-button"
168
+ className={cn(
169
+ "flex cursor-default items-center justify-center py-1",
170
+ className
171
+ )}
172
+ {...props}
173
+ >
174
+ <ChevronDownIcon className="size-4" />
175
+ </SelectPrimitive.ScrollDownButton>
176
+ )
177
+ }
178
+
179
+ export {
180
+ Select,
181
+ SelectContent,
182
+ SelectGroup,
183
+ SelectItem,
184
+ SelectLabel,
185
+ SelectScrollDownButton,
186
+ SelectScrollUpButton,
187
+ SelectSeparator,
188
+ SelectTrigger,
189
+ SelectValue,
190
+ }
ui/components/ui/separator.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Separator({
9
+ className,
10
+ orientation = "horizontal",
11
+ decorative = true,
12
+ ...props
13
+ }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
14
+ return (
15
+ <SeparatorPrimitive.Root
16
+ data-slot="separator"
17
+ decorative={decorative}
18
+ orientation={orientation}
19
+ className={cn(
20
+ "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ export { Separator }
ui/components/ui/slider.tsx ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SliderPrimitive from "@radix-ui/react-slider"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Slider({
9
+ className,
10
+ defaultValue,
11
+ value,
12
+ min = 0,
13
+ max = 100,
14
+ ...props
15
+ }: React.ComponentProps<typeof SliderPrimitive.Root>) {
16
+ const _values = React.useMemo(
17
+ () =>
18
+ Array.isArray(value)
19
+ ? value
20
+ : Array.isArray(defaultValue)
21
+ ? defaultValue
22
+ : [min, max],
23
+ [value, defaultValue, min, max]
24
+ )
25
+
26
+ return (
27
+ <SliderPrimitive.Root
28
+ data-slot="slider"
29
+ defaultValue={defaultValue}
30
+ value={value}
31
+ min={min}
32
+ max={max}
33
+ className={cn(
34
+ "relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
35
+ className
36
+ )}
37
+ {...props}
38
+ >
39
+ <SliderPrimitive.Track
40
+ data-slot="slider-track"
41
+ className={cn(
42
+ "bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
43
+ )}
44
+ >
45
+ <SliderPrimitive.Range
46
+ data-slot="slider-range"
47
+ className={cn(
48
+ "bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
49
+ )}
50
+ />
51
+ </SliderPrimitive.Track>
52
+ {Array.from({ length: _values.length }, (_, index) => (
53
+ <SliderPrimitive.Thumb
54
+ data-slot="slider-thumb"
55
+ key={index}
56
+ className="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
57
+ />
58
+ ))}
59
+ </SliderPrimitive.Root>
60
+ )
61
+ }
62
+
63
+ export { Slider }
ui/components/ui/switch.tsx ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SwitchPrimitive from "@radix-ui/react-switch"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Switch({
9
+ className,
10
+ size = "default",
11
+ ...props
12
+ }: React.ComponentProps<typeof SwitchPrimitive.Root> & {
13
+ size?: "sm" | "default"
14
+ }) {
15
+ return (
16
+ <SwitchPrimitive.Root
17
+ data-slot="switch"
18
+ data-size={size}
19
+ className={cn(
20
+ "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ <SwitchPrimitive.Thumb
26
+ data-slot="switch-thumb"
27
+ className={cn(
28
+ "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
29
+ )}
30
+ />
31
+ </SwitchPrimitive.Root>
32
+ )
33
+ }
34
+
35
+ export { Switch }
ui/components/ui/table.tsx ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
8
+ return (
9
+ <div
10
+ data-slot="table-container"
11
+ className="relative w-full overflow-x-auto"
12
+ >
13
+ <table
14
+ data-slot="table"
15
+ className={cn("w-full caption-bottom text-sm", className)}
16
+ {...props}
17
+ />
18
+ </div>
19
+ )
20
+ }
21
+
22
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
23
+ return (
24
+ <thead
25
+ data-slot="table-header"
26
+ className={cn("[&_tr]:border-b", className)}
27
+ {...props}
28
+ />
29
+ )
30
+ }
31
+
32
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
33
+ return (
34
+ <tbody
35
+ data-slot="table-body"
36
+ className={cn("[&_tr:last-child]:border-0", className)}
37
+ {...props}
38
+ />
39
+ )
40
+ }
41
+
42
+ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
43
+ return (
44
+ <tfoot
45
+ data-slot="table-footer"
46
+ className={cn(
47
+ "bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+
55
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
56
+ return (
57
+ <tr
58
+ data-slot="table-row"
59
+ className={cn(
60
+ "hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
69
+ return (
70
+ <th
71
+ data-slot="table-head"
72
+ className={cn(
73
+ "text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
74
+ className
75
+ )}
76
+ {...props}
77
+ />
78
+ )
79
+ }
80
+
81
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
82
+ return (
83
+ <td
84
+ data-slot="table-cell"
85
+ className={cn(
86
+ "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
87
+ className
88
+ )}
89
+ {...props}
90
+ />
91
+ )
92
+ }
93
+
94
+ function TableCaption({
95
+ className,
96
+ ...props
97
+ }: React.ComponentProps<"caption">) {
98
+ return (
99
+ <caption
100
+ data-slot="table-caption"
101
+ className={cn("text-muted-foreground mt-4 text-sm", className)}
102
+ {...props}
103
+ />
104
+ )
105
+ }
106
+
107
+ export {
108
+ Table,
109
+ TableHeader,
110
+ TableBody,
111
+ TableFooter,
112
+ TableHead,
113
+ TableRow,
114
+ TableCell,
115
+ TableCaption,
116
+ }
ui/components/ui/tabs.tsx ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function Tabs({
10
+ className,
11
+ orientation = "horizontal",
12
+ ...props
13
+ }: React.ComponentProps<typeof TabsPrimitive.Root>) {
14
+ return (
15
+ <TabsPrimitive.Root
16
+ data-slot="tabs"
17
+ data-orientation={orientation}
18
+ orientation={orientation}
19
+ className={cn(
20
+ "group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ const tabsListVariants = cva(
29
+ "rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
30
+ {
31
+ variants: {
32
+ variant: {
33
+ default: "bg-muted",
34
+ line: "gap-1 bg-transparent",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ },
40
+ }
41
+ )
42
+
43
+ function TabsList({
44
+ className,
45
+ variant = "default",
46
+ ...props
47
+ }: React.ComponentProps<typeof TabsPrimitive.List> &
48
+ VariantProps<typeof tabsListVariants>) {
49
+ return (
50
+ <TabsPrimitive.List
51
+ data-slot="tabs-list"
52
+ data-variant={variant}
53
+ className={cn(tabsListVariants({ variant }), className)}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ function TabsTrigger({
60
+ className,
61
+ ...props
62
+ }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
63
+ return (
64
+ <TabsPrimitive.Trigger
65
+ data-slot="tabs-trigger"
66
+ className={cn(
67
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
68
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
69
+ "data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
70
+ "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
71
+ className
72
+ )}
73
+ {...props}
74
+ />
75
+ )
76
+ }
77
+
78
+ function TabsContent({
79
+ className,
80
+ ...props
81
+ }: React.ComponentProps<typeof TabsPrimitive.Content>) {
82
+ return (
83
+ <TabsPrimitive.Content
84
+ data-slot="tabs-content"
85
+ className={cn("flex-1 outline-none", className)}
86
+ {...props}
87
+ />
88
+ )
89
+ }
90
+
91
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
ui/components/ui/textarea.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
6
+ export interface TextareaProps
7
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
8
+
9
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
10
+ ({ className, ...props }, ref) => {
11
+ return (
12
+ <textarea
13
+ className={cn(
14
+ "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
15
+ className
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+ )
23
+ Textarea.displayName = "Textarea"
24
+
25
+ export { Textarea }
ui/package.json CHANGED
@@ -6,26 +6,37 @@
6
  "dev": "next dev",
7
  "build": "next build",
8
  "start": "next start",
9
- "lint": "eslint"
 
10
  },
11
  "dependencies": {
 
 
 
 
 
 
 
12
  "class-variance-authority": "^0.7.1",
13
  "clsx": "^2.1.1",
 
14
  "lucide-react": "^0.563.0",
15
- "next": "16.1.4",
16
- "react": "19.2.3",
17
- "react-dom": "19.2.3",
 
18
  "tailwind-merge": "^3.4.0"
19
  },
20
  "devDependencies": {
21
- "@tailwindcss/postcss": "^4",
22
- "@types/node": "^20",
23
- "@types/react": "^19",
24
- "@types/react-dom": "^19",
25
- "eslint": "^9",
26
- "eslint-config-next": "16.1.4",
27
- "tailwindcss": "^4",
 
28
  "tw-animate-css": "^1.4.0",
29
- "typescript": "^5"
30
  }
31
  }
 
6
  "dev": "next dev",
7
  "build": "next build",
8
  "start": "next start",
9
+ "lint": "eslint .",
10
+ "type-check": "tsc --noEmit"
11
  },
12
  "dependencies": {
13
+ "@radix-ui/react-label": "^2.1.8",
14
+ "@radix-ui/react-select": "^2.2.6",
15
+ "@radix-ui/react-separator": "^1.1.8",
16
+ "@radix-ui/react-slider": "^1.3.6",
17
+ "@radix-ui/react-slot": "^1.2.4",
18
+ "@radix-ui/react-switch": "^1.2.6",
19
+ "@radix-ui/react-tabs": "^1.1.13",
20
  "class-variance-authority": "^0.7.1",
21
  "clsx": "^2.1.1",
22
+ "framer-motion": "^12.29.2",
23
  "lucide-react": "^0.563.0",
24
+ "next": "^16.1.4",
25
+ "react": "^19.2.3",
26
+ "react-dom": "^19.2.3",
27
+ "recharts": "^3.7.0",
28
  "tailwind-merge": "^3.4.0"
29
  },
30
  "devDependencies": {
31
+ "@tailwindcss/postcss": "^4.1.18",
32
+ "@types/node": "^25.0.10",
33
+ "@types/react": "^19.2.9",
34
+ "@types/react-dom": "^19.2.3",
35
+ "eslint": "^9.39.2",
36
+ "eslint-config-next": "^16.1.4",
37
+ "shadcn": "^3.7.0",
38
+ "tailwindcss": "^4.1.18",
39
  "tw-animate-css": "^1.4.0",
40
+ "typescript": "^5.9.3"
41
  }
42
  }
ui/pnpm-lock.yaml CHANGED
The diff for this file is too large to render. See raw diff
 
ui/pnpm-workspace.yaml CHANGED
@@ -1,5 +1,2 @@
1
  packages:
2
  - .
3
- ignoredBuiltDependencies:
4
- - sharp
5
- - unrs-resolver
 
1
  packages:
2
  - .