@danhachuel/thunderbolt 0.4.71 → 0.4.73

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.
package/app/main.py CHANGED
@@ -4349,6 +4349,28 @@ def load_video_tasks_for_catalog() -> list[dict[str, Any]]:
4349
4349
  return [task for task in saved if isinstance(task, dict) and str(task.get("id") or "").strip()]
4350
4350
 
4351
4351
 
4352
+ def _task_thumbnail_path(task: dict[str, Any]) -> Path | None:
4353
+ """Resolve a generated thumbnail from current and legacy task fields."""
4354
+ artifacts = task.get("artifacts") if isinstance(task.get("artifacts"), dict) else {}
4355
+ candidates = [
4356
+ artifacts.get("thumbnail"),
4357
+ artifacts.get("cover"),
4358
+ task.get("thumbnail_path"),
4359
+ task.get("thumbnail_url"),
4360
+ ]
4361
+ for candidate in candidates:
4362
+ raw = str(candidate or "").strip()
4363
+ if not raw or raw.startswith(("http://", "https://")):
4364
+ continue
4365
+ paths = [Path(raw)]
4366
+ if not Path(raw).is_absolute():
4367
+ paths.extend([STORAGE / raw, STORAGE / "videos" / raw])
4368
+ for path in paths:
4369
+ if path.is_file():
4370
+ return path
4371
+ return None
4372
+
4373
+
4352
4374
  def _is_music_task(task: dict[str, Any]) -> bool:
4353
4375
  """Identify music pipeline tasks without conflating them with ordinary video tasks."""
4354
4376
  return bool(task.get("music_mode")) or str(task.get("style_wide") or task.get("style") or "").strip().casefold() in {"music", "música"}
@@ -4901,6 +4923,11 @@ def render_automation():
4901
4923
  with st.container(border=True):
4902
4924
  task_cols = st.columns([2.25, 1.65, 1.15, 1.85])
4903
4925
  with task_cols[0]:
4926
+ thumbnail_path = _task_thumbnail_path(task)
4927
+ if thumbnail_path:
4928
+ st.image(thumbnail_path, width=180, caption="Thumbnail")
4929
+ else:
4930
+ st.caption("Thumbnail ainda não pronta")
4904
4931
  st.write(f"**{task.get('topic', 'Sem tópico')}**")
4905
4932
  st.caption(f"{task.get('channel_name', 'Canal')} · {task.get('id', '')}")
4906
4933
  with task_cols[1]:
@@ -18,6 +18,7 @@ import requests
18
18
  PACKAGE_NAME = "@danhachuel/thunderbolt"
19
19
  REGISTRY_URL = "https://registry.npmjs.org/@danhachuel/thunderbolt/latest"
20
20
  UPDATE_TIMEOUT_SECONDS = 20 * 60
21
+ LAUNCHER_RESTART_EXIT_CODE = 75
21
22
 
22
23
 
23
24
  @dataclass(frozen=True)
@@ -44,7 +45,9 @@ class UpdateResult:
44
45
 
45
46
 
46
47
  def restart_current_process(*, exec_fn: Callable[..., Any] = os.execv) -> None:
47
- """Replace the running Streamlit process with the same command line."""
48
+ """Ask the launcher to restart Streamlit, or re-exec standalone callers."""
49
+ if os.environ.get("THUNDERBOLT_LAUNCHER_RESTART") == "1":
50
+ raise SystemExit(LAUNCHER_RESTART_EXIT_CODE)
48
51
  executable = sys.executable
49
52
  exec_fn(executable, [executable, *sys.argv])
50
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.71",
3
+ "version": "0.4.73",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",
package/scripts/cli.mjs CHANGED
@@ -242,23 +242,49 @@ const pipelineWorker = spawn(python, ["-m", "hermes_ui.pipeline_worker"], {
242
242
  env: runtimeEnv,
243
243
  windowsHide: false,
244
244
  });
245
- const child = spawn(python, ["-m", "streamlit", "run", main, "--server.port", String(backendPort), "--server.address", "127.0.0.1"], {
246
- cwd: root,
247
- stdio: "ignore",
248
- env: runtimeEnv,
249
- windowsHide: false,
250
- });
245
+ const restartExitCode = 75;
246
+ let shuttingDown = false;
247
+ let child;
248
+
249
+ function startStreamlit() {
250
+ child = spawn(python, ["-m", "streamlit", "run", main, "--server.port", String(backendPort), "--server.address", "127.0.0.1"], {
251
+ cwd: root,
252
+ stdio: "ignore",
253
+ env: { ...runtimeEnv, THUNDERBOLT_LAUNCHER_RESTART: "1" },
254
+ windowsHide: false,
255
+ });
256
+ child.on("exit", (code, signal) => {
257
+ if (shuttingDown) {
258
+ stopWorker();
259
+ process.exit(code ?? (signal ? 1 : 0));
260
+ }
261
+ if (code === restartExitCode) {
262
+ const executable = platform() === "win32" ? "npx.cmd" : "npx";
263
+ const replacement = spawn(executable, ["--yes", "--prefer-online", "@danhachuel/thunderbolt"], {
264
+ cwd: userHome,
265
+ env: process.env,
266
+ detached: true,
267
+ stdio: "ignore",
268
+ windowsHide: false,
269
+ });
270
+ replacement.unref();
271
+ stopWorker();
272
+ process.exit(0);
273
+ }
274
+ stopWorker();
275
+ process.exit(code ?? (signal ? 1 : 0));
276
+ });
277
+ }
278
+
279
+ startStreamlit();
251
280
 
252
281
  const stopWorker = () => {
282
+ shuttingDown = true;
253
283
  proxy.close();
254
284
  if (!worker.killed) worker.kill();
255
285
  if (!pipelineWorker.killed) pipelineWorker.kill();
256
286
  };
257
287
  process.on("SIGINT", stopWorker);
258
288
  process.on("SIGTERM", stopWorker);
259
- child.on("exit", (code, signal) => {
260
- stopWorker();
261
- process.exit(code ?? (signal ? 1 : 0));
262
- });
263
289
  worker.on("error", (error) => console.error(`Thunderbolt worker: ${error.message}`));
264
290
  pipelineWorker.on("error", (error) => console.error(`Thunderbolt pipeline worker: ${error.message}`));