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.
- flashstudio/__init__.py +5 -0
- flashstudio/app.py +64 -0
- flashstudio/cli.py +18 -0
- flashstudio/components/__init__.py +0 -0
- flashstudio/components/sidebar.py +45 -0
- flashstudio/components/styles.py +273 -0
- flashstudio/components/wizard.py +46 -0
- flashstudio/launcher.py +73 -0
- flashstudio/pages/__init__.py +0 -0
- flashstudio/pages/dashboard.py +168 -0
- flashstudio/pages/data.py +272 -0
- flashstudio/pages/export.py +212 -0
- flashstudio/pages/inference.py +1112 -0
- flashstudio/pages/model.py +370 -0
- flashstudio/pages/training.py +672 -0
- flashstudio/utils/__init__.py +0 -0
- flashstudio/utils/device.py +58 -0
- flashstudio-0.1.0.dist-info/METADATA +133 -0
- flashstudio-0.1.0.dist-info/RECORD +22 -0
- flashstudio-0.1.0.dist-info/WHEEL +5 -0
- flashstudio-0.1.0.dist-info/entry_points.txt +2 -0
- flashstudio-0.1.0.dist-info/top_level.txt +1 -0
flashstudio/__init__.py
ADDED
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()
|
flashstudio/launcher.py
ADDED
|
@@ -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
|