flashstudio 0.2.0__tar.gz → 0.2.1__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.

Potentially problematic release.


This version of flashstudio might be problematic. Click here for more details.

Files changed (110) hide show
  1. {flashstudio-0.2.0 → flashstudio-0.2.1}/PKG-INFO +1 -1
  2. flashstudio-0.2.1/flashstudio/launcher.py +288 -0
  3. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio.egg-info/PKG-INFO +1 -1
  4. {flashstudio-0.2.0 → flashstudio-0.2.1}/pyproject.toml +1 -1
  5. flashstudio-0.2.0/flashstudio/launcher.py +0 -183
  6. {flashstudio-0.2.0 → flashstudio-0.2.1}/README.md +0 -0
  7. {flashstudio-0.2.0 → flashstudio-0.2.1}/README_PYPI.md +0 -0
  8. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/__init__.py +0 -0
  9. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/app.py +0 -0
  10. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/cli.py +0 -0
  11. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/components/__init__.py +0 -0
  12. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/components/project_manager.py +0 -0
  13. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/components/sidebar.py +0 -0
  14. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/components/styles.py +0 -0
  15. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/components/wizard.py +0 -0
  16. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/components/zone_drawer/__init__.py +0 -0
  17. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/constants.py +0 -0
  18. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/core/__init__.py +0 -0
  19. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/core/constants.py +0 -0
  20. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/__init__.py +0 -0
  21. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/__init__.py +0 -0
  22. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/_common.py +0 -0
  23. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/overview/__init__.py +0 -0
  24. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/overview/metrics.py +0 -0
  25. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/overview/pipeline_status.py +0 -0
  26. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/overview/project_banner.py +0 -0
  27. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/overview/quick_actions.py +0 -0
  28. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/overview/recent_runs.py +0 -0
  29. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/overview/tab.py +0 -0
  30. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/dashboard/page.py +0 -0
  31. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/__init__.py +0 -0
  32. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/_common.py +0 -0
  33. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/download/__init__.py +0 -0
  34. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/download/tab.py +0 -0
  35. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/helpers.py +0 -0
  36. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/page.py +0 -0
  37. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/preview/__init__.py +0 -0
  38. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/preview/tab.py +0 -0
  39. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/upload/__init__.py +0 -0
  40. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/upload/tab.py +0 -0
  41. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/verify/__init__.py +0 -0
  42. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/data/verify/tab.py +0 -0
  43. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/export/__init__.py +0 -0
  44. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/export/page.py +0 -0
  45. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/export/weights/__init__.py +0 -0
  46. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/export/weights/exporter.py +0 -0
  47. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/export/weights/settings.py +0 -0
  48. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/__init__.py +0 -0
  49. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/_common.py +0 -0
  50. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/data/__init__.py +0 -0
  51. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/data/tab.py +0 -0
  52. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/model/__init__.py +0 -0
  53. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/model/tab.py +0 -0
  54. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/page.py +0 -0
  55. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/run/__init__.py +0 -0
  56. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/run/detection.py +0 -0
  57. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/run/tab.py +0 -0
  58. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/run/video.py +0 -0
  59. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/solution/__init__.py +0 -0
  60. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/inference/solution/tab.py +0 -0
  61. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/__init__.py +0 -0
  62. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/_common.py +0 -0
  63. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/advanced/__init__.py +0 -0
  64. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/advanced/tab.py +0 -0
  65. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/architecture/__init__.py +0 -0
  66. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/architecture/tab.py +0 -0
  67. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/augment/__init__.py +0 -0
  68. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/augment/tab.py +0 -0
  69. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/hyperparams/__init__.py +0 -0
  70. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/hyperparams/tab.py +0 -0
  71. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/page.py +0 -0
  72. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/utils/__init__.py +0 -0
  73. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/model/utils/config.py +0 -0
  74. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/__init__.py +0 -0
  75. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/_common.py +0 -0
  76. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/launch/__init__.py +0 -0
  77. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/launch/controls.py +0 -0
  78. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/launch/dialogs.py +0 -0
  79. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/launch/preflight.py +0 -0
  80. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/launch/runner.py +0 -0
  81. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/launch/tab.py +0 -0
  82. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/__init__.py +0 -0
  83. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/checkpoints.py +0 -0
  84. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/curves.py +0 -0
  85. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/dashboard.py +0 -0
  86. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/gt_verification.py +0 -0
  87. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/log_viewer.py +0 -0
  88. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/parsers.py +0 -0
  89. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/run_meta.py +0 -0
  90. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/tab.py +0 -0
  91. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/monitor/visualizations.py +0 -0
  92. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/pages/training/page.py +0 -0
  93. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/utils/__init__.py +0 -0
  94. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/utils/config.py +0 -0
  95. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/utils/device.py +0 -0
  96. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/utils/filesystem.py +0 -0
  97. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/utils/jobs.py +0 -0
  98. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio/utils/training_hooks.py +0 -0
  99. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio.egg-info/SOURCES.txt +0 -0
  100. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio.egg-info/dependency_links.txt +0 -0
  101. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio.egg-info/entry_points.txt +0 -0
  102. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio.egg-info/requires.txt +0 -0
  103. {flashstudio-0.2.0 → flashstudio-0.2.1}/flashstudio.egg-info/top_level.txt +0 -0
  104. {flashstudio-0.2.0 → flashstudio-0.2.1}/setup.cfg +0 -0
  105. {flashstudio-0.2.0 → flashstudio-0.2.1}/tests/test_app.py +0 -0
  106. {flashstudio-0.2.0 → flashstudio-0.2.1}/tests/test_cli.py +0 -0
  107. {flashstudio-0.2.0 → flashstudio-0.2.1}/tests/test_constants.py +0 -0
  108. {flashstudio-0.2.0 → flashstudio-0.2.1}/tests/test_launcher.py +0 -0
  109. {flashstudio-0.2.0 → flashstudio-0.2.1}/tests/test_pipeline_flow.py +0 -0
  110. {flashstudio-0.2.0 → flashstudio-0.2.1}/tests/test_save_dir_compat.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashstudio
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Interactive Training & Inference UI for FlashDet — runs on Google Colab
5
5
  Author: FlashVision
