@danhachuel/thunderbolt 0.4.94 → 0.4.96

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
@@ -5,6 +5,7 @@ import io
5
5
  os.environ["PYTHONIOENCODING"] = "utf-8"
6
6
  os.environ["PYTHONUTF8"] = "1"
7
7
  os.environ["CLICK_NO_WIN_CONSOLE"] = "1"
8
+ os.environ["PYTHONLEGACYWINDOWSSTDIO"] = "1"
8
9
 
9
10
  def _force_utf8_stream(stream: object) -> object:
10
11
  if os.name != "nt":
@@ -7133,6 +7134,7 @@ def _render_media_provider_card(settings: dict[str, Any], cards: list[dict[str,
7133
7134
  card_form = nullcontext() if embedded else st.form(f"media_card_form_{card_id}")
7134
7135
  with card_form:
7135
7136
  key_col, model_col = st.columns(2)
7137
+ refresh_clicked = False
7136
7138
  with key_col:
7137
7139
  api_key = str(card.get("api_key") or "")
7138
7140
  if definition.requires_api_key:
@@ -7158,9 +7160,15 @@ def _render_media_provider_card(settings: dict[str, Any], cards: list[dict[str,
7158
7160
  key=f"media_card_{card_id}_model_select",
7159
7161
  )
7160
7162
  model = "" if selected_model == "__select_model__" else selected_model
7161
- base_url = st.text_input("Base URL", value=str(card.get("base_url") or definition.default_base_url), key=f"media_card_{card_id}_base_url")
7163
+ if definition.code == "canva":
7164
+ refresh_clicked = st.form_submit_button("Consultar Modelos", use_container_width=True, key=f"media_card_{card_id}_refresh")
7165
+ if definition.code == "canva":
7166
+ base_url = definition.default_base_url
7167
+ st.text_input("Base URL", value=base_url, disabled=True, key=f"media_card_{card_id}_base_url_display")
7168
+ else:
7169
+ base_url = st.text_input("Base URL", value=str(card.get("base_url") or definition.default_base_url), key=f"media_card_{card_id}_base_url")
7162
7170
  extra_values: dict[str, str] = {}
7163
- if definition.extra_fields:
7171
+ if definition.extra_fields and definition.code != "canva":
7164
7172
  extra_cols = st.columns(len(definition.extra_fields))
7165
7173
  for extra_col, field_name in zip(extra_cols, definition.extra_fields):
7166
7174
  with extra_col:
@@ -7169,17 +7177,37 @@ def _render_media_provider_card(settings: dict[str, Any], cards: list[dict[str,
7169
7177
  supports_image = True
7170
7178
  supports_video = False
7171
7179
  st.caption("Canva Connect é usado exclusivamente para thumbnails no Pool de Imagem. Não é provider de vídeo.")
7172
- redirect_uri = str(card.get("redirect_uri") or "").strip()
7173
- if not redirect_uri:
7174
- st.info("Defina o Redirect URI no Developer Portal Canva e repita-o neste card antes de autorizar.")
7175
- elif str(card.get("client_id") or "").strip() and str(card.get("client_secret") or "").strip():
7176
- if st.button("Autorizar Canva", key=f"media_card_{card_id}_authorize"):
7180
+ credential_cols = st.columns(3)
7181
+ with credential_cols[0]:
7182
+ client_id = st.text_input("Client Id", value=str(card.get("client_id") or ""), key=f"media_card_{card_id}_client_id")
7183
+ with credential_cols[1]:
7184
+ client_secret = st.text_input("Client Secret", value=str(card.get("client_secret") or ""), type="password", key=f"media_card_{card_id}_client_secret")
7185
+ with credential_cols[2]:
7186
+ redirect_uri = st.text_input("Redirect Uri", value=str(card.get("redirect_uri") or ""), key=f"media_card_{card_id}_redirect_uri")
7187
+ extra_values.update({"client_id": client_id.strip(), "client_secret": client_secret.strip(), "redirect_uri": redirect_uri.strip()})
7188
+ oauth_token = card.get("oauth_token") if isinstance(card.get("oauth_token"), dict) else {}
7189
+ has_credentials = bool(client_id.strip() and client_secret.strip() and redirect_uri.strip())
7190
+ is_authorized = bool(oauth_token.get("access_token"))
7191
+ oauth_state = "Autorizado" if is_authorized else ("Não Autorizado" if has_credentials else "⚠️ Incompleto")
7192
+ st.markdown(f"**Estado OAuth:** `{oauth_state}`")
7193
+ if not has_credentials:
7194
+ st.info("Preencha Client Id, Client Secret e Redirect Uri para iniciar a autorização.")
7195
+ else:
7196
+ if st.button("Iniciar autorização", key=f"media_card_{card_id}_authorize"):
7177
7197
  verifier, challenge = create_pkce_pair()
7178
7198
  state = create_state()
7179
7199
  st.session_state[f"canva_verifier_{card_id}"] = verifier
7180
7200
  st.session_state[f"canva_state_{card_id}"] = state
7181
- st.link_button("Abrir autorização Canva", authorization_url(str(card.get("client_id")), redirect_uri, "design:content:read design:content:write", state, challenge), use_container_width=True)
7182
- st.caption("Estado OAuth: autorizado" if (card.get("oauth_token") or {}).get("access_token") else "Estado OAuth: não autorizado")
7201
+ st.session_state[f"canva_authorization_url_{card_id}"] = authorization_url(client_id, redirect_uri, "design:content:read design:content:write", state, challenge)
7202
+ authorization_link = str(st.session_state.get(f"canva_authorization_url_{card_id}") or "")
7203
+ if authorization_link:
7204
+ st.link_button("Abrir autorização Canva", authorization_link, use_container_width=True)
7205
+ with st.expander("Opções de exportação e dimensões", expanded=False):
7206
+ export_quality = st.selectbox("Export Quality", ["High", "Medium", "Low"], index=["high", "medium", "low"].index(str(card.get("export_quality") or "medium").lower()), key=f"media_card_{card_id}_export_quality")
7207
+ export_format = st.selectbox("Export Format", ["PNG", "JPG", "PDF"], index=["png", "jpg", "pdf"].index(str(card.get("export_format") or "png").lower()), key=f"media_card_{card_id}_export_format")
7208
+ dimensions = st.selectbox("Thumbnail Width / Height", ["1280 x 720", "1792 x 1024"], index=1 if str(card.get("thumbnail_width")) == "1792" or str(card.get("thumbnail_height")) == "1024" else 0, key=f"media_card_{card_id}_dimensions")
7209
+ selected_width, selected_height = dimensions.replace(" ", "").split("x", 1)
7210
+ extra_values.update({"export_quality": export_quality.lower(), "export_format": export_format.lower(), "thumbnail_width": selected_width, "thumbnail_height": selected_height})
7183
7211
  status_cols = st.columns(4)
7184
7212
  with status_cols[0]:
7185
7213
  enabled = st.checkbox("Provider activo", value=bool(card.get("enabled", True)), key=f"media_card_{card_id}_enabled")
@@ -7191,7 +7219,8 @@ def _render_media_provider_card(settings: dict[str, Any], cards: list[dict[str,
7191
7219
  priority = st.number_input("Prioridade", min_value=0, max_value=999, value=int(card.get("priority", index)), step=1, key=f"media_card_{card_id}_priority")
7192
7220
  action_cols = st.columns(4)
7193
7221
  with action_cols[0]:
7194
- refresh_clicked = st.form_submit_button("Consultar Modelos", use_container_width=True, key=f"media_card_{card_id}_refresh")
7222
+ if definition.code != "canva":
7223
+ refresh_clicked = st.form_submit_button("Consultar Modelos", use_container_width=True, key=f"media_card_{card_id}_refresh")
7195
7224
  with action_cols[1]:
7196
7225
  test_clicked = st.form_submit_button("Testar Chamada API", use_container_width=True, key=f"media_card_{card_id}_test")
7197
7226
  with action_cols[2]:
@@ -219,6 +219,18 @@ def normalize_media_card(card: Any, index: int = 0) -> dict[str, Any]:
219
219
  for field in definition.extra_fields:
220
220
  result[field] = str(source.get(field) or "").strip()
221
221
  if provider == "canva":
222
+ quality = str(source.get("export_quality") or "medium").strip().lower()
223
+ result["export_quality"] = {"regular": "medium", "default": "medium"}.get(quality, quality if quality in {"high", "medium", "low"} else "medium")
224
+ export_format = str(source.get("export_format") or "png").strip().lower()
225
+ result["export_format"] = export_format if export_format in {"png", "jpg", "pdf"} else "png"
226
+ raw_width = str(source.get("thumbnail_width") or "").strip().lower().replace("×", "x").replace(" ", "")
227
+ raw_height = str(source.get("thumbnail_height") or "").strip().lower().replace("×", "x").replace(" ", "")
228
+ combined = raw_width if "x" in raw_width else raw_height
229
+ if "x" in combined:
230
+ left, right = combined.split("x", 1)
231
+ raw_width, raw_height = left, right
232
+ result["thumbnail_width"] = raw_width if raw_width in {"1280", "1792"} else "1280"
233
+ result["thumbnail_height"] = raw_height if raw_height in {"720", "1024"} else ("1024" if result["thumbnail_width"] == "1792" else "720")
222
234
  result["oauth_token"] = dict(source.get("oauth_token") or {}) if isinstance(source.get("oauth_token"), Mapping) else {}
223
235
  if provider == "nano_banana":
224
236
  result["aspect_ratio"] = str(source.get("aspect_ratio") or INTERNAL_IMAGE_ASPECT_RATIO).strip()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.94",
3
+ "version": "0.4.96",
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,13 +26,13 @@ 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", CLICK_NO_WIN_CONSOLE: "1" };
29
+ const pythonEnvironment = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", PYTHONLEGACYWINDOWSSTDIO: "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}...`);
33
33
  const result = spawnSync(command, commandArgs, {
34
34
  stdio: "inherit",
35
- env: { ...pythonEnvironment, ...environment, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" },
35
+ env: { ...pythonEnvironment, ...environment, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", PYTHONLEGACYWINDOWSSTDIO: "1", CLICK_NO_WIN_CONSOLE: "1" },
36
36
  cwd: root,
37
37
  windowsHide: false,
38
38
  });
@@ -155,6 +155,7 @@ const runtimeEnv = {
155
155
  ...process.env,
156
156
  PYTHONIOENCODING: "utf-8",
157
157
  PYTHONUTF8: "1",
158
+ PYTHONLEGACYWINDOWSSTDIO: "1",
158
159
  CLICK_NO_WIN_CONSOLE: "1",
159
160
  THUNDERBOLT_STORAGE_DIR: storageDir,
160
161
  STREAMLIT_BROWSER_GATHER_USAGE_STATS: "false",
@@ -258,7 +259,7 @@ function startStreamlit() {
258
259
  child = spawn(python, ["-m", "streamlit", "run", main, "--server.port", String(backendPort), "--server.address", "127.0.0.1"], {
259
260
  cwd: root,
260
261
  stdio: "inherit",
261
- env: { ...runtimeEnv, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", CLICK_NO_WIN_CONSOLE: "1", THUNDERBOLT_LAUNCHER_RESTART: "1" },
262
+ env: { ...runtimeEnv, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", PYTHONLEGACYWINDOWSSTDIO: "1", CLICK_NO_WIN_CONSOLE: "1", THUNDERBOLT_LAUNCHER_RESTART: "1" },
262
263
  windowsHide: false,
263
264
  });
264
265
  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", CLICK_NO_WIN_CONSOLE: "1" };
45
+ const pythonEnvironment = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1", PYTHONLEGACYWINDOWSSTDIO: "1", CLICK_NO_WIN_CONSOLE: "1" };
46
46
 
47
47
  function legacyRoots() {
48
48
  const userProfile = process.env.USERPROFILE || home;