flashstudio 0.1.0__py3-none-any.whl

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,5 @@
1
+ """FlashStudio — Interactive Training & Inference UI for FlashDet."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from flashstudio.launcher import launch # noqa: F401
flashstudio/app.py ADDED
@@ -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()
flashstudio/cli.py ADDED
@@ -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()
@@ -0,0 +1,73 @@
1
+ """Launch FlashStudio UI — works locally and inside Google Colab."""
2
+
3
+ import subprocess
4
+ import sys
5
+ import os
6
+ from pathlib import Path
7
+
8
+
9
+ def _is_colab() -> bool:
10
+ try:
11
+ import google.colab # noqa: F401
12
+ return True
13
+ except ImportError:
14
+ return False
15
+
16
+
17
+ def _get_app_path() -> str:
18
+ return str(Path(__file__).parent / "app.py")
19
+
20
+
21
+ def launch(port: int = 8501, share: bool = True, ngrok_token: str | None = None):
22
+ """Launch FlashStudio Streamlit app.
23
+
24
+ Args:
25
+ port: Port to run the Streamlit server on.
26
+ share: If True and running in Colab, creates a public ngrok tunnel.
27
+ ngrok_token: Optional ngrok auth token. If not provided, uses NGROK_TOKEN env var.
28
+ """
29
+ app_path = _get_app_path()
30
+
31
+ if _is_colab():
32
+ _launch_colab(app_path, port, share, ngrok_token)
33
+ else:
34
+ _launch_local(app_path, port)
35
+
36
+
37
+ def _launch_colab(app_path: str, port: int, share: bool, ngrok_token: str | None):
38
+ """Launch in Google Colab with ngrok tunnel."""
39
+ from pyngrok import ngrok, conf
40
+
41
+ token = ngrok_token or os.environ.get("NGROK_TOKEN", "")
42
+ if token:
43
+ conf.get_default().auth_token = token
44
+
45
+ proc = subprocess.Popen(
46
+ [sys.executable, "-m", "streamlit", "run", app_path,
47
+ "--server.port", str(port),
48
+ "--server.headless", "true",
49
+ "--server.enableCORS", "false",
50
+ "--server.enableXsrfProtection", "false"],
51
+ stdout=subprocess.PIPE,
52
+ stderr=subprocess.PIPE,
53
+ )
54
+
55
+ if share:
56
+ public_url = ngrok.connect(port, "http")
57
+ print(f"\n{'=' * 60}")
58
+ print(" FlashStudio is running!")
59
+ print(f" Local: http://localhost:{port}")
60
+ print(f" Public: {public_url}")
61
+ print(f"{'=' * 60}\n")
62
+ else:
63
+ print(f"\n FlashStudio running at http://localhost:{port}\n")
64
+
65
+ return proc
66
+
67
+
68
+ def _launch_local(app_path: str, port: int):
69
+ """Launch locally with streamlit."""
70
+ subprocess.run(
71
+ [sys.executable, "-m", "streamlit", "run", app_path,
72
+ "--server.port", str(port)],
73
+ )
File without changes