Spaces:
Running
Running
Commit History
23 days ago
-
fix(dub): VieNeu nạp được với onnxruntime mới — tải model thành file thật
Claude +1: Claude Opus 5.5Unverified signatureLỗi trên Space: "External data path escapes model directory". Cache Hugging Face để mỗi file ở một blob riêng, thư mục snapshot chỉ là symlink -> .onnx và vieneu_backbone_shared.data phân giải ra hai thư mục khác nhau, onnxruntime mới (tái hiện được với 1.29) từ chối nạp. VieNeu hỏng -> tiếng Việt câm. Nay VieNeu tải model bằng local_dir (file thật, nằm cạnh nhau) trong HF_HOME. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwiJZLUb773B8NwksxfTU92f83e46
25 days ago
-
fix(dub): Edge bị chặn 403 không còn kéo VieNeu và cả tool câm theo
Claude +1: Claude Opus 5.5Unverified signatureLỗi trên Space: Microsoft trả 403 khi bắt tay WebSocket với Edge (edge-tts đã là bản mới nhất, nên không phải do thư viện cũ). Tiếng Việt cũng câm vì VieNeu từng nạp hỏng một lần và load_engine gán luôn Edge vào chỗ VieNeu tới lần restart. - load_engine: nạp hỏng thì Edge chỉ đứng TẠM, sau 5 phút nạp lại; nạp model trong khoá để job và việc làm sẵn giọng thử lúc khởi động không nạp song song. - synth_line: Edge cũng hỏng -> en/fil đọc bằng OmniVoice (local), giọng cùng giới tính với giọng Edge đang chọn. - Thêm env EDGE_TTS_PROXY để Edge đi qua proxy khi IP máy chủ bị chặn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwiJZLUb773B8NwksxfTU98b45574
-
fix(down list): trục trặc relay thì dừng ngay, đừng thử 4 site vô ích
Đo thật trên Space sau khi deploy: một link Douyin mất 228 GIÂY mới chịu báo lỗi, vì Worker relay không phản hồi mà code vẫn kiên nhẫn thử 4 site x 3 lần, mỗi lần chờ hết timeout 30s. Trước khi có nhánh site tải hộ thì nó hỏng nhanh, nên đây là hồi quy do chính tôi gây ra. Nay tách bạch "site nói không có video" với "trục trặc ở chính relay": - Gọi relay ném exception -> RelayProblem, dừng cả chuỗi ngay, kèm loại lỗi thật (vd OSError: ...) để biết đường mà sửa thay vì đoán. - Worker bản cũ trả JSON thiếu-tham-số -> cũng RelayProblem như trước. - Timeout POST qua relay 30s -> 12s. 120/120 test PASS (thêm test bắt đúng ca 228s này). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>bf478ef -
fix(down list): /api/download/start tải song song — chỗ người dùng thật sự chạm
Commit trước song song hoá `download_many`, nhưng api/download.py KHÔNG gọi hàm đó: nó có vòng lặp tuần tự riêng. Nên tối ưu không chạm tới người dùng. Nay `work()` tải nhiều link cùng lúc (trần 4, đổi bằng DOWNLOAD_WORKERS). Giữ nguyên: thứ tự kết quả theo thứ tự link nhập, huỷ giữa chừng, log/tiến độ từng link; thêm: 1 link ném exception không còn giết cả mẻ. Test mới bắt đúng lỗi này (4 link x 0.4s phải xong dưới 1.2s). 119/119 PASS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>8ae4ca6 -
feat(down list): port đường tải qua site tải hộ từ tool local down_ads
Bản web mới chỉ có yt-dlp + tikwm/ssstik, còn Douyin thì đi qua trang chia sẻ iesdouyin — mà trang đó đã bị ByteDance rút sạch dữ liệu (đo 2026-09), nên Douyin trên web coi như không tải được. Bản local down_ads đã giải bằng cách hỏi các SITE TẢI HỘ; nay bê phần đó sang. Thêm backend/app/services/downloader/webdl.py (port từ down_ads/core/webdl.py): - 4 site: savetik, tiktokio, snapsave, lovetik; parser CHUNG (quét URL giống link media, không dò thẻ/class) nên site đổi giao diện vẫn chạy. - Lưới cuối: không khớp tên miền CDN nào thì quét mọi URL rồi HỎI SERVER cái nào là video qua Content-Type — không cần biết trước tên miền. - Đo dung lượng thật bằng Range rồi lấy bản to nhất; nhãn "MP4 HD" của savetik trỏ file 576p nhỏ hơn cả link thường, không tin được. - Bóc link CDN gốc trong token JWT của snapcdn; giữ token nguyên vẹn lúc tải vì token dùng được đúng MỘT LẦN. - Giữ nhịp hỏi theo từng site (savetik chịu ~1 request/2.5s). Nối vào ydl.py: - douyin: site tải hộ -> iesdouyin …7196abe -
fix(cut): cắt 2+ video không còn 500; tách khúc tự động nhanh gấp ~3 lần
Lỗi: thả nhiều video một lượt lúc chưa có video nào -> `cur` là biến chụp lúc render nên mọi lần upload đều gửi session rỗng -> backend tạo mỗi video một phiên riêng. Lúc cắt, FE chỉ gửi phiên của video đầu nên video thứ 2 trở đi không tìm thấy file, ffprobe chết, cả request 500 "Internal Server Error". Một video thì không bao giờ dính. Sửa lỗi: - FE giữ sid chạy theo vòng lặp upload -> mọi video vào cùng một phiên; và gửi kèm session của TỪNG video khi cắt để các phiên cũ đã lỡ tách vẫn chạy. - resolve_src() chặn thoát thư mục phiên; probe() báo lý do đọc được thay vì CalledProcessError trống trơn. - execute-batch đưa việc nặng vào job: 1 video hỏng chỉ hỏng phần của nó, hỏng hết thì job báo lỗi thật. - Khúc end <= start trước đây bị ffmpeg nuốt im lặng (-frames:v âm = không giới hạn) và xuất ra clip dài tới hết video -> nay chặn. - alert hiện `detail` thay vì nguyên cục JSON. Tăng tốc (đo thật, video QC 1080x1920 30s): - Tách khúc tự động 11.9s -> 3.9s/video. PySceneDetect …07d3e14
-
feat(dịch): cạn quota cả 4 key thì xoay sang model khác thay vì rơi xuống Groq
Quota miễn phí của Gemini tính RIÊNG THEO TỪNG MODEL, nên khi cả 4 key đều 429 với model chính thì quota của model khác vẫn còn nguyên — dịch bằng model đó vẫn hơn hẳn rơi xuống Groq/deep-translator. - Thêm GEMINI_MODEL_CHAIN (mặc định gemini-3.1-flash-lite, gemini-3.5-flash, gemini-flash-latest): chuỗi model thử tiếp sau model chính + model dự phòng. Đo thật: bí danh '-latest' DÙNG CHUNG túi quota với bản đánh số (gemini-flash-lite-latest = 3.5-flash-lite, gemini-flash-latest = 3.8-flash), nên chuỗi chỉ xếp model khác túi mới có tác dụng. - Cooldown và con trỏ key nay tính theo CẶP (key, model) chứ không theo key: key cạn quota model này vẫn được ưu tiên dùng cho model khác. - _gemini vét hai lượt: lượt đầu chỉ dùng cặp còn khoẻ (đủ mọi model), lượt sau mới đụng cặp đang nghỉ — thà gọi model còn quota hơn đốt lại túi vừa cạn. - Cặp (key, model) bị 404 ("no longer available to new users") nhớ trong _km_404, khỏi thử lại; mỗi ngày mới xoá đi dò lại một lần. - check_gemini…db170a7 -
fix(dịch): sang ngày mới xoay lại từ key 1; chuyển cả 4 key sang gemini-3.5-flash-lite
Xoay key: trước đây con trỏ key dính lại ở key cuối còn sống của hôm trước, sáng hôm sau vẫn đốt tiếp key đó dù quota mọi key đã nạp lại, lại còn bị cooldown cũ (tối đa 1h) chặn. Nay `_reset_if_new_day()` so ngày theo giờ Thái Bình Dương — đúng mốc Google nạp quota ngày — sang ngày mới thì về key 1 và xoá sạch cooldown. Model: quota miễn phí tính RIÊNG THEO MODEL. Đo thực tế trên chính 4 key đang dùng: họ 'flash' (2.5-flash, 3.5-flash, 3.8-flash, gemini-flash-latest) chỉ 5 request/phút/key, họ 'flash-lite' được 15 — gấp 3. gemini-3.5-flash-lite bật thinkingLevel 'medium' dịch bám hạn ký tự và tự nhiên ngang 3.5-flash (~5s cho cả batch), nên lấy làm model chính cho cả 4 key, dự phòng gemini-flash-lite-latest. Gemini 3.x chỉnh suy nghĩ bằng `thinkingLevel`, 2.5 dùng `thinkingBudget` — gửi nhầm loại là 400, nên `_gemini_once` thử lần lượt rồi nhớ config dùng được theo từng model. Thêm GEMINI_THINKING_LEVEL (mặc định medium) để chỉnh không cần sửa code. Test: 94/94 PASS (thêm 2 test: re…c969932
-
fix(core): mọi tool đều giữ chỗ phiên khi chạy, thêm lớp chặn cho việc chạy đồng bộ
Nối tiếp 20e69c0 (mới bọc riêng Dịch/đổi giọng). Cut video, Xào video và Down list dùng chung cơ chế phiên nên vẫn dính: bộ dọn phiên quá hạn có thể xoá trúng thư mục job đang ghi. - Chuyển hàm bọc thành submit_for_session() dùng chung trong core/jobs.py; cả 4 tool (11 chỗ tạo job) đi qua đó, không còn jobs.submit trần. - Listing ảnh chạy THẲNG TRONG REQUEST, không qua job nên không đăng ký được vào sổ phiên-đang-bận. Thêm lớp chặn thứ hai ngay trong cleanup_old: quét sâu tìm dấu vết ghi mới, thấy là bỏ qua. Dừng sớm khi gặp file mới và có trần số mục để phiên vài trăm video không làm chậm đợt dọn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>c8ce1a0 -
fix(dub): dọn phiên quá hạn xoá trúng phiên đang chạy -> job chết, trả video gốc
Lỗi người dùng gặp: Không tổng hợp giọng được nên trả video gốc: ffmpeg ... anullsrc ... line_0000.wav returned non-zero exit status 254 và last_error trên Space: FileNotFoundError: '/data/tnt_work/dub_.../work/tts/line_0000.wav.mp3' Cả engine chính, Edge đọc đỡ, lẫn bước tạo file lặng đều không ghi nổi file -> thư mục làm việc đã BIẾN MẤT giữa job. Thủ phạm là cleanup_old: nó xét tuổi bằng mtime của THƯ MỤC PHIÊN, mà mtime thư mục chỉ đổi khi thêm/bớt mục con TRỰC TIẾP. Job ghi sâu bên trong (out/<video>/work/tts/...) không hề chạm tới mtime đó, nên phiên đang xử lý vẫn bị tính là quá hạn và bị rmtree ngay giữa chừng (sweep chạy ké mỗi lần có người mở phiên mới). - Đếm job đang chạy theo phiên (session_in_use); cleanup_old bỏ qua phiên bận. - cleanup_old cũng thôi đụng thư mục dùng chung (voice_previews) — không phải phiên thì không có "hạn". - Mọi bước ghi file TTS tự dựng lại thư mục nếu mất, để một sự cố tương tự chỉ làm chậm chứ không giết cả job. Co-Authored-By: Clau…20e69c0 -
feat(báo cáo): dựng lại tab thời lượng theo mẫu, CSV đọc được bằng tiếng Việt
Bảng nhân sự: cột "Xong" chỉ in SỐ việc xong, bỏ phần trăm đi kèm — cột này đứng cạnh cột "Số task" nên tỷ lệ tự đọc ra được, in thêm % chỉ làm ô chật. Thêm cột Team/BU tách từ tên đăng nhập kiểu "họ tên - team - bu". Giao diện dựng theo hai file mẫu đã chốt form: tiêu đề + thanh lọc (người dùng / công cụ / từ ngày / đến ngày) + ba nhóm ô chỉ số có gạch phân cách, rồi tới các card nội dung. Bật lại bảng "Tốc độ máy mỗi bước (B2)". Chỉ số mới trong ETL: số user đã dùng thật (không tính người chỉ ngó trang), dùng hàng tuần/hàng ngày — đếm theo mốc MỚI NHẤT trong khoảng lọc chứ không so với hôm nay, để xem lại báo cáo cũ vẫn đúng. CSV tải về: mỗi cột là một câu tiếng Việt, cục properties_json được bung thành "Bước máy: Bóc lời & dịch · Lần bấm chạy thứ: 1". Cột mã và JSON gốc dồn về cuối để vẫn dò ngược được. `?raw=1` giữ nguyên 11 cột schema v2 cho bên nào cần nạp vào công cụ khác. Bảng dịch để một chỗ ở labels.py, màn "Nhật ký chi tiết" đọc chung nên không lệch với file tải về; khoá …4ffa05a
-
feat(dub): en/fil — giọng dựng sẵn chạy Edge, OmniVoice chỉ lo phần clone
Đo thật trên Space: OmniVoice trên CPU mất 47.8s để đọc 2.83s tiếng (~17x thời gian thực), giọng khác 61.7s cho 2.92s (~21x). Quy ra video 60s có 50s lời thoại = 14-17 phút chỉ riêng khâu đọc, trong khi Edge làm cả video ~10s (8 luồng song song). Không đáng đánh đổi cho giọng dựng sẵn. Nay một ngôn ngữ chạy HAI engine: giọng dựng sẵn = Edge (nhanh như cũ), clone = OmniVoice — thứ Edge hoàn toàn không làm được, đáng để chờ. pick_engine chỉ chọn omni khi CÓ MẪU GIỌNG thật (speaker_wav hoặc clone-preset); bấm "Clone từ video" mà chưa cắt được mẫu thì vẫn Edge. /api/dub/voices trả riêng hai vế (engine/model_label cho giọng sẵn, clone_engine/clone_label/clone_slow cho clone). Giao diện báo trước ngay dưới nút chọn nguồn giọng: mỗi 1 giây lời thoại tốn ~15-20 giây xử lý, kẻo user tưởng treo máy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>54ef81a -
fix(dub): cache giọng thử đặt tên theo ĐỊNH NGHĨA giọng, không chỉ theo id
Bản instruct đã sửa lên Space rồi (nhãn giọng mới, last_error rỗng) nhưng /voice-preview vẫn trả y nguyên 220110 byte suốt 19 phút: đó là bản do Edge đọc đỡ hồi OmniVoice còn hỏng, đã nằm trong cache. Nó NGHE RA TIẾNG nên phép kiểm "file câm" thêm hôm trước không bắt được, mà STORAGE_DIR thì sống qua cả lần build lại -> nghe giọng Edge mãi dù model đã chạy đúng. Tên file cache nay kèm VÂN TAY của định nghĩa giọng (engine + chuỗi instruct thật): đổi engine hoặc sửa preset là bản cũ tự hết hiệu lực. Ghi bản mới thì xoá luôn bản cũ cùng giọng cho khỏi đọng rác. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>92f69cd -
fix(dub): instruct của OmniVoice phải là THẺ thuộc tính, không phải văn xuôi
Lỗi thật đọc được từ Space: ValueError: Unsupported instruct items found in "An adult female speaker with a soft, expressive voice and a British accent..." `instruct` không nhận mô tả tự do — nó là danh sách thẻ có sẵn ngăn bằng dấu phẩy (omnivoice/utils/voice_design.py), mỗi nhóm chọn đúng một thẻ: male|female · child|teenager|young adult|middle-aged|elderly · very low..very high pitch · whisper · <quốc gia> accent Mấy câu mô tả tôi tự nghĩ ra sai hết -> MỌI câu en/fil đều hỏng, phải nhờ Edge đọc đỡ nên nghe ra giọng Edge dù nhãn ghi OmniVoice. - Viết lại 10 giọng preset bằng thẻ hợp lệ. - BỎ ngữ điệu khỏi instruct: không có thẻ nào tả ngữ điệu (quảng cáo/đọc tin/kể chuyện), nhét vào là hỏng câu. Ngữ điệu vẫn dùng cho VieNeu. - fil không có thẻ chất giọng riêng -> bỏ trống, chốt tiếng bằng tham số `language` ("en"/"fil", khớp LANG_NAME_TO_ID của model). - Test khoá từ vựng lại: mọi preset chỉ được dùng thẻ hợp lệ, mỗi nhóm một thẻ, và instruct không được chứa chữ tả ng…7a822fa -
fix(dub): bỏ bản giọng thử CÂM đã nằm trong cache
Đo trên Space: /voice-preview trả đúng 19278 byte lặng ở MỌI lần gọi, thời gian 1.3s, mà `last_error` vẫn rỗng -> không hề có lần tổng hợp nào chạy. Bản lặng do lần TTS hỏng trước ghi ra đã nằm trong STORAGE_DIR và sống qua cả lần build lại, nên phép thử cũ ("có file và > 44 byte") luôn đúng và đường đi không bao giờ chạm tới engine — vừa câm vừa không ghi nổi lỗi. Nay cache chỉ tính là dùng được khi file KHÔNG câm; câm thì xoá, làm lại. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>268fabb -
fix(dub): TTS hỏng thì đọc bằng Edge, không trả cả video tiếng lặng
Test thật trên Space: /voice-preview trả về ĐÚNG 0.4s im lặng tuyệt đối — là nhánh _silence(0.4) của synth_line, tức OmniVoice ném lỗi ở MỌI câu. Hai lỗi lộ ra: 1. synth_line nuốt lỗi thành khoảng lặng và KHÔNG log gì. Hỏng 1 câu thì không sao, nhưng engine hỏng hệ thống (model chưa tải xong, thiếu RAM) thì cả video câm mà chẳng báo gì. Nay hỏng -> đọc lại bằng Edge TTS (online, gần như luôn có); chỉ khi Edge cũng hỏng mới trả khoảng lặng. 2. _ensure_preview cache luôn bản câm đó -> giọng thử câm vĩnh viễn kể cả sau khi model đã chạy được. Nay phát hiện file câm thì xoá + báo lỗi. Thêm đường chẩn đoán TỪ XA: ghi lỗi gần nhất của từng engine và trả qua /api/dub/voices (`last_error`) — trên Space không xem log tiện, mà triệu chứng thì chỉ là "nghe không thấy gì". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>35076e5 -
build(hf): cài OmniVoice trên Space, tránh gradio đè web stack
- Dockerfile cài `omnivoice` bằng --no-deps + đúng các gói đường suy luận (transformers>=5.3, accelerate, soundfile, pydub, librosa). Metadata của omnivoice khai cả `gradio`/`webdataset`/`tensorboardx` — chỉ dùng cho demo UI và huấn luyện. Dựng thử cây phụ thuộc: cài trọn bộ kéo gradio 6 -> starlette 1.x + fastapi 0.141 + pydantic 2.13 ĐÈ LÊN web stack đang chạy. - Thêm chốt an toàn ngay trong build: import fastapi/pydantic/cv2/numpy/ gradio_client/transformers/omnivoice — gãy thì HỎNG BUILD, không để chết lúc chạy trên Space. - Trọng số 3.3GB không nhúng vào ảnh: warm_voice_previews sẵn có chạy nền lúc khởi động sẽ nạp model (mạng nội bộ HF nhanh) -> job đầu tiên khỏi chờ. - Clone từ video: đưa luôn LỜI GỐC của đoạn mẫu (lấy `src` trong state.json, các câu chồng lấn cửa sổ 15s) sang OmniVoice qua ref_text. Không đưa thì nó tự tải Whisper large-v3-turbo ~1.6GB về bóc lại, trong khi transcript đã có sẵn từ bước ASR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.c…4a400b2 -
feat(dub): thêm OmniVoice (k2-fsa) làm engine TTS cho tiếng Anh & Filipino
en/fil trước chỉ có Edge TTS: giọng dựng sẵn, KHÔNG clone được. Nay chạy OmniVoice (k2-fsa/OmniVoice, 0.6B, 646 ngôn ngữ, ra 24 kHz) với 2 chế độ: - CLONE : có mẫu giọng (từ video hoặc file tải lên) -> ref_audio. - DESIGN : không mẫu -> "instruct" tả giọng bằng chữ. Gói sẵn 6 giọng en + 4 giọng fil (nam/nữ, Mỹ/Anh), ghép thêm ngữ điệu đang chọn (tự nhiên / quảng cáo / đọc tin / kể chuyện). CHƯA CÀI GÓI thì en/fil tự chạy Edge TTS y như cũ — quyết định lấy engine nào làm TRƯỚC bằng find_spec (không import torch trong request /voices), nên /voices trả đúng danh sách giọng của engine sẽ thật sự chạy. /api/dub/voices trả thêm engine + model_label + can_clone; giao diện bỏ hàm đoán isEdge(lang) — clone được hay không là do ENGINE quyết, không phải do tiếng gì. Edge chặn id giọng lạ (vd "omni:*" còn kẹt trong state trình duyệt) để không lỗi cả câu. Chỉnh bằng env: OMNI_DEVICE (cpu|cuda:0|mps), OMNI_STEPS (16), OMNI_ENABLED=0. Co-Authored-By: Claude Opus 5 <…6f5141d -
fix(analytics): bảng đóng góp hiện số thật; tool chưa chạy hết được 100đ "ít chạy lại"
Ảnh chụp thực tế cho thấy cột "Ít chạy lại" TOOL NÀO CŨNG 100 -> thành phần 20% mất tác dụng. Nguyên nhân: điểm kẹp sàn attempts_avg = 1.0, nên tool mở ra rồi bỏ (attempts_avg = 0) cũng hoá "ít chạy lại nhất". Bỏ kẹp sàn: chỉ so nghịch đảo giữa các tool THẬT SỰ có bấm chạy; chưa chạy lần nào = 0 điểm. Giao diện: 3 cột nhu cầu / ít chạy lại / sản phẩm giờ hiện SỐ THẬT (33 việc, 0.8 lần/việc, 26 sản phẩm) thay vì điểm quy đổi — thanh tỷ lệ vẫn vẽ theo điểm, rê chuột vào thanh mới hiện điểm. Tool chưa chạy ghi rõ "chưa chạy". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>4b0cace -
feat(analytics): xếp hạng tool đóng góp nhiều/ít nhất; tạm ẩn bảng B2
Điểm đóng góp mỗi tool (etl.score_contribution), trọng số nghiệp vụ chốt: 40% nhu cầu người dùng — số việc người giao cho tool 20% ít phải bấm chạy lại — A4 nghịch đảo, làm 1 phát ăn ngay = điểm cao 40% sản phẩm đầu ra — B1 Mỗi thành phần quy về thang 100 theo tool cao nhất TRONG KỲ ĐANG LỌC, nên điểm chỉ so được giữa các tool cùng khoảng thời gian. Tool chưa chạy việc nào = 0 điểm. Giao diện: thẻ "🏆 Tool đóng góp nhiều nhất / ít nhất" — 2 ô nổi bật + bảng đủ 3 điểm thành phần (có thanh tỷ lệ) để đối chiếu vì sao ra thứ hạng đó. Tạm ẩn (comment) bảng "Tốc độ máy mỗi bước (B2)" theo yêu cầu; backend vẫn tính và trả data.jobs như cũ, muốn hiện lại chỉ cần bỏ comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>392e31c
-
feat(dub): xoay nhiều key Gemini khi hết quota để dịch không gãy
Trước đây chỉ có 1 GOOGLE_API_KEY: key hết quota (429) là rơi thẳng xuống Groq rồi deep-translator -> chất lượng dịch tụt hẳn. Nay: - Thêm GOOGLE_API_KEYS (nhiều key, ngăn cách bằng dấu phẩy/xuống dòng). Key đang dùng dính 429/401/403/5xx -> XOAY SANG KEY KẾ TIẾP NGAY trong chính request đó; chỉ khi mọi key đều hỏng mới rơi xuống Groq như cũ. - Con trỏ key "dính" ở key tốt (không round-robin vô ích); key hết quota được cho nghỉ GEMINI_KEY_COOLDOWN giây, nhân đôi mỗi lần hỏng liên tiếp, tối đa 1h -> không đốt request vào key đã cạn quota ngày. - Google khoá model cũ với key mới tạo (404 "no longer available to new users"), nên mỗi key tự dò và NHỚ model dùng được: key cũ giữ nguyên GEMINI_MODEL, key mới rơi về GEMINI_MODEL_FALLBACK (gemini-flash-latest). - backend/check_gemini_keys.py: kiểm tra thật từng key trước khi deploy (thoát mã != 0 nếu có key hỏng). Đã chạy: 4/4 key gọi được model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>a835d98
-
feat(analytics): tab "Quản lý thời lượng" theo dõi người dùng cho quản trị
Ghi log thô raw_events rồi suy ra 9 chỉ số (A1-A5, B1-B4) bằng ETL, không lưu sẵn chỉ số. Lưu Postgres nếu có DATABASE_URL, không thì SQLite trong STORAGE_DIR (giữ 90 ngày, tự dọn). - backend: services/analytics (storage + etl + track), api/analytics (POST /api/events + các endpoint quản trị), middleware reqctx đọc header X-TNT-* để dòng log máy chạy gắn đúng task; JobManager tự phát job_queued/started/done kèm latency_ms (đã trừ thời gian chờ hàng đợi). - frontend: lib/track.js (đồng hồ người), nút 👍/👎 (ThumbsFeedback) cho B3, dashboard SVG thuần trong tab Quản lý thời lượng; gắn điểm đo vào 8 tool. Kèm sửa lỗi phát hiện khi test: - khôi phục core.files.rel_path() + gom mọi đường dẫn phiên ở cut/dub/shuffle/ download về "/" (trước nối "\" của Windows làm hỏng URL). - get_file trả 404 khi trỏ vào thư mục (thay vì 500). - run_tests.py cô lập STORAGE_DIR sang temp (trước dọn nhầm phiên thật). - cập nhật test_cut/test_dub theo API hiện hành + thêm test_analytics (14 test). …00d2c12
-
fix(download): sửa tải TikTok quảng cáo lỗi khi deploy
Commit 81399e3 revert nhầm phần xử lý ads, gây 2 hồi quy: - Khôi phục _resolve_tiktok_url + gọi trong normalize_url: giải link rút gọn vt/vm.tiktok.com (Spark Ads hầu hết là link rút gọn). - tiktok_photo (ảnh/slideshow ad) lại vào impersonation, _REFERER và nhánh fallback tikwm (trước đó bị bỏ -> fail hẳn). - Regex tách aweme id mạnh hơn (item_id=, số dài). - tikwm: thêm retry + backoff cho rate-limit "Free Api Limit" (code -1) hay gặp khi deploy nhiều user; báo rõ khi là Spark Ads nhắm mục tiêu. - Sửa test_download.py đang gãy (bỏ import _format_issue không còn tồn tại, cập nhật format mặc định) + thêm test chốt hồi quy tiktok_photo. Lưu ý: yt-dlp extractor TikTok hiện đã hỏng ("rehydration"), mọi link TikTok thực tế chỉ tải được nhờ fallback tikwm. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>0889623
-
deploy merged code
9b53f70