flashstudio 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: flashstudio
3
+ Version: 0.1.0
4
+ Summary: Interactive Training & Inference UI for FlashDet — runs on Google Colab
5
+ Author: Gaurav14cs17
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/FlashVision/FlashStudio
8
+ Project-URL: Repository, https://github.com/FlashVision/FlashStudio
9
+ Keywords: object-detection,flashdet,training,inference,ui,colab
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: streamlit>=1.28.0
18
+ Requires-Dist: plotly>=5.0
19
+ Requires-Dist: pandas>=1.5
20
+ Requires-Dist: pillow>=9.0
21
+ Requires-Dist: numpy>=1.21
22
+ Provides-Extra: full
23
+ Requires-Dist: pyngrok>=6.0; extra == "full"
24
+ Requires-Dist: torch>=2.0; extra == "full"
25
+ Requires-Dist: opencv-python>=4.5; extra == "full"
26
+ Provides-Extra: colab
27
+ Requires-Dist: pyngrok>=6.0; extra == "colab"
28
+ Provides-Extra: dev
29
+ Requires-Dist: ruff; extra == "dev"
30
+ Requires-Dist: pytest; extra == "dev"
31
+
32
+ # ⚡ FlashStudio
33
+
34
+ **Interactive Training & Inference UI for FlashDet** — runs locally or on Google Colab with a Streamlit interface.
35
+
36
+ <p align="center">
37
+ <img src="docs/mockups/flashstudio_streamlit_mockup.png" width="800" alt="FlashStudio UI"/>
38
+ </p>
39
+
40
+ ## Features
41
+
42
+ - đŸ‹ī¸ **Training Dashboard** — Real-time monitoring with live loss curves, visualizations, GT verification
43
+ - 🧠 **Model Config** — All 6 FlashDet sizes + YOLOv8/v9/v10/v11/YOLOX with accurate params
44
+ - 🔍 **Inference Pipeline** — 4-step wizard: Model → Data → Zone → Run (17 solutions, 6 trackers)
45
+ - 📤 **Export** — ONNX export with FP16 auto-generated weights
46
+ - đŸ“Ļ **Data** — Native `flashdet download` datasets + custom upload
47
+ - 🚀 **Colab Support** — ngrok tunneling for remote access
48
+
49
+ ## Quick Start
50
+
51
+ ### Install from GitHub
52
+
53
+ ```bash
54
+ pip install git+https://github.com/FlashVision/FlashStudio.git
55
+ ```
56
+
57
+ ### Install locally (development)
58
+
59
+ ```bash
60
+ git clone https://github.com/FlashVision/FlashStudio.git
61
+ cd FlashStudio
62
+ pip install -e .
63
+ ```
64
+
65
+ ### Install with all dependencies (FlashDet + PyTorch)
66
+
67
+ ```bash
68
+ pip install "flashstudio[full] @ git+https://github.com/FlashVision/FlashStudio.git"
69
+ ```
70
+
71
+ ### Run
72
+
73
+ ```bash
74
+ # CLI
75
+ flashstudio --port 8501
76
+
77
+ # Or directly
78
+ streamlit run flashstudio/app.py
79
+ ```
80
+
81
+ ### Python API (for Colab)
82
+
83
+ ```python
84
+ from flashstudio import launch
85
+ launch() # Opens ngrok tunnel in Colab, localhost otherwise
86
+ ```
87
+
88
+ ## Google Colab
89
+
90
+ | Notebook | Description | Link |
91
+ |----------|-------------|------|
92
+ | Training | Train FlashDet models | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/FlashVision/FlashStudio/blob/main/notebooks/FlashStudio_Train.ipynb) |
93
+ | Inference | Run detection on images/video | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/FlashVision/FlashStudio/blob/main/notebooks/FlashStudio_Inference.ipynb) |
94
+
95
+ ## Architecture
96
+
97
+ ```
98
+ FlashStudio/
99
+ ├── flashstudio/
100
+ │ ├── __init__.py # Package init + launch() export
101
+ │ ├── app.py # Main Streamlit app (wizard flow)
102
+ │ ├── launcher.py # Colab/local launcher with ngrok
103
+ │ ├── cli.py # CLI entrypoint
104
+ │ ├── pages/
105
+ │ │ ├── dashboard.py # Overview + recent runs
106
+ │ │ ├── data.py # Dataset upload/download
107
+ │ │ ├── model.py # Architecture & hyperparameter config
108
+ │ │ ├── training.py # Training monitor (reads real workspace)
109
+ │ │ ├── export.py # ONNX export
110
+ │ │ └── inference.py # 4-step inference pipeline
111
+ │ ├── components/
112
+ │ │ ├── sidebar.py # Navigation sidebar
113
+ │ │ ├── styles.py # Custom CSS
114
+ │ │ └── wizard.py # Step indicator & navigation
115
+ │ └── utils/
116
+ │ └── device.py # GPU/environment detection
117
+ ├── notebooks/
118
+ │ ├── FlashStudio_Train.ipynb
119
+ │ └── FlashStudio_Inference.ipynb
120
+ ├── .streamlit/config.toml
121
+ ├── pyproject.toml
122
+ └── README.md
123
+ ```
124
+
125
+ ## Requirements
126
+
127
+ - Python >= 3.9
128
+ - FlashDet (install separately: `pip install git+https://github.com/FlashVision/FlashDet.git`)
129
+ - GPU recommended for training (T4 or better)
130
+
131
+ ## License
132
+
133
+ Apache-2.0
@@ -0,0 +1,102 @@
1
+ # ⚡ FlashStudio
2
+
3
+ **Interactive Training & Inference UI for FlashDet** — runs locally or on Google Colab with a Streamlit interface.
4
+
5
+ <p align="center">
6
+ <img src="docs/mockups/flashstudio_streamlit_mockup.png" width="800" alt="FlashStudio UI"/>
7
+ </p>
8
+
9
+ ## Features
10
+
11
+ - đŸ‹ī¸ **Training Dashboard** — Real-time monitoring with live loss curves, visualizations, GT verification
12
+ - 🧠 **Model Config** — All 6 FlashDet sizes + YOLOv8/v9/v10/v11/YOLOX with accurate params
13
+ - 🔍 **Inference Pipeline** — 4-step wizard: Model → Data → Zone → Run (17 solutions, 6 trackers)
14
+ - 📤 **Export** — ONNX export with FP16 auto-generated weights
15
+ - đŸ“Ļ **Data** — Native `flashdet download` datasets + custom upload
16
+ - 🚀 **Colab Support** — ngrok tunneling for remote access
17
+
18
+ ## Quick Start
19
+
20
+ ### Install from GitHub
21
+
22
+ ```bash
23
+ pip install git+https://github.com/FlashVision/FlashStudio.git
24
+ ```
25
+
26
+ ### Install locally (development)
27
+
28
+ ```bash
29
+ git clone https://github.com/FlashVision/FlashStudio.git
30
+ cd FlashStudio
31
+ pip install -e .
32
+ ```
33
+
34
+ ### Install with all dependencies (FlashDet + PyTorch)
35
+
36
+ ```bash
37
+ pip install "flashstudio[full] @ git+https://github.com/FlashVision/FlashStudio.git"
38
+ ```
39
+
40
+ ### Run
41
+
42
+ ```bash
43
+ # CLI
44
+ flashstudio --port 8501
45
+
46
+ # Or directly
47
+ streamlit run flashstudio/app.py
48
+ ```
49
+
50
+ ### Python API (for Colab)
51
+
52
+ ```python
53
+ from flashstudio import launch
54
+ launch() # Opens ngrok tunnel in Colab, localhost otherwise
55
+ ```
56
+
57
+ ## Google Colab
58
+
59
+ | Notebook | Description | Link |
60
+ |----------|-------------|------|
61
+ | Training | Train FlashDet models | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/FlashVision/FlashStudio/blob/main/notebooks/FlashStudio_Train.ipynb) |
62
+ | Inference | Run detection on images/video | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/FlashVision/FlashStudio/blob/main/notebooks/FlashStudio_Inference.ipynb) |
63
+
64
+ ## Architecture
65
+
66
+ ```
67
+ FlashStudio/
68
+ ├── flashstudio/
69
+ │ ├── __init__.py # Package init + launch() export
70
+ │ ├── app.py # Main Streamlit app (wizard flow)
71
+ │ ├── launcher.py # Colab/local launcher with ngrok
72
+ │ ├── cli.py # CLI entrypoint
73
+ │ ├── pages/
74
+ │ │ ├── dashboard.py # Overview + recent runs
75
+ │ │ ├── data.py # Dataset upload/download
76
+ │ │ ├── model.py # Architecture & hyperparameter config
77
+ │ │ ├── training.py # Training monitor (reads real workspace)
78
+ │ │ ├── export.py # ONNX export
79
+ │ │ └── inference.py # 4-step inference pipeline
80
+ │ ├── components/
81
+ │ │ ├── sidebar.py # Navigation sidebar
82
+ │ │ ├── styles.py # Custom CSS
83
+ │ │ └── wizard.py # Step indicator & navigation
84
+ │ └── utils/
85
+ │ └── device.py # GPU/environment detection
86
+ ├── notebooks/
87
+ │ ├── FlashStudio_Train.ipynb
88
+ │ └── FlashStudio_Inference.ipynb
89
+ ├── .streamlit/config.toml
90
+ ├── pyproject.toml
91
+ └── README.md
92
+ ```
93
+
94
+ ## Requirements
95
+
96
+ - Python >= 3.9
97
+ - FlashDet (install separately: `pip install git+https://github.com/FlashVision/FlashDet.git`)
98
+ - GPU recommended for training (T4 or better)
99
+
100
+ ## License
101
+
102
+ Apache-2.0
@@ -0,0 +1,5 @@
1
+ """FlashStudio — Interactive Training & Inference UI for FlashDet."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from flashstudio.launcher import launch # noqa: F401
@@ -0,0 +1,64 @@
1
+ """FlashStudio — Main Streamlit Application."""
2
+
3
+ import streamlit as st
4
+
5
+ st.set_page_config(
6
+ page_title="FlashStudio",
7
+ page_icon="⚡",
8
+ layout="wide",
9
+ initial_sidebar_state="expanded",
10
+ )
11
+
12
+ from flashstudio.components.styles import inject_custom_css # noqa: E402
13
+ from flashstudio.components.sidebar import render_sidebar # noqa: E402
14
+ from flashstudio.components.wizard import render_step_indicator, render_navigation # noqa: E402
15
+ from flashstudio.pages.dashboard import render_dashboard # noqa: E402
16
+ from flashstudio.pages.data import render_data_page # noqa: E402
17
+ from flashstudio.pages.model import render_model_page # noqa: E402
18
+ from flashstudio.pages.training import render_training_page # noqa: E402
19
+ from flashstudio.pages.export import render_export_page # noqa: E402
20
+ from flashstudio.pages.inference import render_inference_page # noqa: E402
21
+
22
+ STEPS = [
23
+ {"id": "dashboard", "label": "Dashboard", "icon": "🏠"},
24
+ {"id": "data", "label": "Data", "icon": "đŸ“Ļ"},
25
+ {"id": "model", "label": "Model", "icon": "🧠"},
26
+ {"id": "training", "label": "Training", "icon": "đŸ‹ī¸"},
27
+ {"id": "export", "label": "Export", "icon": "📤"},
28
+ {"id": "inference", "label": "Inference", "icon": "🔍"},
29
+ ]
30
+
31
+ PAGE_RENDERERS = {
32
+ "dashboard": render_dashboard,
33
+ "data": render_data_page,
34
+ "model": render_model_page,
35
+ "training": render_training_page,
36
+ "export": render_export_page,
37
+ "inference": render_inference_page,
38
+ }
39
+
40
+
41
+ def main():
42
+ import traceback as _tb
43
+
44
+ inject_custom_css()
45
+
46
+ if "current_step" not in st.session_state:
47
+ st.session_state["current_step"] = 0
48
+
49
+ render_sidebar()
50
+ render_step_indicator(STEPS, st.session_state["current_step"])
51
+
52
+ current = STEPS[st.session_state["current_step"]]
53
+
54
+ try:
55
+ PAGE_RENDERERS[current["id"]]()
56
+ except Exception as _e:
57
+ st.error(f"Error: {_e}")
58
+ st.code(_tb.format_exc())
59
+
60
+ render_navigation(STEPS, st.session_state["current_step"])
61
+
62
+
63
+ if __name__ == "__main__":
64
+ main()
@@ -0,0 +1,18 @@
1
+ """FlashStudio CLI entrypoint."""
2
+
3
+ import argparse
4
+
5
+
6
+ def main():
7
+ parser = argparse.ArgumentParser(description="FlashStudio — Training & Inference UI for FlashDet")
8
+ parser.add_argument("--port", type=int, default=8501, help="Port to serve on")
9
+ parser.add_argument("--no-share", action="store_true", help="Disable ngrok sharing in Colab")
10
+ parser.add_argument("--ngrok-token", type=str, default=None, help="ngrok auth token")
11
+ args = parser.parse_args()
12
+
13
+ from flashstudio.launcher import launch
14
+ launch(port=args.port, share=not args.no_share, ngrok_token=args.ngrok_token)
15
+
16
+
17
+ if __name__ == "__main__":
18
+ main()
File without changes
@@ -0,0 +1,45 @@
1
+ """FlashStudio sidebar — compact, working navigation."""
2
+
3
+ import streamlit as st
4
+ from flashstudio.utils.device import get_gpu_info, get_colab_runtime_type
5
+
6
+ PAGES = ["🏠 Dashboard", "đŸ“Ļ Data", "🧠 Model", "đŸ‹ī¸ Training", "📤 Export", "🔍 Inference"]
7
+
8
+
9
+ def render_sidebar():
10
+ """Render sidebar with reliable button navigation."""
11
+ with st.sidebar:
12
+ # Logo
13
+ st.markdown(
14
+ "<div style='text-align:center; padding:0.5rem 0;'>"
15
+ "<span style='font-size:1.6rem;'>⚡</span> "
16
+ "<b style='font-size:1.1rem;'>FlashStudio</b>"
17
+ "</div>",
18
+ unsafe_allow_html=True,
19
+ )
20
+
21
+ st.divider()
22
+
23
+ # Navigation buttons
24
+ current = st.session_state.get("current_step", 0)
25
+ for i, page in enumerate(PAGES):
26
+ btn_type = "primary" if i == current else "secondary"
27
+ if st.button(page, key=f"sb_{i}", type=btn_type, use_container_width=True):
28
+ st.session_state["current_step"] = i
29
+ st.rerun()
30
+
31
+ st.divider()
32
+
33
+ # Compact status
34
+ st.caption(f"Model: {st.session_state.get('model_arch', 'FlashDet-Nano')}")
35
+ st.caption(f"Dataset: {st.session_state.get('dataset_name', '—')}")
36
+ st.caption(f"Status: {st.session_state.get('training_status', 'Not started')}")
37
+
38
+ st.divider()
39
+
40
+ # Environment
41
+ gpu = get_gpu_info()
42
+ if gpu["available"]:
43
+ st.caption(f"đŸ–Ĩī¸ {gpu['name']}")
44
+ else:
45
+ st.caption("đŸ’ģ CPU Mode")
@@ -0,0 +1,273 @@
1
+ """FlashStudio — Custom CSS Styles matching the professional mockup (light theme)."""
2
+
3
+ import streamlit as st
4
+
5
+
6
+ def inject_custom_css():
7
+ """Inject custom CSS to match the professional mockup design — light theme, dark sidebar."""
8
+ st.markdown("""
9
+ <style>
10
+ /* ═══════ Global ═══════ */
11
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
12
+
13
+ .stApp {
14
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
15
+ }
16
+
17
+ /* ═══════ Sidebar ═══════ */
18
+ section[data-testid="stSidebar"] {
19
+ background: #FAFBFC !important;
20
+ border-right: 1px solid #E8E8EF;
21
+ width: 240px !important;
22
+ }
23
+
24
+ section[data-testid="stSidebar"] > div {
25
+ width: 240px !important;
26
+ padding: 1rem 0.8rem !important;
27
+ }
28
+
29
+ section[data-testid="stSidebar"] hr {
30
+ border-color: #E8E8EF !important;
31
+ margin: 0.4rem 0 !important;
32
+ }
33
+
34
+ section[data-testid="stSidebar"] .stButton > button {
35
+ font-size: 0.82rem !important;
36
+ padding: 0.4rem 0.7rem !important;
37
+ border-radius: 6px !important;
38
+ text-align: left !important;
39
+ justify-content: flex-start !important;
40
+ min-height: 0 !important;
41
+ height: auto !important;
42
+ }
43
+
44
+ section[data-testid="stSidebar"] .stButton > button[kind="secondary"] {
45
+ background: transparent !important;
46
+ border: none !important;
47
+ color: #374151 !important;
48
+ }
49
+
50
+ section[data-testid="stSidebar"] .stButton > button[kind="secondary"]:hover {
51
+ background: #F5F3FF !important;
52
+ color: #7C3AED !important;
53
+ }
54
+
55
+ section[data-testid="stSidebar"] .stButton > button[kind="primary"] {
56
+ background: #F5F3FF !important;
57
+ border: 1px solid #EDE9FE !important;
58
+ color: #7C3AED !important;
59
+ font-weight: 600 !important;
60
+ }
61
+
62
+ /* Hide Streamlit's auto multipage nav */
63
+ [data-testid="stSidebarNav"] {
64
+ display: none !important;
65
+ }
66
+
67
+ /* ═══════ Main Content ═══════ */
68
+ .stApp > header {
69
+ background: transparent;
70
+ }
71
+
72
+ /* ═══════ Metric Cards ═══════ */
73
+ div[data-testid="stMetric"] {
74
+ background: #FFFFFF;
75
+ border: 1px solid #E8E8EF;
76
+ border-radius: 12px;
77
+ padding: 1rem 1.2rem;
78
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
79
+ transition: transform 0.2s, box-shadow 0.2s;
80
+ }
81
+
82
+ div[data-testid="stMetric"]:hover {
83
+ transform: translateY(-2px);
84
+ box-shadow: 0 4px 12px rgba(124, 58, 237, 0.08);
85
+ }
86
+
87
+ div[data-testid="stMetric"] label {
88
+ font-size: 0.75rem !important;
89
+ text-transform: uppercase;
90
+ letter-spacing: 0.05em;
91
+ color: #6B7280 !important;
92
+ font-weight: 500;
93
+ }
94
+
95
+ div[data-testid="stMetric"] div[data-testid="stMetricValue"] {
96
+ font-size: 1.6rem !important;
97
+ font-weight: 700;
98
+ color: #1A1A2E !important;
99
+ }
100
+
101
+ /* ═══════ Primary Buttons ═══════ */
102
+ .main .stButton > button[kind="primary"] {
103
+ background: #7C3AED !important;
104
+ border: none;
105
+ border-radius: 8px;
106
+ font-weight: 600;
107
+ color: #FFFFFF !important;
108
+ letter-spacing: 0.02em;
109
+ transition: all 0.2s;
110
+ }
111
+
112
+ .main .stButton > button[kind="primary"]:hover {
113
+ background: #6D28D9 !important;
114
+ box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3);
115
+ transform: translateY(-1px);
116
+ }
117
+
118
+ .main .stButton > button[kind="secondary"] {
119
+ border-radius: 8px;
120
+ border: 1px solid #E8E8EF !important;
121
+ font-weight: 500;
122
+ color: #1A1A2E !important;
123
+ }
124
+
125
+ .main .stButton > button[kind="secondary"]:hover {
126
+ border-color: #7C3AED !important;
127
+ color: #7C3AED !important;
128
+ }
129
+
130
+ /* ═══════ Containers / Cards ═══════ */
131
+ div[data-testid="stVerticalBlockBorderWrapper"] {
132
+ border-radius: 12px !important;
133
+ border-color: #E8E8EF !important;
134
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
135
+ }
136
+
137
+ /* ═══════ Step Indicator ═══════ */
138
+ .step-indicator {
139
+ display: flex;
140
+ justify-content: center;
141
+ gap: 0.3rem;
142
+ padding: 0.5rem 0.5rem;
143
+ margin-bottom: 1rem;
144
+ background: #FAFAFA;
145
+ border-radius: 8px;
146
+ border: 1px solid #F0F0F5;
147
+ }
148
+
149
+ .step-item {
150
+ text-align: center;
151
+ flex: 1;
152
+ position: relative;
153
+ }
154
+
155
+ .step-icon { font-size: 1.3rem; display: block; }
156
+ .step-label { font-size: 0.7rem; margin-top: 0.2rem; font-weight: 500; color: #6B7280; }
157
+ .step-bar { height: 3px; border-radius: 2px; margin-top: 0.4rem; }
158
+
159
+ .step-active .step-label { color: #7C3AED; font-weight: 700; }
160
+ .step-active .step-bar { background: #7C3AED; }
161
+ .step-done .step-bar { background: #10B981; }
162
+ .step-pending .step-bar { background: #E5E7EB; }
163
+ .step-pending .step-label { opacity: 0.5; }
164
+
165
+ /* ═══════ Progress Bar ═══════ */
166
+ .stProgress > div > div {
167
+ background: linear-gradient(90deg, #7C3AED, #A78BFA) !important;
168
+ border-radius: 4px;
169
+ }
170
+
171
+ /* ═══════ Tabs ═══════ */
172
+ .stTabs [data-baseweb="tab-list"] {
173
+ gap: 2rem;
174
+ border-bottom: 2px solid #F0F0F5;
175
+ }
176
+
177
+ .stTabs [data-baseweb="tab"] {
178
+ font-weight: 500;
179
+ color: #6B7280;
180
+ }
181
+
182
+ .stTabs [aria-selected="true"] {
183
+ color: #7C3AED !important;
184
+ font-weight: 600;
185
+ }
186
+
187
+ /* ═══════ Info Bar (bottom) ═══════ */
188
+ .info-bar {
189
+ background: #F5F3FF;
190
+ border: 1px solid #EDE9FE;
191
+ border-radius: 8px;
192
+ padding: 0.6rem 1.2rem;
193
+ text-align: center;
194
+ font-size: 0.8rem;
195
+ margin-top: 1.5rem;
196
+ color: #4C1D95;
197
+ }
198
+
199
+ .info-bar b {
200
+ color: #1A1A2E;
201
+ }
202
+
203
+ /* ═══════ File Uploader ═══════ */
204
+ [data-testid="stFileUploader"] section {
205
+ border-radius: 12px;
206
+ border: 2px dashed #D4D4D8 !important;
207
+ transition: border-color 0.2s;
208
+ }
209
+
210
+ [data-testid="stFileUploader"] section:hover {
211
+ border-color: #7C3AED !important;
212
+ }
213
+
214
+ /* ═══════ Sliders ═══════ */
215
+ .stSlider > div > div > div > div {
216
+ background: #7C3AED !important;
217
+ }
218
+
219
+ /* ═══════ Selectbox ═══════ */
220
+ .main .stSelectbox > div > div {
221
+ border-radius: 8px;
222
+ border-color: #E8E8EF;
223
+ }
224
+
225
+ /* ═══════ Dataframe ═══════ */
226
+ .stDataFrame {
227
+ border-radius: 8px;
228
+ overflow: hidden;
229
+ border: 1px solid #E8E8EF;
230
+ }
231
+
232
+ /* ═══════ Expander ═══════ */
233
+ .streamlit-expanderHeader {
234
+ font-weight: 600;
235
+ color: #1A1A2E;
236
+ }
237
+
238
+ /* ═══════ Download Buttons ═══════ */
239
+ .stDownloadButton > button[kind="primary"] {
240
+ background: #7C3AED !important;
241
+ color: white !important;
242
+ border-radius: 8px;
243
+ }
244
+
245
+ .stDownloadButton > button {
246
+ border-radius: 8px;
247
+ border: 1px solid #E8E8EF;
248
+ }
249
+ </style>
250
+ """, unsafe_allow_html=True)
251
+
252
+
253
+ def render_info_bar(items: dict):
254
+ """Render a professional info bar at the bottom (like mockup)."""
255
+ inner = " â€ĸ ".join(f"{k}: <b>{v}</b>" for k, v in items.items())
256
+ st.markdown(
257
+ f'<div class="info-bar">â„šī¸ {inner}</div>',
258
+ unsafe_allow_html=True,
259
+ )
260
+
261
+
262
+ def render_page_header(icon: str, title: str, subtitle: str):
263
+ """Render a professional page header with icon and subtitle."""
264
+ st.markdown(
265
+ f"""<div style="margin-bottom: 1.5rem;">
266
+ <h1 style="margin:0; display:flex; align-items:center; gap:0.6rem;
267
+ font-size:1.8rem; font-weight:700; color:#1A1A2E;">
268
+ <span style="font-size:2rem;">{icon}</span> {title}
269
+ </h1>
270
+ <p style="margin:0.4rem 0 0; color:#6B7280; font-size:0.95rem;">{subtitle}</p>
271
+ </div>""",
272
+ unsafe_allow_html=True,
273
+ )
@@ -0,0 +1,46 @@
1
+ """Step indicator and navigation wizard component."""
2
+
3
+ import streamlit as st
4
+
5
+
6
+ def render_step_indicator(steps: list, current_step: int):
7
+ """Render horizontal step progress indicator."""
8
+ items_html = ""
9
+ for i, step in enumerate(steps):
10
+ if i < current_step:
11
+ state_class = "step-done"
12
+ icon = "✅"
13
+ elif i == current_step:
14
+ state_class = "step-active"
15
+ icon = step["icon"]
16
+ else:
17
+ state_class = "step-pending"
18
+ icon = step["icon"]
19
+
20
+ items_html += f"""
21
+ <div class="step-item {state_class}">
22
+ <span class="step-icon">{icon}</span>
23
+ <div class="step-label">{step['label']}</div>
24
+ <div class="step-bar"></div>
25
+ </div>"""
26
+
27
+ st.markdown(f'<div class="step-indicator">{items_html}</div>', unsafe_allow_html=True)
28
+
29
+
30
+ def render_navigation(steps: list, current_step: int):
31
+ """Render back/next navigation buttons."""
32
+ col_back, col_spacer, col_next = st.columns([1, 3, 1])
33
+
34
+ with col_back:
35
+ if current_step > 0:
36
+ prev_label = f"← {steps[current_step - 1]['label']}"
37
+ if st.button(prev_label, key="wizard_back", use_container_width=True):
38
+ st.session_state["current_step"] = current_step - 1
39
+ st.rerun()
40
+
41
+ with col_next:
42
+ if current_step < len(steps) - 1:
43
+ next_label = f"{steps[current_step + 1]['label']} →"
44
+ if st.button(next_label, key="wizard_next", use_container_width=True, type="primary"):
45
+ st.session_state["current_step"] = current_step + 1
46
+ st.rerun()