@danhachuel/thunderbolt 0.7.51 → 0.7.53
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/social_networks_ui.py +4 -2
- package/hermes_ui/storage.py +32 -1
- package/package.json +1 -1
- package/scripts/cli.mjs +11 -8
|
@@ -506,7 +506,8 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
|
|
|
506
506
|
st.write(f"**{_clean(profile.get('name')) or 'Sem nome'}**")
|
|
507
507
|
st.caption(f"{_clean(profile.get('handle')) or _clean(profile.get('url')) or 'sem handler'}")
|
|
508
508
|
_render_instagram_bio(_profile_bio(profile))
|
|
509
|
-
|
|
509
|
+
country = _profile_country(profile)
|
|
510
|
+
st.caption(f"{_country_display(country) if country else 'País não definido'} · {_clean(profile.get('language')) or 'Idioma não definido'}")
|
|
510
511
|
with header_cols[2]:
|
|
511
512
|
st.metric("posts", _metric(_profile_metric(profile, "post_count", "posts", "media_count")))
|
|
512
513
|
with header_cols[3]:
|
|
@@ -587,7 +588,8 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
|
|
|
587
588
|
with block_cols[1]:
|
|
588
589
|
st.markdown(f"**handler**\n\n{_clean(profile.get('handle')) or '—'}")
|
|
589
590
|
with block_cols[2]:
|
|
590
|
-
|
|
591
|
+
country = _profile_country(profile)
|
|
592
|
+
st.markdown(f"**País**\n\n{_country_display(country) if country else '—'}")
|
|
591
593
|
with block_cols[3]:
|
|
592
594
|
st.markdown(f"**Idioma**\n\n{_clean(profile.get('language')) or '—'}")
|
|
593
595
|
|
package/hermes_ui/storage.py
CHANGED
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import shutil
|
|
6
6
|
import tempfile
|
|
7
7
|
import time
|
|
8
|
+
from threading import RLock
|
|
8
9
|
from contextlib import contextmanager
|
|
9
10
|
from copy import deepcopy
|
|
10
11
|
from datetime import datetime, timezone
|
|
@@ -24,6 +25,9 @@ SEED_BLUEPRINTS = ROOT / "seed" / "blueprints"
|
|
|
24
25
|
SEED_THUMBNAIL_BLUEPRINTS = SEED_BLUEPRINTS / "thumbnails"
|
|
25
26
|
SEED_TIKTOK_PROMPT_MASTERS = ROOT / "seed" / "prompt_masters"
|
|
26
27
|
|
|
28
|
+
_READ_CACHE_LOCK = RLock()
|
|
29
|
+
_READ_CACHE: dict[str, tuple[tuple[int, int], Any]] = {}
|
|
30
|
+
|
|
27
31
|
DEFAULTS: dict[str, Any] = {
|
|
28
32
|
"channels.json": [],
|
|
29
33
|
"channel_videos.json": [],
|
|
@@ -720,6 +724,19 @@ def _load_json_unlocked(path: Path) -> Any:
|
|
|
720
724
|
return json.load(handle)
|
|
721
725
|
|
|
722
726
|
|
|
727
|
+
def _read_fingerprint(path: Path) -> tuple[int, int] | None:
|
|
728
|
+
try:
|
|
729
|
+
stat = path.stat()
|
|
730
|
+
except OSError:
|
|
731
|
+
return None
|
|
732
|
+
return stat.st_mtime_ns, stat.st_size
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def _invalidate_read_cache(path: Path) -> None:
|
|
736
|
+
with _READ_CACHE_LOCK:
|
|
737
|
+
_READ_CACHE.pop(str(path), None)
|
|
738
|
+
|
|
739
|
+
|
|
723
740
|
def _recover_json_unlocked(name: str, path: Path, default: Any | None) -> Any:
|
|
724
741
|
backup = _corrupt_backup(path)
|
|
725
742
|
if name in _PROTECTED_STATE_FILES:
|
|
@@ -743,6 +760,13 @@ def _recover_json_unlocked(name: str, path: Path, default: Any | None) -> Any:
|
|
|
743
760
|
def read_json(name: str, default: Any | None = None) -> Any:
|
|
744
761
|
ensure_storage()
|
|
745
762
|
path = STATE / name
|
|
763
|
+
fingerprint = _read_fingerprint(path)
|
|
764
|
+
cache_key = str(path)
|
|
765
|
+
if fingerprint is not None:
|
|
766
|
+
with _READ_CACHE_LOCK:
|
|
767
|
+
cached = _READ_CACHE.get(cache_key)
|
|
768
|
+
if cached is not None and cached[0] == fingerprint:
|
|
769
|
+
return deepcopy(cached[1])
|
|
746
770
|
with _state_lock(path, read_only=True):
|
|
747
771
|
try:
|
|
748
772
|
data = _load_json_unlocked(path)
|
|
@@ -753,7 +777,12 @@ def read_json(name: str, default: Any | None = None) -> Any:
|
|
|
753
777
|
migrated, changed = _migrate_settings(data)
|
|
754
778
|
if changed:
|
|
755
779
|
_atomic_write_unlocked(path, migrated)
|
|
756
|
-
|
|
780
|
+
result = migrated
|
|
781
|
+
current_fingerprint = _read_fingerprint(path)
|
|
782
|
+
if current_fingerprint is not None:
|
|
783
|
+
with _READ_CACHE_LOCK:
|
|
784
|
+
_READ_CACHE[cache_key] = (current_fingerprint, deepcopy(result))
|
|
785
|
+
return deepcopy(result)
|
|
757
786
|
|
|
758
787
|
|
|
759
788
|
def update_json(name: str, default: Any, mutator: Callable[[Any], Any]) -> Any:
|
|
@@ -767,6 +796,7 @@ def update_json(name: str, default: Any, mutator: Callable[[Any], Any]) -> Any:
|
|
|
767
796
|
current = _recover_json_unlocked(name, path, default)
|
|
768
797
|
result = mutator(current)
|
|
769
798
|
_atomic_write_unlocked(path, current)
|
|
799
|
+
_invalidate_read_cache(path)
|
|
770
800
|
return result
|
|
771
801
|
|
|
772
802
|
|
|
@@ -774,6 +804,7 @@ def write_json(name: str, data: Any) -> None:
|
|
|
774
804
|
"""Persist a state JSON file using :func:`atomic_write`."""
|
|
775
805
|
ensure_storage()
|
|
776
806
|
atomic_write(STATE / name, data)
|
|
807
|
+
_invalidate_read_cache(STATE / name)
|
|
777
808
|
|
|
778
809
|
|
|
779
810
|
def append_json(name: str, item: dict[str, Any]) -> dict[str, Any]:
|
package/package.json
CHANGED
package/scripts/cli.mjs
CHANGED
|
@@ -262,16 +262,19 @@ const proxy = http.createServer((request, response) => {
|
|
|
262
262
|
headers: { ...request.headers, host: `127.0.0.1:${backendPort}`, "accept-encoding": "identity" },
|
|
263
263
|
}, (upstreamResponse) => {
|
|
264
264
|
const responseHeaders = { ...upstreamResponse.headers };
|
|
265
|
-
// Streamlit entrega HTML/JS de uma instância local que pode mudar após
|
|
266
|
-
// uma atualização via npx. Não permitir que o browser reaproveite uma
|
|
267
|
-
// página ou bundle da instância anterior.
|
|
268
|
-
delete responseHeaders.etag;
|
|
269
|
-
delete responseHeaders["last-modified"];
|
|
270
|
-
responseHeaders["cache-control"] = "no-store, no-cache, must-revalidate, max-age=0";
|
|
271
|
-
responseHeaders.pragma = "no-cache";
|
|
272
|
-
responseHeaders.expires = "0";
|
|
273
265
|
const contentType = String(responseHeaders["content-type"] || "").toLowerCase();
|
|
274
266
|
const isHtml = contentType.includes("text/html") && !responseHeaders["content-encoding"];
|
|
267
|
+
// O documento HTML contém o estado da sessão e pode mudar após uma
|
|
268
|
+
// actualização via npx; os bundles estáticos do Streamlit, por outro lado,
|
|
269
|
+
// devem ser reutilizados pelo browser. Desactivar cache em todos os
|
|
270
|
+
// recursos tornava cada aba muito mais pesada e multiplicava downloads.
|
|
271
|
+
if (isHtml) {
|
|
272
|
+
delete responseHeaders.etag;
|
|
273
|
+
delete responseHeaders["last-modified"];
|
|
274
|
+
responseHeaders["cache-control"] = "no-store, no-cache, must-revalidate, max-age=0";
|
|
275
|
+
responseHeaders.pragma = "no-cache";
|
|
276
|
+
responseHeaders.expires = "0";
|
|
277
|
+
}
|
|
275
278
|
if (!isHtml) {
|
|
276
279
|
response.writeHead(upstreamResponse.statusCode || 502, responseHeaders);
|
|
277
280
|
upstreamResponse.pipe(response);
|