@danhachuel/thunderbolt 0.4.90 → 0.4.92

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
@@ -4,12 +4,14 @@ import io
4
4
 
5
5
  os.environ["PYTHONIOENCODING"] = "utf-8"
6
6
  os.environ["PYTHONUTF8"] = "1"
7
+ os.environ["CLICK_NO_WIN_CONSOLE"] = "1"
7
8
 
8
9
  def _force_utf8_stream(stream: object) -> object:
9
10
  buffer = getattr(stream, "buffer", None)
10
- encoding = str(getattr(stream, "encoding", "") or "").lower().replace("-", "_")
11
- if buffer is not None and encoding != "utf_8":
12
- return io.TextIOWrapper(buffer, encoding="utf-8", errors="replace", line_buffering=True)
11
+ if buffer is not None and not getattr(stream, "_thunderbolt_utf8", False):
12
+ wrapped = io.TextIOWrapper(buffer, encoding="utf-8", errors="replace", line_buffering=True)
13
+ setattr(wrapped, "_thunderbolt_utf8", True)
14
+ return wrapped
13
15
  return stream
14
16
 
15
17
  _original_stdout, _original_stderr = sys.stdout, sys.stderr
@@ -2025,7 +2027,7 @@ def render_tiktok_channels():
2025
2027
  for channel in channels:
2026
2028
  channel_id = str(channel.get("id") or "")
2027
2029
  with st.container(border=True):
2028
- card_cols = st.columns([0.7, 2.3, 1.0, 1.0, 1.35, 1.25])
2030
+ card_cols = st.columns([0.7, 3.45, 1.15, 1.15, 1.45, 1.55], gap="small")
2029
2031
  with card_cols[0]:
2030
2032
  avatar_url = _tiktok_avatar_url(channel)
2031
2033
  if avatar_url:
@@ -2041,9 +2043,6 @@ def render_tiktok_channels():
2041
2043
  st.metric("Seguidores", format_metric_number(channel.get("subscriber_count")))
2042
2044
  with card_cols[3]:
2043
2045
  st.metric("Curtidas", format_metric_number(channel.get("likes_count")))
2044
- with card_cols[4]:
2045
- current_prompt = str(channel.get("default_prompt_master") or channel.get("prompt_master") or "")
2046
- selected_prompt = st.selectbox("Prompt Master", prompt_ids, index=prompt_ids.index(current_prompt) if current_prompt in prompt_ids else 0, format_func=lambda item: prompt_labels.get(item, item), key=f"tiktok_import_card_prompt_{channel_id}")
2047
2046
  with card_cols[5]:
2048
2047
  active = st.toggle("Activo", value=bool(channel.get("active", True)), key=f"tiktok_import_card_active_{channel_id}")
2049
2048
  if active != bool(channel.get("active", True)):
@@ -2055,20 +2054,20 @@ def render_tiktok_channels():
2055
2054
  if st.button("Apagar card", key=f"tiktok_import_card_delete_{channel_id}"):
2056
2055
  st.session_state[f"tiktok_delete_{channel_id}"] = True
2057
2056
  st.rerun()
