@danhachuel/thunderbolt 0.2.91 → 0.2.93

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
@@ -216,9 +216,20 @@ st.markdown("""
216
216
 
217
217
 
218
218
  def current_ui_language() -> str:
219
- settings = read_json("settings.json", {})
220
- stored = st.session_state.get("ui_language") or settings.get("ui_language") or "pt"
221
- normalized = language_code(stored)
219
+ """Return the session language without hitting disk on every widget render."""
220
+ cached = st.session_state.get("ui_language")
221
+ if cached:
222
+ return language_code(cached)
223
+ requested = ""
224
+ try:
225
+ requested = str(st.query_params.get("lang") or "").strip()
226
+ except Exception:
227
+ requested = ""
228
+ if requested:
229
+ normalized = language_code(requested)
230
+ else:
231
+ settings = read_json("settings.json", {})
232
+ normalized = language_code(settings.get("ui_language") or "pt")
222
233
  st.session_state["ui_language"] = normalized
223
234
  return normalized
224
235
 
@@ -235,7 +246,7 @@ _STREAMLIT_I18N_INSTALLED = False
235
246
  def _translate_streamlit_arguments(method_name: str, args: tuple[Any, ...], kwargs: dict[str, Any]) -> tuple[tuple[Any, ...], dict[str, Any]]:
236
247
  if not args and "label" not in kwargs:
237
248
  return args, kwargs
238
- selected_language = current_ui_language()
249
+ selected_language = str(st.session_state.get("ui_language") or current_ui_language())
239
250
  translated_args = list(args)
240
251
  if translated_args and isinstance(translated_args[0], str):
241
252
  translated_args[0] = ui_text(translated_args[0], selected_language)
@@ -339,6 +350,10 @@ def render_ui_language_picker(language: str) -> None:
339
350
  )
340
351
  if selected != current:
341
352
  save_ui_language(selected)
353
+ try:
354
+ st.query_params["lang"] = selected
355
+ except Exception:
356
+ pass
342
357
  st.rerun()
343
358
 
344
359
 
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import base64
6
+ from functools import lru_cache
6
7
  from typing import Any
7
8
 
8
9
 
@@ -589,6 +590,22 @@ for _row in _CONTENT_TOKEN_TRANSLATION_ROWS:
589
590
  UI_TOKEN_TRANSLATIONS[_code][_source] = _translated
590
591
 
591
592
 
593
+ @lru_cache(maxsize=16)
594
+ def _combined_translations(language: str) -> dict[str, str]:
595
+ """Build the immutable-language translation index once per process."""
596
+ return {
597
+ **UI_TRANSLATIONS.get(language, {}),
598
+ **UI_CONTENT_TRANSLATIONS.get(language, {}),
599
+ **UI_TOKEN_TRANSLATIONS.get(language, {}),
600
+ }
601
+
602
+
603
+ @lru_cache(maxsize=16)
604
+ def _sorted_translations(language: str) -> tuple[tuple[str, str], ...]:
605
+ """Cache longest-first token replacement order for each language."""
606
+ return tuple(sorted(_combined_translations(language).items(), key=lambda item: len(item[0]), reverse=True))
607
+
608
+
592
609
  def translate_ui_content(value: Any, language: Any = "pt") -> Any:
593
610
  """Translate visible Streamlit content while preserving non-text values and user data markup."""
594
611
  if not isinstance(value, str):
@@ -601,12 +618,7 @@ def translate_ui_content(value: Any, language: Any = "pt") -> Any:
601
618
  if exact is not None:
602
619
  return exact
603
620
  translated = value
604
- combined_translations = {
605
- **UI_TRANSLATIONS.get(code, {}),
606
- **UI_CONTENT_TRANSLATIONS.get(code, {}),
607
- **UI_TOKEN_TRANSLATIONS.get(code, {}),
608
- }
609
- for source, target in sorted(combined_translations.items(), key=lambda item: len(item[0]), reverse=True):
621
+ for source, target in _sorted_translations(code):
610
622
  if source != target and source in translated:
611
623
  translated = translated.replace(source, target)
612
624
  return translated
@@ -127,7 +127,7 @@ DEFAULTS: dict[str, Any] = {
127
127
  "apify_actor_id": "streamers~youtube-scraper",
128
128
  "apify_poll_interval_seconds": 10,
129
129
  "apify_run_timeout_seconds": 900,
130
- "llm_provider": DEFAULT_LLM_PROVIDER
130
+ "llm_provider": DEFAULT_LLM_PROVIDER,
131
131
  "moonshot_api_key": "",
132
132
  "moonshot_base_url": "",
133
133
  "moonshot_model_name": "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.2.91",
3
+ "version": "0.2.93",
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
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn, spawnSync } from "node:child_process";
3
+ import http from "node:http";
4
+ import net from "node:net";
3
5
  import { existsSync, mkdirSync, readFileSync, copyFileSync, readdirSync } from "node:fs";
4
6
  import { homedir, platform } from "node:os";
5
7
  import { join, resolve } from "node:path";
@@ -150,6 +152,51 @@ if (args[0] === "pipeline-worker" || args.includes("--pipeline-worker")) {
150
152
  }
151
153
 
152
154
  const port = process.env.THUNDERBOLT_PORT || process.env.HERMES_PORT || "3030";
155
+ const publicPort = Number.parseInt(String(port), 10);
156
+ const backendPort = Number.isFinite(publicPort) ? publicPort + 1 : 3031;
157
+ const supportedLanguages = new Set(["en", "zh", "de", "vi", "tr", "pt", "ru", "es", "id", "it"]);
158
+
159
+ const proxy = http.createServer((request, response) => {
160
+ const requestUrl = new URL(request.url || "/", `http://localhost:${publicPort}`);
161
+ const pathParts = requestUrl.pathname.split("/").filter(Boolean);
162
+ const languagePrefix = pathParts.length === 1 && supportedLanguages.has(pathParts[0]) ? pathParts[0] : "";
163
+ if (languagePrefix) {
164
+ requestUrl.pathname = "/";
165
+ requestUrl.searchParams.set("lang", languagePrefix);
166
+ response.writeHead(302, { Location: `${requestUrl.pathname}?${requestUrl.searchParams.toString()}` });
167
+ response.end();
168
+ return;
169
+ }
170
+ const upstream = http.request({
171
+ hostname: "127.0.0.1",
172
+ port: backendPort,
173
+ method: request.method,
174
+ path: `${requestUrl.pathname}${requestUrl.search}`,
175
+ headers: { ...request.headers, host: `127.0.0.1:${backendPort}` },
176
+ }, (upstreamResponse) => {
177
+ response.writeHead(upstreamResponse.statusCode || 502, upstreamResponse.headers);
178
+ upstreamResponse.pipe(response);
179
+ });
180
+ upstream.on("error", (error) => {
181
+ response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
182
+ response.end(`Thunderbolt backend indisponível: ${error.message}`);
183
+ });
184
+ request.pipe(upstream);
185
+ });
186
+
187
+ proxy.on("upgrade", (request, clientSocket, head) => {
188
+ const upstreamSocket = net.connect(backendPort, "127.0.0.1", () => {
189
+ const headers = Object.entries(request.headers)
190
+ .map(([name, value]) => `${name}: ${Array.isArray(value) ? value.join(", ") : value}`)
191
+ .join("\\r\\n");
192
+ upstreamSocket.write(`GET ${request.url} HTTP/1.1\\r\\n${headers}\\r\\n\\r\\n`);
193
+ if (head.length) upstreamSocket.write(head);
194
+ clientSocket.pipe(upstreamSocket).pipe(clientSocket);
195
+ });
196
+ upstreamSocket.on("error", () => clientSocket.destroy());
197
+ });
198
+
199
+ proxy.listen(publicPort, "127.0.0.1");
153
200
  const worker = spawn(python, ["-m", "hermes_ui.automation_worker"], {
154
201
  cwd: root,
155
202
  stdio: "inherit",
@@ -162,7 +209,7 @@ const pipelineWorker = spawn(python, ["-m", "hermes_ui.pipeline_worker"], {
162
209
  env: runtimeEnv,
163
210
  windowsHide: false,
164
211
  });
165
- const child = spawn(python, ["-m", "streamlit", "run", main, "--server.port", port, "--server.address", "localhost"], {
212
+ const child = spawn(python, ["-m", "streamlit", "run", main, "--server.port", String(backendPort), "--server.address", "127.0.0.1"], {
166
213
  cwd: root,
167
214
  stdio: "inherit",
168
215
  env: runtimeEnv,
@@ -170,6 +217,7 @@ const child = spawn(python, ["-m", "streamlit", "run", main, "--server.port", po
170
217
  });
171
218
 
172
219
  const stopWorker = () => {
220
+ proxy.close();
173
221
  if (!worker.killed) worker.kill();
174
222
  if (!pipelineWorker.killed) pipelineWorker.kill();
175
223
  };