@danhachuel/thunderbolt 0.3.20 → 0.3.22

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.
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ from typing import Any
5
+
6
+ from .storage import now, read_json, write_json
7
+
8
+ DRAFTS_FILE = "drafts.json"
9
+ MAX_DRAFTS = 200
10
+
11
+
12
+ def list_drafts() -> list[dict[str, Any]]:
13
+ """Return locally persisted drafts, newest first."""
14
+ records = read_json(DRAFTS_FILE, [])
15
+ return [record for record in records if isinstance(record, dict)] if isinstance(records, list) else []
16
+
17
+
18
+ def save_draft(draft: dict[str, Any]) -> dict[str, Any]:
19
+ """Persist one editable pipeline draft without advancing any pipeline task."""
20
+ record = {
21
+ **draft,
22
+ "id": str(draft.get("id") or f"draft_{uuid.uuid4().hex[:12]}"),
23
+ "created_at": str(draft.get("created_at") or now()),
24
+ "updated_at": now(),
25
+ }
26
+ drafts = list_drafts()
27
+ drafts.insert(0, record)
28
+ write_json(DRAFTS_FILE, drafts[:MAX_DRAFTS])
29
+ return record
30
+
31
+
32
+ __all__ = ["DRAFTS_FILE", "list_drafts", "save_draft"]
33
+
@@ -229,34 +229,34 @@ for _language_code, _api_key_values in _API_KEY_EXPANDER_TRANSLATIONS.items():
229
229
  # translation path as the legacy pages.