2058
- summary_cols = st.columns(4, gap="small")
2059
- with summary_cols[0]:
2060
- st.markdown("**Blueprint Padrão**")
2061
- st.caption(prompt_labels.get(str(channel.get("default_prompt_master") or channel.get("prompt_master") or ""), "Sem Blueprint padrão"))
2062
- with summary_cols[1]:
2063
- st.markdown("**Nicho**")
2064
- st.caption(channel_niche_label(channel))
2065
- with summary_cols[2]:
2066
- st.markdown("**Narrador/Voz Padrão**")
2067
- st.caption(str(channel.get("default_voice") or channel.get("voice") or "Sem voz padrão"))
2068
- with summary_cols[3]:
2069
- st.markdown("**Idioma**")
2070
- st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
2071
- st.caption(f"Fonte do vídeo: {channel_video_source_value(channel.get('style_wide'))} · Proporção: {channel.get('video_aspect_ratio') or 'Portrait 9:16'}")
2057
+ summary_cols = st.columns(4, gap="medium")
2058
+ with summary_cols[0]:
2059
+ st.markdown("**Blueprint Padrão**")
2060
+ st.caption(prompt_labels.get(str(channel.get("default_prompt_master") or channel.get("prompt_master") or ""), "Sem Blueprint padrão"))
2061
+ with summary_cols[1]:
2062
+ st.markdown("**Nicho**")
2063
+ st.caption(channel_niche_label(channel))
2064
+ with summary_cols[2]:
2065
+ st.markdown("**Narrador/Voz Padrão**")
2066
+ st.caption(str(channel.get("default_voice") or channel.get("voice") or "Sem voz padrão"))
2067
+ with summary_cols[3]:
2068
+ st.markdown("**Idioma**")
2069
+ st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
2070
+ st.caption(f"Fonte do vídeo: {channel_video_source_value(channel.get('style_wide'))} · Proporção: {channel.get('video_aspect_ratio') or 'Portrait 9:16'}")
2072
2071
  if st.session_state.get(f"tiktok_delete_{channel_id}"):
2073
2072
  st.warning("Apagar este canal TikTok e os dados locais associados?")
2074
2073
  confirm_cols = st.columns(2)
@@ -2081,13 +2080,6 @@ def render_tiktok_channels():
2081
2080
  if st.button("Cancelar", key=f"tiktok_cancel_delete_{channel_id}"):
2082
2081
  st.session_state.pop(f"tiktok_delete_{channel_id}", None)
2083
2082
  st.rerun()
2084
- if st.session_state.get(f"tiktok_niche_{channel_id}"):
2085
- with st.form(f"tiktok_niche_form_{channel_id}"):
2086
- niche_value = st.text_input("Nicho do canal", value=str(channel.get("niche") or ""))
2087
- if st.form_submit_button("Guardar nicho", type="primary"):
2088
- update_channel(channel_id, {"niche": niche_value.strip(), "reference_channels": [niche_value.strip()] if niche_value.strip() else []})
2089
- st.session_state.pop(f"tiktok_niche_{channel_id}", None)
2090
- st.rerun()
2091
2083
  if st.session_state.get(f"tiktok_edit_{channel_id}"):
2092
2084
  with st.form(f"tiktok_edit_form_{channel_id}"):
2093
2085
  edit_name = st.text_input("Nome do canal", value=str(channel.get("name") or ""))
@@ -2096,6 +2088,7 @@ def render_tiktok_channels():
2096
2088
  edit_language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, index=VIDEO_LANGUAGE_SELECTION_OPTIONS.index(normalize_video_language(channel.get("language") or "pt")) if normalize_video_language(channel.get("language") or "pt") in VIDEO_LANGUAGE_SELECTION_OPTIONS else 0, format_func=video_language_label)
2097
2089
  edit_source = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0)
2098
2090
  edit_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Portrait 9:16")) if str(channel.get("video_aspect_ratio") or "Portrait 9:16") in CHANNEL_ASPECT_RATIO_OPTIONS else 1)
2091
+ edit_niche = st.text_input("Nicho", value=str(channel.get("niche") or ""))
2099
2092
  edit_voice = st.selectbox("Narrador/Voz Padrão", voice_options, index=voice_options.index(str(channel.get("default_voice") or channel.get("voice") or "")) if str(channel.get("default_voice") or channel.get("voice") or "") in voice_options else 0, format_func=lambda item: item or "Sem voz padrão")
2100
2093
  edit_automation = st.toggle("Automação ON", value=bool(channel.get("automation_on", False)), key=f"tiktok_edit_automation_{channel_id}")
2101
2094
  edit_time = st.text_input("Horário diário (HH:MM)", value=str(channel.get("automation_time") or "00:00"))
