@danhachuel/thunderbolt 0.3.78 → 0.3.80
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 +28 -2
- package/hermes_ui/pipeline_worker.py +2 -0
- package/package.json +1 -1
- package/scripts/cli.mjs +11 -2
- package/seed/skills/mpt_agent.py +30 -1
package/app/main.py
CHANGED
|
@@ -5397,11 +5397,25 @@ def _render_api_test_feedback(settings: dict[str, Any], test_key: str, result: d
|
|
|
5397
5397
|
st.error(ui_text("Último teste: chamada falhou", language) + suffix)
|
|
5398
5398
|
|
|
5399
5399
|
|
|
5400
|
-
def _render_api_test_control(
|
|
5401
|
-
|
|
5400
|
+
def _render_api_test_control(
|
|
5401
|
+
settings: dict[str, Any],
|
|
5402
|
+
test_key: str,
|
|
5403
|
+
callback: Any,
|
|
5404
|
+
*,
|
|
5405
|
+
widget_key: str,
|
|
5406
|
+
persist_callback: Any = None,
|
|
5407
|
+
) -> None:
|
|
5408
|
+
"""Render a form-safe diagnostic button and persist its redacted result.
|
|
5409
|
+
|
|
5410
|
+
``persist_callback`` keeps a card whose fields live in the global form
|
|
5411
|
+
consistent: testing it must not appear successful while its values remain
|
|
5412
|
+
only in the browser session.
|
|
5413
|
+
"""
|
|
5402
5414
|
if st.form_submit_button("Testar chamada API", use_container_width=True, key=widget_key):
|
|
5403
5415
|
with st.spinner(ui_text("A testar chamada API…", current_ui_language())):
|
|
5404
5416
|
try:
|
|
5417
|
+
if persist_callback is not None:
|
|
5418
|
+
persist_callback()
|
|
5405
5419
|
result = callback()
|
|
5406
5420
|
except Exception:
|
|
5407
5421
|
result = {"status": "error", "message": "A chamada de diagnóstico falhou."}
|
|
@@ -5832,11 +5846,23 @@ def render_settings():
|
|
|
5832
5846
|
azure_speech_key = text_setting("Azure Speech key", "azure_speech_key", secret=True)
|
|
5833
5847
|
_render_credential_status(azure_speech_key)
|
|
5834
5848
|
azure_speech_region = text_setting("Azure Speech region", "azure_speech_region")
|
|
5849
|
+
|
|
5850
|
+
def save_azure_speech() -> None:
|
|
5851
|
+
settings.update({
|
|
5852
|
+
"azure_speech_key": azure_speech_key.strip(),
|
|
5853
|
+
"azure_speech_region": azure_speech_region.strip(),
|
|
5854
|
+
})
|
|
5855
|
+
write_json("settings.json", settings)
|
|
5856
|
+
|
|
5857
|
+
if st.form_submit_button("Guardar Azure Speech", type="primary", use_container_width=True, key="save_azure_speech"):
|
|
5858
|
+
save_azure_speech()
|
|
5859
|
+
st.success("Azure Speech guardado.")
|
|
5835
5860
|
_render_api_test_control(
|
|
5836
5861
|
settings,
|
|
5837
5862
|
"voice:azure_speech",
|
|
5838
5863
|
lambda: test_voice_provider("azure_speech", {"azure_speech_key": azure_speech_key, "azure_speech_region": azure_speech_region}),
|
|
5839
5864
|
widget_key="api_test_voice_azure",
|
|
5865
|
+
persist_callback=save_azure_speech,
|
|
5840
5866
|
)
|
|
5841
5867
|
|
|
5842
5868
|
with st.container(border=True):
|
|
@@ -807,6 +807,8 @@ def _run_video_helper(task: dict[str, Any]) -> Path:
|
|
|
807
807
|
"MPT_PEXELS_API_KEYS": json.dumps(source_keys, ensure_ascii=False) if route == "pexels" and source_keys else "",
|
|
808
808
|
"MPT_PIXABAY_API_KEY": source_keys[0] if route == "pixabay" and source_keys else "",
|
|
809
809
|
"MPT_PIXABAY_API_KEYS": json.dumps(source_keys, ensure_ascii=False) if route == "pixabay" and source_keys else "",
|
|
810
|
+
"MPT_AZURE_SPEECH_KEY": str(settings.get("azure_speech_key") or "").strip(),
|
|
811
|
+
"MPT_AZURE_SPEECH_REGION": str(settings.get("azure_speech_region") or "").strip(),
|
|
810
812
|
}
|
|
811
813
|
for key, value in env_values.items():
|
|
812
814
|
if value:
|
package/package.json
CHANGED
package/scripts/cli.mjs
CHANGED
|
@@ -193,6 +193,11 @@ const proxy = http.createServer((request, response) => {
|
|
|
193
193
|
});
|
|
194
194
|
|
|
195
195
|
proxy.on("upgrade", (request, clientSocket, head) => {
|
|
196
|
+
// Clientes remotos podem encerrar o WebSocket antes de o Streamlit concluir
|
|
197
|
+
// a resposta. Tratar ECONNRESET impede que o Node termine o launcher.
|
|
198
|
+
clientSocket.on("error", () => {
|
|
199
|
+
if (!clientSocket.destroyed) clientSocket.destroy();
|
|
200
|
+
});
|
|
196
201
|
const upstreamSocket = net.connect(backendPort, "127.0.0.1", () => {
|
|
197
202
|
const headers = Object.entries(request.headers)
|
|
198
203
|
.map(([name, value]) => `${name}: ${Array.isArray(value) ? value.join(", ") : value}`)
|
|
@@ -201,10 +206,14 @@ proxy.on("upgrade", (request, clientSocket, head) => {
|
|
|
201
206
|
if (head.length) upstreamSocket.write(head);
|
|
202
207
|
clientSocket.pipe(upstreamSocket).pipe(clientSocket);
|
|
203
208
|
});
|
|
204
|
-
upstreamSocket.on("error", () =>
|
|
209
|
+
upstreamSocket.on("error", () => {
|
|
210
|
+
if (!clientSocket.destroyed) clientSocket.destroy();
|
|
211
|
+
});
|
|
205
212
|
});
|
|
206
213
|
|
|
207
|
-
|
|
214
|
+
// O backend Streamlit fica em 127.0.0.1; apenas o proxy escuta em todas as
|
|
215
|
+
// interfaces para que o encaminhamento seguro do ambiente consiga alcançá-lo.
|
|
216
|
+
proxy.listen(publicPort, () => {
|
|
208
217
|
console.log(`Thunderbolt: interface disponível em http://localhost:${publicPort}/`);
|
|
209
218
|
});
|
|
210
219
|
const worker = spawn(python, ["-m", "hermes_ui.automation_worker"], {
|
package/seed/skills/mpt_agent.py
CHANGED
|
@@ -203,6 +203,27 @@ def _replace_config_value(text: str, key: str, value: object) -> str:
|
|
|
203
203
|
return pattern.sub(lambda match: f"{match.group(1)}{encoded}", text, count=1)
|
|
204
204
|
|
|
205
205
|
|
|
206
|
+
def _replace_toml_section_value(text: str, section: str, key: str, value: object) -> str:
|
|
207
|
+
"""Replace or append a scalar value inside one TOML section."""
|
|
208
|
+
encoded = json.dumps(value, ensure_ascii=False)
|
|
209
|
+
section_pattern = re.compile(
|
|
210
|
+
rf"(?ms)^\[{re.escape(section)}\][ \t]*(?:\n|$)(.*?)(?=^\[|\Z)"
|
|
211
|
+
)
|
|
212
|
+
section_match = section_pattern.search(text)
|
|
213
|
+
if section_match is None:
|
|
214
|
+
separator = "" if not text.strip() else "\n\n"
|
|
215
|
+
return f"{text.rstrip()}{separator}[{section}]\n{key} = {encoded}\n"
|
|
216
|
+
body = section_match.group(1)
|
|
217
|
+
value_pattern = re.compile(rf"(?m)^({re.escape(key)}\s*=\s*).*$")
|
|
218
|
+
if value_pattern.search(body):
|
|
219
|
+
updated_body = value_pattern.sub(
|
|
220
|
+
lambda match: f"{match.group(1)}{encoded}", body, count=1
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
updated_body = f"{body.rstrip()}\n{key} = {encoded}\n"
|
|
224
|
+
return f"{text[:section_match.start(1)]}{updated_body}{text[section_match.end(1):]}"
|
|
225
|
+
|
|
226
|
+
|
|
206
227
|
def _has_configured_value(value: str) -> bool:
|
|
207
228
|
"""Treat empty strings and whitespace-only key arrays as unconfigured."""
|
|
208
229
|
if not value:
|
|
@@ -237,13 +258,15 @@ def apply_environment_config(config_path: Path) -> None:
|
|
|
237
258
|
model_name = os.environ.get("MPT_LLM_MODEL_NAME", "").strip()
|
|
238
259
|
pexels_key = os.environ.get("MPT_PEXELS_API_KEY", "").strip()
|
|
239
260
|
pixabay_key = os.environ.get("MPT_PIXABAY_API_KEY", "").strip()
|
|
261
|
+
azure_speech_key = os.environ.get("MPT_AZURE_SPEECH_KEY", "").strip()
|
|
262
|
+
azure_speech_region = os.environ.get("MPT_AZURE_SPEECH_REGION", "").strip()
|
|
240
263
|
pexels_keys = _parse_string_list(os.environ.get("MPT_PEXELS_API_KEYS", ""))
|
|
241
264
|
pixabay_keys = _parse_string_list(os.environ.get("MPT_PIXABAY_API_KEYS", ""))
|
|
242
265
|
if not pexels_keys and pexels_key:
|
|
243
266
|
pexels_keys = [pexels_key]
|
|
244
267
|
if not pixabay_keys and pixabay_key:
|
|
245
268
|
pixabay_keys = [pixabay_key]
|
|
246
|
-
if not any((provider, llm_key, base_url, model_name, pexels_keys, pixabay_keys)):
|
|
269
|
+
if not any((provider, llm_key, base_url, model_name, pexels_keys, pixabay_keys, azure_speech_key, azure_speech_region)):
|
|
247
270
|
return
|
|
248
271
|
|
|
249
272
|
text = config_path.read_text(encoding="utf-8")
|
|
@@ -268,6 +291,12 @@ def apply_environment_config(config_path: Path) -> None:
|
|
|
268
291
|
if pixabay_keys:
|
|
269
292
|
text = _replace_config_value(text, "pixabay_api_keys", pixabay_keys)
|
|
270
293
|
changes.append("pixabay_api_keys")
|
|
294
|
+
if azure_speech_key:
|
|
295
|
+
text = _replace_toml_section_value(text, "azure", "speech_key", azure_speech_key)
|
|
296
|
+
changes.append("azure.speech_key")
|
|
297
|
+
if azure_speech_region:
|
|
298
|
+
text = _replace_toml_section_value(text, "azure", "speech_region", azure_speech_region)
|
|
299
|
+
changes.append("azure.speech_region")
|
|
271
300
|
_atomic_write_text(config_path, text)
|
|
272
301
|
log("updated configuration fields: " + ", ".join(changes))
|
|
273
302
|
|