230
230
  UI_NAV_TRANSLATIONS: dict[str, dict[str, str]] = {
231
231
  "pt": {
232
- "Arquivos Base": "Arquivos Base", "Pipeline Vídeos": "Pipeline Vídeos", "Canais e Perfis de Vídeos": "Canais e Perfis de Vídeos", "Canais YouTube": "Canais YouTube", "Facebook Pages": "Facebook Pages", "Prompt Masters": "Prompt Masters", "Backlog Vídeos": "Backlog Vídeos", "Música": "Música", "Upload Música": "Upload Música", "Geração de Conteúdo IA": "Geração de Conteúdo IA", "Motion Control": "Motion Control", "UGC Products": "UGC Products", "Growth": "Growth", "Analista Growth Youtube": "Analista Growth Youtube", "Analista Growth Tiktok": "Analista Growth Tiktok", "Analista Growth Instagram": "Analista Growth Instagram", "Documentação": "Documentação", "Tutorial Kaggle": "Tutorial Kaggle", "Tutorial Apify": "Tutorial Apify", "Pipeline Música": "Pipeline Música", "módulos disponíveis": "módulos disponíveis",
232
+ "Arquivos Base": "Arquivos Base", "Pipeline Vídeos": "Pipeline Vídeos", "Canais e Perfis de Vídeos": "Canais e Perfis de Vídeos", "Canais YouTube": "Canais YouTube", "Facebook Pages": "Facebook Pages", "Prompt Masters": "Prompt Masters", "Backlog Vídeos": "Backlog Vídeos", "Thumbnails": "Thumbnails", "Música": "Música", "Upload Música": "Upload Música", "Geração de Conteúdo IA": "Geração de Conteúdo IA", "Motion Control": "Motion Control", "UGC Products": "UGC Products", "Growth": "Growth", "Analista Growth Youtube": "Analista Growth Youtube", "Analista Growth Tiktok": "Analista Growth Tiktok", "Analista Growth Instagram": "Analista Growth Instagram", "Documentação": "Documentação", "Tutorial Kaggle": "Tutorial Kaggle", "Tutorial Apify": "Tutorial Apify", "Pipeline Música": "Pipeline Música", "módulos disponíveis": "módulos disponíveis",
233
233
  },
234
234
  "en": {
235
- "Arquivos Base": "Base Files", "Pipeline Vídeos": "Video Pipeline", "Canais e Perfis de Vídeos": "Video Channels & Profiles", "Canais YouTube": "YouTube Channels", "Facebook Pages": "Facebook Pages", "Prompt Masters": "Prompt Masters", "Backlog Vídeos": "Video Backlog", "Música": "Music", "Upload Música": "Music Upload", "Geração de Conteúdo IA": "AI Content Generation", "Motion Control": "Motion Control", "UGC Products": "UGC Products", "Growth": "Growth", "Analista Growth Youtube": "YouTube Growth Analyst", "Analista Growth Tiktok": "TikTok Growth Analyst", "Analista Growth Instagram": "Instagram Growth Analyst", "Documentação": "Documentation", "Tutorial Kaggle": "Kaggle Tutorial", "Tutorial Apify": "Apify Tutorial", "Pipeline Música": "Music Pipeline", "módulos disponíveis": "available modules",
235
+ "Arquivos Base": "Base Files", "Pipeline Vídeos": "Video Pipeline", "Canais e Perfis de Vídeos": "Video Channels & Profiles", "Canais YouTube": "YouTube Channels", "Facebook Pages": "Facebook Pages", "Prompt Masters": "Prompt Masters", "Backlog Vídeos": "Video Backlog", "Thumbnails": "Thumbnails", "Música": "Music", "Upload Música": "Music Upload", "Geração de Conteúdo IA": "AI Content Generation", "Motion Control": "Motion Control", "UGC Products": "UGC Products", "Growth": "Growth", "Analista Growth Youtube": "YouTube Growth Analyst", "Analista Growth Tiktok": "TikTok Growth Analyst", "Analista Growth Instagram": "Instagram Growth Analyst", "Documentação": "Documentation", "Tutorial Kaggle": "Kaggle Tutorial", "Tutorial Apify": "Apify Tutorial", "Pipeline Música": "Music Pipeline", "módulos disponíveis": "available modules",
236
236
  },
237
237
  "zh": {
238
- "Arquivos Base": "基础文件", "Pipeline Vídeos": "视频流程", "Canais e Perfis de Vídeos": "视频频道与资料", "Canais YouTube": "YouTube 频道", "Facebook Pages": "Facebook 页面", "Prompt Masters": "主提示词", "Backlog Vídeos": "视频待办", "Música": "音乐", "Upload Música": "音乐上传", "Geração de Conteúdo IA": "AI 内容生成", "Motion Control": "动作控制", "UGC Products": "UGC 产品", "Growth": "增长", "Analista Growth Youtube": "YouTube 增长分析师", "Analista Growth Tiktok": "TikTok 增长分析师", "Analista Growth Instagram": "Instagram 增长分析师", "Documentação": "文档", "Tutorial Kaggle": "Kaggle 教程", "Tutorial Apify": "Apify 教程", "Pipeline Música": "音乐流程", "módulos disponíveis": "可用模块",
238
+ "Arquivos Base": "基础文件", "Pipeline Vídeos": "视频流程", "Canais e Perfis de Vídeos": "视频频道与资料", "Canais YouTube": "YouTube 频道", "Facebook Pages": "Facebook 页面", "Prompt Masters": "主提示词", "Backlog Vídeos": "视频待办", "Thumbnails": "缩略图", "Música": "音乐", "Upload Música": "音乐上传", "Geração de Conteúdo IA": "AI 内容生成", "Motion Control": "动作控制", "UGC Products": "UGC 产品", "Growth": "增长", "Analista Growth Youtube": "YouTube 增长分析师", "Analista Growth Tiktok": "TikTok 增长分析师", "Analista Growth Instagram": "Instagram 增长分析师", "Documentação": "文档", "Tutorial Kaggle": "Kaggle 教程", "Tutorial Apify": "Apify 教程", "Pipeline Música": "音乐流程", "módulos disponíveis": "可用模块",
239
239
  },
240
240
  "de": {
241
- "Arquivos Base": "Basisdateien", "Pipeline Vídeos": "Video-Pipeline", "Canais e Perfis de Vídeos": "Videokanäle und Profile", "Canais YouTube": "YouTube-Kanäle", "Facebook Pages": "Facebook-Seiten", "Prompt Masters": "Prompt Masters", "Backlog Vídeos": "Video-Backlog", "Música": "Musik", "Upload Música": "Musik-Upload", "Geração de Conteúdo IA": "KI-Inhaltserstellung", "Motion Control": "Motion Control", "UGC Products": "UGC-Produkte", "Growth": "Wachstum", "Analista Growth Youtube": "YouTube-Wachstumsanalyst", "Analista Growth Tiktok": "TikTok-Wachstumsanalyst", "Analista Growth Instagram": "Instagram-Wachstumsanalyst", "Documentação": "Dokumentation", "Tutorial Kaggle": "Kaggle-Tutorial", "Tutorial Apify": "Apify-Tutorial", "Pipeline Música": "Musik-Pipeline", "módulos disponíveis": "verfügbare Module",
241
+ "Arquivos Base": "Basisdateien", "Pipeline Vídeos": "Video-Pipeline", "Canais e Perfis de Vídeos": "Videokanäle und Profile", "Canais YouTube": "YouTube-Kanäle", "Facebook Pages": "Facebook-Seiten", "Prompt Masters": "Prompt Masters", "Backlog Vídeos": "Video-Backlog", "Thumbnails": "Thumbnails", "Música": "Musik", "Upload Música": "Musik-Upload", "Geração de Conteúdo IA": "KI-Inhaltserstellung", "Motion Control": "Motion Control", "UGC Products": "UGC-Produkte", "Growth": "Wachstum", "Analista Growth Youtube": "YouTube-Wachstumsanalyst", "Analista Growth Tiktok": "TikTok-Wachstumsanalyst", "Analista Growth Instagram": "Instagram-Wachstumsanalyst", "Documentação": "Dokumentation", "Tutorial Kaggle": "Kaggle-Tutorial", "Tutorial Apify": "Apify-Tutorial", "Pipeline Música": "Musik-Pipeline", "módulos disponíveis": "verfügbare Module",
242
242
  },
243
243
  "vi": {
244
- "Arquivos Base": "Tệp cơ sở", "Pipeline Vídeos": "Quy trình video", "Canais e Perfis de Vídeos": "Kênh và hồ sơ video", "Canais YouTube": "Kênh YouTube", "Facebook Pages": "Trang Facebook", "Prompt Masters": "Prompt Master", "Backlog Vídeos": "Danh sách video", "Música": "Âm nhạc", "Upload Música": "Tải nhạc lên", "Geração de Conteúdo IA": "Tạo nội dung AI", "Motion Control": "Điều khiển chuyển động", "UGC Products": "Sản phẩm UGC", "Growth": "Tăng trưởng", "Analista Growth Youtube": "Chuyên viên phân tích tăng trưởng YouTube", "Analista Growth Tiktok": "Chuyên viên phân tích tăng trưởng TikTok", "Analista Growth Instagram": "Chuyên viên phân tích tăng trưởng Instagram", "Documentação": "Tài liệu", "Tutorial Kaggle": "Hướng dẫn Kaggle", "Tutorial Apify": "Hướng dẫn Apify", "Pipeline Música": "Quy trình âm nhạc", "módulos disponíveis": "mô-đun khả dụng",
244
+ "Arquivos Base": "Tệp cơ sở", "Pipeline Vídeos": "Quy trình video", "Canais e Perfis de Vídeos": "Kênh và hồ sơ video", "Canais YouTube": "Kênh YouTube", "Facebook Pages": "Trang Facebook", "Prompt Masters": "Prompt Master", "Backlog Vídeos": "Danh sách video", "Thumbnails": "Ảnh thu nhỏ", "Música": "Âm nhạc", "Upload Música": "Tải nhạc lên", "Geração de Conteúdo IA": "Tạo nội dung AI", "Motion Control": "Điều khiển chuyển động", "UGC Products": "Sản phẩm UGC", "Growth": "Tăng trưởng", "Analista Growth Youtube": "Chuyên viên phân tích tăng trưởng YouTube", "Analista Growth Tiktok": "Chuyên viên phân tích tăng trưởng TikTok", "Analista Growth Instagram": "Chuyên viên phân tích tăng trưởng Instagram", "Documentação": "Tài liệu", "Tutorial Kaggle": "Hướng dẫn Kaggle", "Tutorial Apify": "Hướng dẫn Apify", "Pipeline Música": "Quy trình âm nhạc", "módulos disponíveis": "mô-đun khả dụng",
245
245
  },
246
246
  "tr": {
247
- "Arquivos Base": "Temel Dosyalar", "Pipeline Vídeos": "Video Akışı", "Canais e Perfis de Vídeos": "Video Kanalları ve Profilleri", "Canais YouTube": "YouTube Kanalları", "Facebook Pages": "Facebook Sayfaları", "Prompt Masters": "Prompt Master'lar", "Backlog Vídeos": "Video Bekleme Listesi", "Música": "Müzik", "Upload Música": "Müzik Yükleme", "Geração de Conteúdo IA": "Yapay Zekâ İçeriği Oluşturma", "Motion Control": "Hareket Kontrolü", "UGC Products": "UGC Ürünleri", "Growth": "Büyüme", "Analista Growth Youtube": "YouTube Büyüme Analisti", "Analista Growth Tiktok": "TikTok Büyüme Analisti", "Analista Growth Instagram": "Instagram Büyüme Analisti", "Documentação": "Dokümantasyon", "Tutorial Kaggle": "Kaggle Eğitimi", "Tutorial Apify": "Apify Eğitimi", "Pipeline Música": "Müzik Akışı", "módulos disponíveis": "kullanılabilir modüller",
247
+ "Arquivos Base": "Temel Dosyalar", "Pipeline Vídeos": "Video Akışı", "Canais e Perfis de Vídeos": "Video Kanalları ve Profilleri", "Canais YouTube": "YouTube Kanalları", "Facebook Pages": "Facebook Sayfaları", "Prompt Masters": "Prompt Master'lar", "Backlog Vídeos": "Video Bekleme Listesi", "Thumbnails": "Küçük resimler", "Música": "Müzik", "Upload Música": "Müzik Yükleme", "Geração de Conteúdo IA": "Yapay Zekâ İçeriği Oluşturma", "Motion Control": "Hareket Kontrolü", "UGC Products": "UGC Ürünleri", "Growth": "Büyüme", "Analista Growth Youtube": "YouTube Büyüme Analisti", "Analista Growth Tiktok": "TikTok Büyüme Analisti", "Analista Growth Instagram": "Instagram Büyüme Analisti", "Documentação": "Dokümantasyon", "Tutorial Kaggle": "Kaggle Eğitimi", "Tutorial Apify": "Apify Eğitimi", "Pipeline Música": "Müzik Akışı", "módulos disponíveis": "kullanılabilir modüller",
248
248
  },
249
249
  "ru": {
250
- "Arquivos Base": "Базовые файлы", "Pipeline Vídeos": "Конвейер видео", "Canais e Perfis de Vídeos": "Каналы и профили видео", "Canais YouTube": "Каналы YouTube", "Facebook Pages": "Страницы Facebook", "Prompt Masters": "Мастер-промпты", "Backlog Vídeos": "Очередь видео", "Música": "Музыка", "Upload Música": "Загрузка музыки", "Geração de Conteúdo IA": "Генерация контента ИИ", "Motion Control": "Управление движением", "UGC Products": "UGC-продукты", "Growth": "Рост", "Analista Growth Youtube": "Аналитик роста YouTube", "Analista Growth Tiktok": "Аналитик роста TikTok", "Analista Growth Instagram": "Аналитик роста Instagram", "Documentação": "Документация", "Tutorial Kaggle": "Руководство Kaggle", "Tutorial Apify": "Руководство Apify", "Pipeline Música": "Конвейер музыки", "módulos disponíveis": "доступные модули",
250
+ "Arquivos Base": "Базовые файлы", "Pipeline Vídeos": "Конвейер видео", "Canais e Perfis de Vídeos": "Каналы и профили видео", "Canais YouTube": "Каналы YouTube", "Facebook Pages": "Страницы Facebook", "Prompt Masters": "Мастер-промпты", "Backlog Vídeos": "Очередь видео", "Thumbnails": "Миниатюры", "Música": "Музыка", "Upload Música": "Загрузка музыки", "Geração de Conteúdo IA": "Генерация контента ИИ", "Motion Control": "Управление движением", "UGC Products": "UGC-продукты", "Growth": "Рост", "Analista Growth Youtube": "Аналитик роста YouTube", "Analista Growth Tiktok": "Аналитик роста TikTok", "Analista Growth Instagram": "Аналитик роста Instagram", "Documentação": "Документация", "Tutorial Kaggle": "Руководство Kaggle", "Tutorial Apify": "Руководство Apify", "Pipeline Música": "Конвейер музыки", "módulos disponíveis": "доступные модули",
251
251
  },
252
252
  "es": {
253
- "Arquivos Base": "Archivos base", "Pipeline Vídeos": "Flujo de vídeos", "Canais e Perfis de Vídeos": "Canales y perfiles de vídeo", "Canais YouTube": "Canales de YouTube", "Facebook Pages": "Páginas de Facebook", "Prompt Masters": "Prompts maestros", "Backlog Vídeos": "Cola de vídeos", "Música": "Música", "Upload Música": "Subir música", "Geração de Conteúdo IA": "Generación de contenido con IA", "Motion Control": "Control de movimiento", "UGC Products": "Productos UGC", "Growth": "Crecimiento", "Analista Growth Youtube": "Analista de crecimiento de YouTube", "Analista Growth Tiktok": "Analista de crecimiento de TikTok", "Analista Growth Instagram": "Analista de crecimiento de Instagram", "Documentação": "Documentación", "Tutorial Kaggle": "Tutorial de Kaggle", "Tutorial Apify": "Tutorial de Apify", "Pipeline Música": "Flujo de música", "módulos disponíveis": "módulos disponibles",
253
+ "Arquivos Base": "Archivos base", "Pipeline Vídeos": "Flujo de vídeos", "Canais e Perfis de Vídeos": "Canales y perfiles de vídeo", "Canais YouTube": "Canales de YouTube", "Facebook Pages": "Páginas de Facebook", "Prompt Masters": "Prompts maestros", "Backlog Vídeos": "Cola de vídeos", "Thumbnails": "Miniaturas", "Música": "Música", "Upload Música": "Subir música", "Geração de Conteúdo IA": "Generación de contenido con IA", "Motion Control": "Control de movimiento", "UGC Products": "Productos UGC", "Growth": "Crecimiento", "Analista Growth Youtube": "Analista de crecimiento de YouTube", "Analista Growth Tiktok": "Analista de crecimiento de TikTok", "Analista Growth Instagram": "Analista de crecimiento de Instagram", "Documentação": "Documentación", "Tutorial Kaggle": "Tutorial de Kaggle", "Tutorial Apify": "Tutorial de Apify", "Pipeline Música": "Flujo de música", "módulos disponíveis": "módulos disponibles",
254
254
  },
255
255
  "id": {
256
- "Arquivos Base": "File Dasar", "Pipeline Vídeos": "Alur Video", "Canais e Perfis de Vídeos": "Kanal dan Profil Video", "Canais YouTube": "Kanal YouTube", "Facebook Pages": "Halaman Facebook", "Prompt Masters": "Prompt Master", "Backlog Vídeos": "Antrean Video", "Música": "Musik", "Upload Música": "Unggah Musik", "Geração de Conteúdo IA": "Pembuatan Konten AI", "Motion Control": "Kontrol Gerakan", "UGC Products": "Produk UGC", "Growth": "Pertumbuhan", "Analista Growth Youtube": "Analis Pertumbuhan YouTube", "Analista Growth Tiktok": "Analis Pertumbuhan TikTok", "Analista Growth Instagram": "Analis Pertumbuhan Instagram", "Documentação": "Dokumentasi", "Tutorial Kaggle": "Tutorial Kaggle", "Tutorial Apify": "Tutorial Apify", "Pipeline Música": "Alur Musik", "módulos disponíveis": "modul tersedia",
256
+ "Arquivos Base": "File Dasar", "Pipeline Vídeos": "Alur Video", "Canais e Perfis de Vídeos": "Kanal dan Profil Video", "Canais YouTube": "Kanal YouTube", "Facebook Pages": "Halaman Facebook", "Prompt Masters": "Prompt Master", "Backlog Vídeos": "Antrean Video", "Thumbnails": "Thumbnail", "Música": "Musik", "Upload Música": "Unggah Musik", "Geração de Conteúdo IA": "Pembuatan Konten AI", "Motion Control": "Kontrol Gerakan", "UGC Products": "Produk UGC", "Growth": "Pertumbuhan", "Analista Growth Youtube": "Analis Pertumbuhan YouTube", "Analista Growth Tiktok": "Analis Pertumbuhan TikTok", "Analista Growth Instagram": "Analis Pertumbuhan Instagram", "Documentação": "Dokumentasi", "Tutorial Kaggle": "Tutorial Kaggle", "Tutorial Apify": "Tutorial Apify", "Pipeline Música": "Alur Musik", "módulos disponíveis": "modul tersedia",
257
257
  },
258
258
  "it": {
259
- "Arquivos Base": "File di base", "Pipeline Vídeos": "Pipeline video", "Canais e Perfis de Vídeos": "Canali e profili video", "Canais YouTube": "Canali YouTube", "Facebook Pages": "Pagine Facebook", "Prompt Masters": "Prompt Master", "Backlog Vídeos": "Coda video", "Música": "Musica", "Upload Música": "Caricamento musica", "Geração de Conteúdo IA": "Generazione di contenuti IA", "Motion Control": "Controllo del movimento", "UGC Products": "Prodotti UGC", "Growth": "Crescita", "Analista Growth Youtube": "Analista della crescita YouTube", "Analista Growth Tiktok": "Analista della crescita TikTok", "Analista Growth Instagram": "Analista della crescita Instagram", "Documentação": "Documentazione", "Tutorial Kaggle": "Tutorial Kaggle", "Tutorial Apify": "Tutorial Apify", "Pipeline Música": "Pipeline musicale", "módulos disponíveis": "moduli disponibili",
259
+ "Arquivos Base": "File di base", "Pipeline Vídeos": "Pipeline video", "Canais e Perfis de Vídeos": "Canali e profili video", "Canais YouTube": "Canali YouTube", "Facebook Pages": "Pagine Facebook", "Prompt Masters": "Prompt Master", "Backlog Vídeos": "Coda video", "Thumbnails": "Miniature", "Música": "Musica", "Upload Música": "Caricamento musica", "Geração de Conteúdo IA": "Generazione di contenuti IA", "Motion Control": "Controllo del movimento", "UGC Products": "Prodotti UGC", "Growth": "Crescita", "Analista Growth Youtube": "Analista della crescita YouTube", "Analista Growth Tiktok": "Analista della crescita TikTok", "Analista Growth Instagram": "Analista della crescita Instagram", "Documentação": "Documentazione", "Tutorial Kaggle": "Tutorial Kaggle", "Tutorial Apify": "Tutorial Apify", "Pipeline Música": "Pipeline musicale", "módulos disponíveis": "moduli disponibili",
260
260
  },
261
261
  }