@@ -2105,7 +2098,7 @@ def render_tiktok_channels():
2105
2098
  if not valid_hhmm(edit_time):
2106
2099
  st.error("O horário diário deve estar no formato HH:MM.")
2107
2100
  else:
2108
- update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "default_voice": edit_voice, "voice": edit_voice, "style_wide": channel_video_source_storage(edit_source), "video_aspect_ratio": edit_aspect, "automation_on": edit_automation, "automation_time": edit_time.strip(), "description": edit_description.strip(), "platform": "tiktok"})
2101
+ update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "niche": edit_niche.strip(), "reference_channels": [edit_niche.strip()] if edit_niche.strip() else [], "default_voice": edit_voice, "voice": edit_voice, "style_wide": channel_video_source_storage(edit_source), "video_aspect_ratio": edit_aspect, "automation_on": edit_automation, "automation_time": edit_time.strip(), "description": edit_description.strip(), "platform": "tiktok"})
2109
2102
  st.session_state.pop(f"tiktok_edit_{channel_id}", None)
2110
2103
  st.rerun()
2111
2104
  with st.expander("Últimos 10 vídeos publicados", expanded=False):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.90",
3
+ "version": "0.4.92",
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
@@ -26,7 +26,7 @@ const venvPython = platform() === "win32" ? join(venvDir, "Scripts", "python.exe
26
26
  const python = process.env.THUNDERBOLT_PYTHON || process.env.HERMES_PYTHON || (existsSync(venvPython) ? venvPython : (platform() === "win32" ? "python" : "python3"));
27
27
  const main = resolve(root, "app", "main.py");
28
28
  const settingsPath = join(thunderboltHome, "storage", "state", "settings.json");
29
- const pythonEnvironment = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" };
29
+ const pythonEnvironment = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", CLICK_NO_WIN_CONSOLE: "1" };
30
30
 
31
31
  function run(command, commandArgs, label = "comando", environment = process.env) {
32
32
  console.log(`Thunderbolt: a iniciar ${label}...`);
@@ -155,6 +155,7 @@ const runtimeEnv = {
155
155
  ...process.env,
156
156
  PYTHONIOENCODING: "utf-8",
157
157
  PYTHONUTF8: "1",
158
+ CLICK_NO_WIN_CONSOLE: "1",
158
159
  THUNDERBOLT_STORAGE_DIR: storageDir,
159
160
  STREAMLIT_BROWSER_GATHER_USAGE_STATS: "false",
160
161
  STREAMLIT_SERVER_HEADLESS: "true",
@@ -257,7 +258,7 @@ function startStreamlit() {
257
258
  child = spawn(python, ["-m", "streamlit", "run", main, "--server.port", String(backendPort), "--server.address", "127.0.0.1"], {
258
259
  cwd: root,
259
260
  stdio: "inherit",
260
- env: { ...runtimeEnv, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", THUNDERBOLT_LAUNCHER_RESTART: "1" },
261
+ env: { ...runtimeEnv, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", CLICK_NO_WIN_CONSOLE: "1", THUNDERBOLT_LAUNCHER_RESTART: "1" },
261
262
  windowsHide: false,
262
263
  });
263
264
  child.on("error", (error) => {
@@ -42,7 +42,7 @@ const defaultMpt = process.env.MONEYPRINTER_PATH || join(thunderboltHome, "Money
42
42
  const dependencyStatePath = join(thunderboltHome, "storage", "state", "install-state.json");
43
43
  const forceDeps = args.includes("--force-deps");
44
44
  const refreshMoneyPrinter = args.includes("--refresh-moneyprinter");
45
- const pythonEnvironment = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" };
45
+ const pythonEnvironment = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", CLICK_NO_WIN_CONSOLE: "1" };
46
46
 
47
47
  function legacyRoots() {
48
48
  const userProfile = process.env.USERPROFILE || home;