6
6
  License: Apache-2.0
@@ -0,0 +1,288 @@
1
+ """Launch FlashStudio UI — works locally and inside Google Colab."""
2
+
3
+ import subprocess
4
+ import sys
5
+ import os
6
+ import re
7
+ import time as _time
8
+ from pathlib import Path
9
+
10
+ from flashstudio.utils.device import is_colab
11
+
12
+ _STREAMLIT_CONFIG = """\
13
+ [theme]
14
+ primaryColor = "#7C3AED"
15
+ backgroundColor = "#FFFFFF"
16
+ secondaryBackgroundColor = "#F8F9FB"
17
+ textColor = "#1A1A2E"
18
+ font = "sans serif"
19
+
20
+ [server]
21
+ headless = true
22
+ port = 8501
23
+ enableCORS = false
24
+ enableXsrfProtection = false
25
+
26
+ [client]
27
+ showSidebarNavigation = false
28
+
29
+ [browser]
30
+ gatherUsageStats = false
31
+ """
32
+
33
+
34
+ def _get_app_path() -> str:
35
+ return str(Path(__file__).parent / "app.py")
36
+
37
+
38
+ def _ensure_streamlit_config():
39
+ """Write the default Streamlit config if one doesn't already exist."""
40
+ config_dir = Path.home() / ".streamlit"
41
+ config_file = config_dir / "config.toml"
42
+ if config_file.exists():
43
+ return
44
+ config_dir.mkdir(parents=True, exist_ok=True)
45
+ config_file.write_text(_STREAMLIT_CONFIG)
46
+
47
+
48
+ def launch(port: int = 8501, share: bool = True, ngrok_token: str | None = None):
49
+ """Launch FlashStudio Streamlit app.
50
+
51
+ Args:
52
+ port: Port to run the Streamlit server on.
53
+ share: If True and running in Colab, creates a public tunnel
54
+ (works on mobile too).
55
+ ngrok_token: Optional ngrok auth token. If not provided, uses
56
+ NGROK_TOKEN env var.
57
+ """
58
+ _ensure_streamlit_config()
59
+ app_path = _get_app_path()
60
+
61
+ if is_colab():
62
+ return _launch_colab(app_path, port, share, ngrok_token)
63
+ else:
64
+ _launch_local(app_path, port)
65
+ return None
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Tunnel helpers
70
+ # ---------------------------------------------------------------------------
71
+
72
+ def _start_cloudflared_tunnel(port: int) -> str | None:
73
+ """Start a free Cloudflare tunnel (no sign-up required).
74
+
75
+ Returns the public https URL or None on failure.
76
+ """
77
+ binary = "/usr/local/bin/cloudflared"
78
+ if not os.path.isfile(binary):
79
+ try:
80
+ subprocess.check_call(
81
+ ["wget", "-q",
82
+ "https://github.com/cloudflare/cloudflared/releases/latest"
83
+ "/download/cloudflared-linux-amd64",
84
+ "-O", binary],
85
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
86
+ )
87
+ os.chmod(binary, 0o755)
88
+ except Exception:
89
+ return None
90
+
91
+ tunnel_proc = subprocess.Popen(
92
+ [binary, "tunnel", "--url", f"http://localhost:{port}"],
93
+ stdout=subprocess.PIPE,
94
+ stderr=subprocess.PIPE,
95
+ )
96
+
97
+ url_re = re.compile(r"https://[a-z0-9-]+\.trycloudflare\.com")
98
+ deadline = _time.monotonic() + 20
99
+ while _time.monotonic() < deadline:
100
+ line = tunnel_proc.stderr.readline().decode("utf-8", errors="replace")
101
+ if not line:
102
+ if tunnel_proc.poll() is not None:
103
+ return None
104
+ _time.sleep(0.2)
105
+ continue
106
+ match = url_re.search(line)
107
+ if match:
108
+ return match.group(0)
109
+
110
+ return None
111
+
112
+
113
+ def _start_ngrok_tunnel(port: int, ngrok_token: str) -> str | None:
114
+ """Start an ngrok tunnel. Requires an auth token."""
115
+ try:
116
+ from pyngrok import ngrok, conf
117
+ except ImportError:
118
+ try:
119
+ subprocess.check_call(
120
+ [sys.executable, "-m", "pip", "install", "-q", "pyngrok"],
121
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
122
+ )
123
+ from pyngrok import ngrok, conf
124
+ except Exception:
125
+ return None
126
+ try:
127
+ conf.get_default().auth_token = ngrok_token
128
+ return str(ngrok.connect(port, "http"))
129
+ except Exception:
130
+ return None
131
+
132
+
133
+ def _get_colab_proxy_url(port: int) -> str | None:
134
+ """Get Colab's built-in proxy URL (only works in the same browser session)."""
135
+ try:
136
+ from google.colab.output import eval_js
137
+ result = eval_js(f"google.colab.kernel.proxyPort({port})")
138
+ if result and str(result).startswith("http"):
139
+ return str(result)
140
+ except Exception:
141
+ pass
142
+ return None
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Colab launcher
147
+ # ---------------------------------------------------------------------------
148
+
149
+ def _launch_colab(app_path: str, port: int, share: bool, ngrok_token: str | None):
150
+ """Launch in Google Colab with a public URL that works on any device."""
151
+ import urllib.request
152
+
153
+ print(" Starting FlashStudio...", end="", flush=True)
154
+
155
+ proc = subprocess.Popen(
156
+ [sys.executable, "-m", "streamlit", "run", app_path,
157
+ "--server.port", str(port),
158
+ "--server.headless", "true",
159
+ "--server.enableCORS", "false",
160
+ "--server.enableXsrfProtection", "false"],
161
+ stdout=subprocess.PIPE,
162
+ stderr=subprocess.PIPE,
163
+ )
164
+
165
+ ready = False
166
+ for _ in range(30):
167
+ _time.sleep(1)
168
+ if proc.poll() is not None:
169
+ _, stderr = proc.communicate()
170
+ err_msg = (stderr.decode("utf-8", errors="replace")[-500:]
171
+ if stderr else "Unknown error")
172
+ print(f"\n\n FlashStudio failed to start:\n {err_msg}")
173
+ return proc
174
+ try:
175
+ urllib.request.urlopen(
176
+ f"http://localhost:{port}/_stcore/health", timeout=2)
177
+ ready = True
178
+ break
179
+ except Exception:
180
+ print(".", end="", flush=True)
181
+
182
+ if ready:
183
+ print(" Ready!")
184
+ else:
185
+ print(" (still loading, URL may take a moment)")
186
+
187
+ # --- Obtain a public URL ---
188
+ # Priority: cloudflared (free, works everywhere including mobile)
189
+ # > ngrok (needs token) > Colab proxy (desktop-only)
190
+ public_url = None
191
+ tunnel_type = None
192
+
193
+ if share:
194
+ # 1) Free Cloudflare tunnel — works on mobile, no sign-up
195
+ print(" Setting up public URL (works on mobile)...", end="", flush=True)
196
+ public_url = _start_cloudflared_tunnel(port)
197
+ if public_url:
198
+ tunnel_type = "cloudflared"
199
+ print(" Done!")
200
+
201
+ # 2) ngrok — if a token is available
202
+ if not public_url:
203
+ token = ngrok_token or os.environ.get("NGROK_TOKEN", "")
204
+ if token:
205
+ public_url = _start_ngrok_tunnel(port, token)
206
+ if public_url:
207
+ tunnel_type = "ngrok"
208
+ print(" Done!")
209
+
210
+ if not public_url:
211
+ print(" Skipped (using Colab proxy)")
212
+
213
+ # 3) Colab's built-in proxy (fallback — desktop browser only)
214
+ colab_proxy_url = _get_colab_proxy_url(port)
215
+ if not public_url:
216
+ public_url = colab_proxy_url
217
+
218
+ # --- Display results ---
219
+ print(f"\n{'=' * 60}")
220
+ print(" FlashStudio is running!")
221
+ if public_url:
222
+ print(f" Open: {public_url}")
223
+ if tunnel_type:
224
+ print(f" (works on mobile & any device)")
225
+ else:
226
+ print(f" ⚠ This URL only works in this browser.")
227
+ print(f" On mobile? Re-run with: launch(share=True)")
228
+ else:
229
+ print(f" Port: {port}")
230
+ print("")
231
+ print(" Run this in a new cell to get the URL:")
232
+ print(f" from google.colab.output import eval_js")
233
+ print(f" print(eval_js('google.colab.kernel.proxyPort({port})'))")
234
+ print(f"{'=' * 60}\n")
235
+
236
+ # Show clickable link
237
+ if public_url:
238
+ try:
239
+ from IPython.display import display, HTML
240
+ mobile_badge = ""
241
+ if tunnel_type:
242
+ mobile_badge = (
243
+ '<span style="display:inline-block;font-size:11px;'
244
+ 'padding:2px 8px;background:#10B981;color:white;'
245
+ 'border-radius:4px;margin-left:8px;vertical-align:middle;">'
246
+ 'Mobile Ready</span>'
247
+ )
248
+ display(HTML(
249
+ f'<div style="margin:8px 0;">'
250
+ f'<a href="{public_url}" target="_blank" '
251
+ f'style="display:inline-block;font-size:16px;padding:10px 24px;'
252
+ f'background:#7C3AED;color:white;border-radius:8px;'
253
+ f'text-decoration:none;font-weight:600;">'
254
+ f'Open FlashStudio</a>{mobile_badge}</div>'
255
+ ))
256
+ except Exception:
257
+ pass
258
+
259
+ # Embed Streamlit directly in the Colab cell output
260
+ try:
261
+ from google.colab import output
262
+ output.serve_kernel_port_as_iframe(port, height=800)
263
+ except Exception:
264
+ if not public_url:
265
+ try:
266
+ from IPython.display import display, HTML
267
+ display(HTML(
268
+ f'<div style="padding:12px;background:#FEF3C7;'
269
+ f'border-radius:8px;margin:8px 0;font-size:14px;">'
270
+ f'Run in a <b>new cell</b>:<br>'
271
+ f'<code style="background:#F3F4F6;padding:4px 8px;'
272
+ f'border-radius:4px;">'
273
+ f'from google.colab import output; '
274
+ f'output.serve_kernel_port_as_iframe({port}, height=800)'
275
+ f'</code></div>'
276
+ ))
277
+ except Exception:
278
+ pass
279
+
280
+ return proc
281
+
282
+
283
+ def _launch_local(app_path: str, port: int):
284
+ """Launch locally with streamlit."""
285
+ subprocess.run(
286
+ [sys.executable, "-m", "streamlit", "run", app_path,
287
+ "--server.port", str(port)],
288
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flashstudio
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Interactive Training & Inference UI for FlashDet — runs on Google Colab
5
5
  Author: FlashVision
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flashstudio"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Interactive Training & Inference UI for FlashDet — runs on Google Colab"
9
9
  readme = "README_PYPI.md"
10
10
  license = {text = "Apache-2.0"}
@@ -1,183 +0,0 @@
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
- from flashstudio.utils.device import is_colab
9
-
10
- _STREAMLIT_CONFIG = """\
11
- [theme]
12
- primaryColor = "#7C3AED"
13
- backgroundColor = "#FFFFFF"
14
- secondaryBackgroundColor = "#F8F9FB"
15
- textColor = "#1A1A2E"
16
- font = "sans serif"
17
-
18
- [server]
19
- headless = true
20
- port = 8501
21
- enableCORS = false
22
- enableXsrfProtection = false
23
-
24
- [client]
25
- showSidebarNavigation = false
26
-
27
- [browser]
28
- gatherUsageStats = false
29
- """
30
-
31
-
32
- def _get_app_path() -> str:
33
- return str(Path(__file__).parent / "app.py")
34
-
35
-
36
- def _ensure_streamlit_config():
37
- """Write the default Streamlit config if one doesn't already exist."""
38
- config_dir = Path.home() / ".streamlit"
39
- config_file = config_dir / "config.toml"
40
- if config_file.exists():
41
- return
42
- config_dir.mkdir(parents=True, exist_ok=True)
43
- config_file.write_text(_STREAMLIT_CONFIG)
44
-
45
-
46
- def launch(port: int = 8501, share: bool = True, ngrok_token: str | None = None):
47
- """Launch FlashStudio Streamlit app.
48
-
49
- Args:
50
- port: Port to run the Streamlit server on.
51
- share: If True and running in Colab, creates a public ngrok tunnel.
52
- ngrok_token: Optional ngrok auth token. If not provided, uses NGROK_TOKEN env var.
53
- """
54
- _ensure_streamlit_config()
55
- app_path = _get_app_path()
56
-
57
- if is_colab():
58
- return _launch_colab(app_path, port, share, ngrok_token)
59
- else:
60
- _launch_local(app_path, port)
61
- return None
62
-
63
-
64
- def _launch_colab(app_path: str, port: int, share: bool, ngrok_token: str | None):
65
- """Launch in Google Colab with automatic tunnel detection."""
66
- import time as _time
67
- import urllib.request
68
- import urllib.error
69
-
70
- print(" Starting FlashStudio...", end="", flush=True)
71
-
72
- proc = subprocess.Popen(
73
- [sys.executable, "-m", "streamlit", "run", app_path,
74
- "--server.port", str(port),
75
- "--server.headless", "true",
76
- "--server.enableCORS", "false",
77
- "--server.enableXsrfProtection", "false"],
78
- stdout=subprocess.PIPE,
79
- stderr=subprocess.PIPE,
80
- )
81
-
82
- # Wait until Streamlit is actually responding (up to 30s)
83
- ready = False
84
- for i in range(30):
85
- _time.sleep(1)
86
- if proc.poll() is not None:
87
- _, stderr = proc.communicate()
88
- err_msg = stderr.decode("utf-8", errors="replace")[-500:] if stderr else "Unknown error"
89
- print(f"\n\n FlashStudio failed to start:\n {err_msg}")
90
- return proc
91
- try:
92
- urllib.request.urlopen(f"http://localhost:{port}/_stcore/health", timeout=2)
93
- ready = True
94
- break
95
- except Exception:
96
- print(".", end="", flush=True)
97
-
98
- if ready:
99
- print(" Ready!")
100
- else:
101
- print(" (still loading, URL may take a moment)")
102
-
103
- public_url = None
104
-
105
- # Method 1: Colab's built-in proxy (simplest, no install needed)
106
- try:
107
- from google.colab.output import eval_js
108
- result = eval_js(f"google.colab.kernel.proxyPort({port})")
109
- if result and str(result).startswith("http"):
110
- public_url = str(result)
111
- except Exception:
112
- pass
113
-
114
- # Method 2: ngrok (only if token is provided)
115
- if not public_url:
116
- token = ngrok_token or os.environ.get("NGROK_TOKEN", "")
117
- if token:
118
- try:
119
- from pyngrok import ngrok, conf
120
- conf.get_default().auth_token = token
121
- public_url = str(ngrok.connect(port, "http"))
122
- except ImportError:
123
- try:
124
- subprocess.check_call(
125
- [sys.executable, "-m", "pip", "install", "-q", "pyngrok"],
126
- stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
127
- )
128
- from pyngrok import ngrok, conf
129
- conf.get_default().auth_token = token
130
- public_url = str(ngrok.connect(port, "http"))
131
- except Exception:
132
- pass
133
- except Exception:
134
- pass
135
-
136
- print(f"\n{'=' * 60}")
137
- print(" FlashStudio is running!")
138
- if public_url:
139
- print(f" Open: {public_url}")
140
- else:
141
- print(f" Port: {port}")
142
- print("")
143
- print(" Run this in a new cell to get the URL:")
144
- print(f" from google.colab.output import eval_js")
145
- print(f" print(eval_js('google.colab.kernel.proxyPort({port})'))")
146
- print(f"{'=' * 60}\n")
147
-
148
- if public_url:
149
- try:
150
- from IPython.display import display, HTML
151
- display(HTML(
152
- f'<a href="{public_url}" target="_blank" '
153
- f'style="display:inline-block;font-size:16px;padding:10px 24px;'
154
- f'background:#7C3AED;color:white;border-radius:8px;'
155
- f'text-decoration:none;font-weight:600;margin:8px 0;">'
156
- f'Open FlashStudio</a>'
157
- ))
158
- except Exception:
159
- pass
160
- else:
161
- try:
162
- from IPython.display import display, HTML
163
- display(HTML(
164
- f'<div style="padding:12px;background:#FEF3C7;border-radius:8px;'
165
- f'margin:8px 0;font-size:14px;">'
166
- f'Run in a <b>new cell</b>:<br>'
167
- f'<code style="background:#F3F4F6;padding:4px 8px;border-radius:4px;">'
168
- f'from google.colab.output import eval_js; '
169
- f'print(eval_js("google.colab.kernel.proxyPort({port})"))</code>'
170
- f'</div>'
171
- ))
172
- except Exception:
173
- pass
174
-
175
- return proc
176
-
177
-
178
- def _launch_local(app_path: str, port: int):
179
- """Launch locally with streamlit."""
180
- subprocess.run(
181
- [sys.executable, "-m", "streamlit", "run", app_path,
182
- "--server.port", str(port)],
183
- )
File without changes
File without changes
File without changes