262
262
  for _language_code, _navigation_values in UI_NAV_TRANSLATIONS.items():
@@ -407,6 +407,72 @@ for _language_code, _video_generation_translation in VIDEO_GENERATION_TRANSLATIO
407
407
  UI_TRANSLATIONS[_language_code].update(_video_generation_translation)
408
408
 
409
409
 
410
+ _PIPELINE_FEATURE_TRANSLATIONS: dict[str, dict[str, str]] = {
411
+ "pt": {
412
+ "Thumbnails": "Thumbnails", "Refazer thumbnail": "Refazer thumbnail", "Salvar rascunho": "Salvar rascunho",
413
+ "Rascunho guardado localmente": "Rascunho guardado localmente", "Thumbnail refeita com sucesso.": "Thumbnail refeita com sucesso.",
414
+ "Ver prompt da thumbnail": "Ver prompt da thumbnail", "Sem imagem": "Sem imagem", "Imagem ainda não gerada": "Imagem ainda não gerada",
415
+ "Palavras-chave": "Palavras-chave", "Título do rascunho": "Título do rascunho",
416
+ },
417
+ "en": {
418
+ "Thumbnails": "Thumbnails", "Refazer thumbnail": "Regenerate thumbnail", "Salvar rascunho": "Save draft",
419
+ "Rascunho guardado localmente": "Draft saved locally", "Thumbnail refeita com sucesso.": "Thumbnail regenerated successfully.",
420
+ "Ver prompt da thumbnail": "View thumbnail prompt", "Sem imagem": "No image", "Imagem ainda não gerada": "Image not generated yet",
421
+ "Palavras-chave": "Keywords", "Título do rascunho": "Draft title",
422
+ },
423
+ "zh": {
424
+ "Thumbnails": "缩略图", "Refazer thumbnail": "重新生成缩略图", "Salvar rascunho": "保存草稿",
425
+ "Rascunho guardado localmente": "草稿已保存到本地", "Thumbnail refeita com sucesso.": "缩略图已成功重新生成。",
426
+ "Ver prompt da thumbnail": "查看缩略图提示词", "Sem imagem": "无图像", "Imagem ainda não gerada": "尚未生成图像",
427
+ "Palavras-chave": "关键词", "Título do rascunho": "草稿标题",
428
+ },
429
+ "de": {
430
+ "Thumbnails": "Thumbnails", "Refazer thumbnail": "Thumbnail neu generieren", "Salvar rascunho": "Entwurf speichern",
431
+ "Rascunho guardado localmente": "Entwurf lokal gespeichert", "Thumbnail refeita com sucesso.": "Thumbnail erfolgreich neu generiert.",
432
+ "Ver prompt da thumbnail": "Thumbnail-Prompt anzeigen", "Sem imagem": "Kein Bild", "Imagem ainda não gerada": "Bild noch nicht generiert",
433
+ "Palavras-chave": "Schlüsselwörter", "Título do rascunho": "Entwurfstitel",
434
+ },
435
+ "vi": {
436
+ "Thumbnails": "Ảnh thu nhỏ", "Refazer thumbnail": "Tạo lại ảnh thu nhỏ", "Salvar rascunho": "Lưu bản nháp",
437
+ "Rascunho guardado localmente": "Đã lưu bản nháp cục bộ", "Thumbnail refeita com sucesso.": "Đã tạo lại ảnh thu nhỏ thành công.",
438
+ "Ver prompt da thumbnail": "Xem prompt ảnh thu nhỏ", "Sem imagem": "Không có hình ảnh", "Imagem ainda não gerada": "Chưa tạo hình ảnh",
439
+ "Palavras-chave": "Từ khóa", "Título do rascunho": "Tiêu đề bản nháp",
440
+ },
441
+ "tr": {
442
+ "Thumbnails": "Küçük resimler", "Refazer thumbnail": "Küçük resmi yeniden oluştur", "Salvar rascunho": "Taslağı kaydet",
443
+ "Rascunho guardado localmente": "Taslak yerel olarak kaydedildi", "Thumbnail refeita com sucesso.": "Küçük resim başarıyla yeniden oluşturuldu.",
444
+ "Ver prompt da thumbnail": "Küçük resim istemini görüntüle", "Sem imagem": "Görsel yok", "Imagem ainda não gerada": "Görsel henüz oluşturulmadı",
445
+ "Palavras-chave": "Anahtar kelimeler", "Título do rascunho": "Taslak başlığı",
446
+ },
447
+ "ru": {
448
+ "Thumbnails": "Миниатюры", "Refazer thumbnail": "Создать миниатюру заново", "Salvar rascunho": "Сохранить черновик",
449
+ "Rascunho guardado localmente": "Черновик сохранён локально", "Thumbnail refeita com sucesso.": "Миниатюра успешно создана заново.",
450
+ "Ver prompt da thumbnail": "Показать промпт миниатюры", "Sem imagem": "Нет изображения", "Imagem ainda não gerada": "Изображение ещё не создано",
451
+ "Palavras-chave": "Ключевые слова", "Título do rascunho": "Название черновика",
452
+ },
453
+ "es": {
454
+ "Thumbnails": "Miniaturas", "Refazer thumbnail": "Regenerar miniatura", "Salvar rascunho": "Guardar borrador",
455
+ "Rascunho guardado localmente": "Borrador guardado localmente", "Thumbnail refeita com sucesso.": "Miniatura regenerada correctamente.",
456
+ "Ver prompt da thumbnail": "Ver prompt de la miniatura", "Sem imagem": "Sin imagen", "Imagem ainda não gerada": "Imagen aún no generada",
457
+ "Palavras-chave": "Palabras clave", "Título do rascunho": "Título del borrador",
458
+ },
459
+ "id": {
460
+ "Thumbnails": "Thumbnail", "Refazer thumbnail": "Buat ulang thumbnail", "Salvar rascunho": "Simpan draf",
461
+ "Rascunho guardado localmente": "Draf disimpan secara lokal", "Thumbnail refeita com sucesso.": "Thumbnail berhasil dibuat ulang.",
462
+ "Ver prompt da thumbnail": "Lihat prompt thumbnail", "Sem imagem": "Tidak ada gambar", "Imagem ainda não gerada": "Gambar belum dibuat",
463
+ "Palavras-chave": "Kata kunci", "Título do rascunho": "Judul draf",
464
+ },
465
+ "it": {
466
+ "Thumbnails": "Miniature", "Refazer thumbnail": "Rigenera miniatura", "Salvar rascunho": "Salva bozza",
467
+ "Rascunho guardado localmente": "Bozza salvata localmente", "Thumbnail refeita com sucesso.": "Miniatura rigenerata con successo.",
468
+ "Ver prompt da thumbnail": "Mostra prompt della miniatura", "Sem imagem": "Nessuna immagine", "Imagem ainda não gerada": "Immagine non ancora generata",
469
+ "Palavras-chave": "Parole chiave", "Título do rascunho": "Titolo della bozza",
470
+ },
471
+ }
472
+ for _language_code, _pipeline_feature_translation in _PIPELINE_FEATURE_TRANSLATIONS.items():
473
+ UI_TRANSLATIONS[_language_code].update(_pipeline_feature_translation)
474
+
475
+
410
476
  _TAB_LABELS = (
411
477
  "Blueprints", "Brandings", "Pesquisa pública", "Cadastro manual", "Contas cadastradas", "Biblioteca",
412
478
  "Importar do YouTube", "Canais em lote gmail", "Criar vídeo", "Vídeos", "Novo roteiro/letra", "Histórico guardado",
@@ -781,6 +847,7 @@ _CONTENT_TRANSLATION_ROWS = (
781
847
  ("O Thunderbolt é o cliente. A API key é enviada exclusivamente ao servidor Postiz configurado; não é colocada em URLs, logs ou repositório.", "O Thunderbolt é o cliente. A API key é enviada exclusivamente ao servidor Postiz configurado; não é colocada em URLs, logs ou repositório.", "Thunderbolt is the client. The API key is sent only to the configured Postiz server; it is not placed in URLs, logs or the repository.", "Thunderbolt 是客户端。API 密钥仅发送到配置的 Postiz 服务器,不会放入 URL、日志或代码库。", "Thunderbolt ist der Client. Der API-Schlüssel wird nur an den konfigurierten Postiz-Server gesendet und nicht in URLs, Logs oder dem Repository abgelegt.", "Thunderbolt là client. API key chỉ gửi đến máy chủ Postiz đã cấu hình; không đặt trong URL, log hoặc repository.", "Thunderbolt istemcidir. API anahtarı yalnızca yapılandırılmış Postiz sunucusuna gönderilir; URL'lere, günlüklerine veya depoya yazılmaz.", "Thunderbolt — клиент. API-ключ отправляется только настроенному серверу Postiz и не помещается в URL, логи или репозиторий.", "Thunderbolt es el cliente. La clave API solo se envía al servidor Postiz configurado; no se incluye en URL, registros ni repositorio.", "Thunderbolt adalah klien. Kunci API hanya dikirim ke server Postiz yang dikonfigurasi; tidak dimasukkan ke URL, log, atau repositori.", "Thunderbolt è il client. La chiave API viene inviata solo al server Postiz configurato; non viene inserita in URL, log o repository."),
782
848
  ("Modo de ligação", "Modo de ligação", "Connection mode", "连接模式", "Verbindungsmodus", "Chế độ kết nối", "Bağlantı modu", "Режим подключения", "Modo de conexión", "Mode koneksi", "Modalità di connessione"),
783
849
  ("Metadados removidos e nova cópia criada. O original continua preservado.", "Metadados removidos e nova cópia criada. O original continua preservado.", "Metadata removed and a new copy created. The original remains preserved.", "元数据已移除并创建新副本。原文件仍然保留。", "Metadaten entfernt und eine neue Kopie erstellt. Das Original bleibt erhalten.", "Đã xóa siêu dữ liệu và tạo bản sao mới. Bản gốc vẫn được giữ nguyên.", "Üst veriler kaldırıldı ve yeni bir kopya oluşturuldu. Orijinal korunur.", "Метаданные удалены и создана новая копия. Оригинал сохранён.", "Metadatos eliminados y nueva copia creada. El original se conserva.", "Metadata dihapus dan salinan baru dibuat. File asli tetap dipertahankan.", "Metadati rimossi e nuova copia creata. L'originale è preservato."),
850
+ ("Detalhes da conta Google", "Detalhes da conta Google", "Google account details", "Google 账户详情", "Details des Google-Kontos", "Chi tiết tài khoản Google", "Google hesap ayrıntıları", "Сведения об аккаунте Google", "Detalles de la cuenta de Google", "Detail akun Google", "Dettagli dell'account Google"),
784
851
  )
785
852
  UI_CONTENT_TRANSLATIONS: dict[str, dict[str, str]] = {code: {} for code in _CONTENT_TRANSLATION_CODES}
786
853
  for _row in _CONTENT_TRANSLATION_ROWS:
@@ -34,6 +34,7 @@ DEFAULTS: dict[str, Any] = {
34
34
  "metadata_edits.json": [],
35
35
  "python_editor_edits.json": [],
36
36
  "scripts.json": [],
37
+ "drafts.json": [],
37
38
  "mcp_server.json": {
38
39
  "enabled": False,
39
40
  "host": "127.0.0.1",
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ import uuid
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from .storage import STORAGE, now, read_json, write_json
9
+ from .thumbnail_generation import ThumbnailGenerationError, generate_thumbnail_image
10
+
11
+
12
+ def _as_path(value: Any) -> Path | None:
13
+ raw = str(value or "").strip()
14
+ if not raw:
15
+ return None
16
+ candidates = [Path(raw)]
17
+ path = Path(raw)
18
+ if not path.is_absolute():
19
+ candidates.extend([STORAGE / path, STORAGE.parent / path])
20
+ for candidate in candidates:
21
+ if candidate.is_file():
22
+ return candidate
23
+ return Path(raw)
24
+
25
+
26
+ def _variant_index(task: dict[str, Any], variant: dict[str, Any], variants: list[Any]) -> int:
27
+ for key in ("variant_index", "index"):
28
+ try:
29
+ value = int(variant.get(key))
30
+ except (TypeError, ValueError):
31
+ continue
32
+ if 0 <= value < len(variants):
33
+ return value
34
+ for index, candidate in enumerate(variants):
35
+ if isinstance(candidate, dict) and candidate is variant:
36
+ return index
37
+ if isinstance(candidate, dict) and variant and candidate.get("image_prompt") == variant.get("image_prompt"):
38
+ return index
39
+ return 0
40
+
41
+
42
+ def normalize_thumbnail_task(task: dict[str, Any]) -> dict[str, Any]:
43
+ variants = task.get("thumbnail_variants") if isinstance(task.get("thumbnail_variants"), list) else []
44
+ variant = task.get("thumbnail_variant") if isinstance(task.get("thumbnail_variant"), dict) else {}
45
+ artifacts = task.get("artifacts") if isinstance(task.get("artifacts"), dict) else {}
46
+ image_path = variant.get("image_path") or task.get("thumbnail_path") or artifacts.get("thumbnail")
47
+ prompt = variant.get("image_prompt") or task.get("thumbnail_prompt") or ""
48
+ title = str(task.get("title") or task.get("topic") or "Vídeo sem título").strip()
49
+ return {
50
+ "task": task,
51
+ "task_id": str(task.get("id") or ""),
52
+ "title": title,
53
+ "topic": str(task.get("topic") or "").strip(),
54
+ "channel_name": str(task.get("channel_name") or "Canal sem nome").strip(),
55
+ "status": str(task.get("thumbnail_status") or "not_generated"),
56
+ "prompt": str(prompt or "").strip(),
57
+ "image_path": _as_path(image_path),
58
+ "variant": variant,
59
+ "variants": variants,
60
+ "variant_index": _variant_index(task, variant, variants),
61
+ }
62
+
63
+
64
+ def list_thumbnail_tasks() -> list[dict[str, Any]]:
65
+ tasks = read_json("tasks.json", [])
66
+ if not isinstance(tasks, list):
67
+ return []
68
+ records = []
69
+ for task in tasks:
70
+ if not isinstance(task, dict):
71
+ continue
72
+ record = normalize_thumbnail_task(task)
73
+ has_thumbnail_signal = bool(
74
+ record["image_path"]
75
+ or record["prompt"]
76
+ or task.get("thumbnail_status")
77
+ or task.get("thumbnail_variant")
78
+ or task.get("thumbnail_variants")
79
+ )
80
+ if record["task_id"] and has_thumbnail_signal:
81
+ records.append(record)
82
+ return records
83
+
84
+
85
+ def regenerate_thumbnail(task_id: str, settings: dict[str, Any]) -> tuple[dict[str, Any], Path]:
86
+ tasks = read_json("tasks.json", [])
87
+ if not isinstance(tasks, list):
88
+ raise ThumbnailGenerationError("O índice de tarefas não está disponível.")
89
+ for task in tasks:
90
+ if not isinstance(task, dict) or str(task.get("id") or "") != str(task_id):
91
+ continue
92
+ record = normalize_thumbnail_task(task)
93
+ if not record["prompt"]:
94
+ raise ThumbnailGenerationError("Esta tarefa não tem um prompt de imagem para refazer a thumbnail.")
95
+ previous_image = record.get("image_path")
96
+ if previous_image and previous_image.is_file():
97
+ history_dir = STORAGE / "thumbnails" / "history"
98
+ history_dir.mkdir(parents=True, exist_ok=True)
99
+ history_path = history_dir / f"{task_id}-{uuid.uuid4().hex[:10]}-{previous_image.name}"
100
+ shutil.copy2(previous_image, history_path)
101
+ image_path = generate_thumbnail_image(
102
+ settings,
103
+ record["prompt"],
104
+ topic=record["title"] or record["topic"],
105
+ variant_index=record["variant_index"],
106
+ )
107
+ artifacts = dict(task.get("artifacts") or {})
108
+ artifacts["thumbnail"] = str(image_path)
109
+ task["artifacts"] = artifacts
110
+ task["thumbnail_path"] = str(image_path)
111
+ task["thumbnail_status"] = "generated"
112
+ variant = dict(record["variant"])
113
+ if variant:
114
+ variant["image_path"] = str(image_path)
115
+ task["thumbnail_variant"] = variant
116
+ variants = task.get("thumbnail_variants")
117
+ if isinstance(variants, list) and 0 <= record["variant_index"] < len(variants) and isinstance(variants[record["variant_index"]], dict):
118
+ variants[record["variant_index"]] = {**variants[record["variant_index"]], "image_path": str(image_path)}
119
+ task["thumbnail_variants"] = variants
120
+ task["updated_at"] = now()
121
+ write_json("tasks.json", tasks)
122
+ return task, image_path
123
+ raise ThumbnailGenerationError(f"A tarefa {task_id} não foi encontrada.")
124
+
125
+
126
+ __all__ = ["list_thumbnail_tasks", "normalize_thumbnail_task", "regenerate_thumbnail"]
127
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
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",