@danhachuel/thunderbolt 0.3.5 → 0.3.6
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 +139 -40
- package/hermes_ui/creative_generation.py +60 -0
- package/hermes_ui/languages.py +67 -0
- package/hermes_ui/pipeline_worker.py +33 -12
- package/package.json +1 -1
package/app/main.py
CHANGED
|
@@ -43,7 +43,7 @@ from hermes_ui.script_documents import list_script_documents, read_script_docume
|
|
|
43
43
|
from hermes_ui.script_generation import generate_script_document
|
|
44
44
|
from hermes_ui.voice_preview import DEFAULT_SAMPLE, load_preview_file, synthesize_preview
|
|
45
45
|
from hermes_ui.thumbnail_generation import ThumbnailGenerationError, generate_thumbnail_image
|
|
46
|
-
from hermes_ui.creative_generation import CreativeGenerationError, generate_creative_package, generate_topic_for_channel
|
|
46
|
+
from hermes_ui.creative_generation import CreativeGenerationError, generate_creative_package, generate_topic_for_channel, generate_video_keywords
|
|
47
47
|
from integrations.platforms import IntegrationResult, TikTokAdapter, YouTubeAdapter, fetch_channel_videos_public
|
|
48
48
|
from integrations.tiktok_public import fetch_public_tiktok_profile, normalize_tiktok_reference
|
|
49
49
|
from integrations.postiz import PostizAdapter
|
|
@@ -492,6 +492,93 @@ def generate_topic_for_ui(settings: dict[str, Any], channel: dict, user_context:
|
|
|
492
492
|
return generate_topic_for_channel(settings, channel, blueprint_for_channel(channel), user_context=user_context)
|
|
493
493
|
|
|
494
494
|
|
|
495
|
+
def generate_video_content_for_ui(
|
|
496
|
+
settings: dict[str, Any],
|
|
497
|
+
channel: dict,
|
|
498
|
+
subject: str,
|
|
499
|
+
language: str,
|
|
500
|
+
generation_settings: dict[str, Any] | None = None,
|
|
501
|
+
) -> dict[str, Any]:
|
|
502
|
+
"""Generate the subject, script and English keywords in one MoneyPrinter-style action."""
|
|
503
|
+
subject = str(subject or "").strip()
|
|
504
|
+
topic_result: dict[str, Any] | None = None
|
|
505
|
+
if not subject:
|
|
506
|
+
topic_result = generate_topic_for_ui(settings, channel)
|
|
507
|
+
subject = str(topic_result.get("topic") or "").strip()
|
|
508
|
+
if not subject:
|
|
509
|
+
raise CreativeGenerationError("A IA não devolveu um Video Subject válido.")
|
|
510
|
+
|
|
511
|
+
blueprint = blueprint_for_channel(channel)
|
|
512
|
+
script_result = generate_script_document(
|
|
513
|
+
settings,
|
|
514
|
+
document_type="Roteiro de vídeo",
|
|
515
|
+
title=subject,
|
|
516
|
+
brief=subject,
|
|
517
|
+
language=str(language or channel.get("language") or "Português"),
|
|
518
|
+
channel=channel,
|
|
519
|
+
blueprint=blueprint,
|
|
520
|
+
structure_notes=str((generation_settings or {}).get("script_structure_notes") or ""),
|
|
521
|
+
generation_settings=generation_settings or {},
|
|
522
|
+
)
|
|
523
|
+
script = str(script_result.get("content") or "").strip()
|
|
524
|
+
keywords = generate_video_keywords(
|
|
525
|
+
settings,
|
|
526
|
+
channel,
|
|
527
|
+
subject,
|
|
528
|
+
script,
|
|
529
|
+
blueprint,
|
|
530
|
+
language=str(language or channel.get("language") or "Português"),
|
|
531
|
+
)
|
|
532
|
+
return {
|
|
533
|
+
"topic": subject,
|
|
534
|
+
"topic_result": topic_result,
|
|
535
|
+
"script_result": script_result,
|
|
536
|
+
"script": script,
|
|
537
|
+
"keywords": keywords,
|
|
538
|
+
"topic_source": "llm" if topic_result else "manual",
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _generate_video_content_callback(prefix: str, channel: dict, fallback_language: str) -> None:
|
|
543
|
+
"""Streamlit callback for the single button below Subject, Script and Keywords."""
|
|
544
|
+
settings = read_json("settings.json", {})
|
|
545
|
+
generation_settings = {
|
|
546
|
+
"video_subject": str(st.session_state.get(f"{prefix}_video_subject") or "").strip(),
|
|
547
|
+
"script_language": str(st.session_state.get(f"{prefix}_script_language") or fallback_language or "pt"),
|
|
548
|
+
"script_structure_notes": str(st.session_state.get(f"{prefix}_script_structure_notes") or "").strip(),
|
|
549
|
+
"generate_script_with_ai": True,
|
|
550
|
+
}
|
|
551
|
+
try:
|
|
552
|
+
result = generate_video_content_for_ui(
|
|
553
|
+
settings,
|
|
554
|
+
channel,
|
|
555
|
+
generation_settings["video_subject"],
|
|
556
|
+
generation_settings["script_language"],
|
|
557
|
+
generation_settings=generation_settings,
|
|
558
|
+
)
|
|
559
|
+
st.session_state[f"{prefix}_video_subject"] = result["topic"]
|
|
560
|
+
st.session_state[f"{prefix}_video_script"] = result["script"]
|
|
561
|
+
st.session_state[f"{prefix}_video_keywords"] = ", ".join(result["keywords"])
|
|
562
|
+
st.session_state["new_video_topic"] = result["topic"]
|
|
563
|
+
st.session_state["new_video_topic_meta"] = result.get("topic_result") or {
|
|
564
|
+
"topic": result["topic"],
|
|
565
|
+
"topic_source": result.get("topic_source", "manual"),
|
|
566
|
+
}
|
|
567
|
+
# A subject change invalidates a previously generated title/thumbnail package.
|
|
568
|
+
st.session_state.pop("new_video_creative_payload", None)
|
|
569
|
+
st.session_state[f"{prefix}_generate_content_notice"] = "Tema, roteiro e palavras-chave gerados com IA."
|
|
570
|
+
st.session_state.pop(f"{prefix}_generate_content_error", None)
|
|
571
|
+
except CreativeGenerationError as exc:
|
|
572
|
+
st.session_state[f"{prefix}_generate_content_error"] = str(exc)
|
|
573
|
+
st.session_state.pop(f"{prefix}_generate_content_notice", None)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _video_topic_source(subject: str) -> str:
|
|
577
|
+
meta = st.session_state.get("new_video_topic_meta") or {}
|
|
578
|
+
generated_topic = str(meta.get("topic") or "").strip() if isinstance(meta, dict) else ""
|
|
579
|
+
return "llm" if generated_topic and generated_topic == str(subject or "").strip() and meta.get("topic_source") == "llm" else "manual"
|
|
580
|
+
|
|
581
|
+
|
|
495
582
|
def generate_creative_for_ui(settings: dict[str, Any], channel: dict, topic: str, topic_source: str = "manual") -> dict[str, Any]:
|
|
496
583
|
creative = generate_creative_package(
|
|
497
584
|
settings,
|
|
@@ -530,7 +617,13 @@ def voice_catalog(current: str = "") -> list[str]:
|
|
|
530
617
|
return voices
|
|
531
618
|
|
|
532
619
|
|
|
533
|
-
def render_video_generation_settings(
|
|
620
|
+
def render_video_generation_settings(
|
|
621
|
+
prefix: str,
|
|
622
|
+
*,
|
|
623
|
+
current_language: str = "",
|
|
624
|
+
channel: dict[str, Any] | None = None,
|
|
625
|
+
generate_content_callback: Any | None = None,
|
|
626
|
+
) -> dict[str, Any]:
|
|
534
627
|
"""Render the shared MoneyPrinter-style settings and return a serializable payload."""
|
|
535
628
|
settings: dict[str, Any] = {}
|
|
536
629
|
st.markdown("### Video Subject Settings")
|
|
@@ -572,6 +665,19 @@ def render_video_generation_settings(prefix: str, *, current_language: str = "")
|
|
|
572
665
|
key=f"{prefix}_video_keywords",
|
|
573
666
|
height=90,
|
|
574
667
|
)
|
|
668
|
+
if generate_content_callback is not None:
|
|
669
|
+
st.button(
|
|
670
|
+
"Gerar tópico, roteiro e palavras-chave com IA",
|
|
671
|
+
key=f"{prefix}_generate_video_content",
|
|
672
|
+
use_container_width=True,
|
|
673
|
+
type="secondary",
|
|
674
|
+
icon=":material/auto_awesome:",
|
|
675
|
+
on_click=generate_content_callback,
|
|
676
|
+
)
|
|
677
|
+
if st.session_state.get(f"{prefix}_generate_content_notice"):
|
|
678
|
+
st.success(st.session_state[f"{prefix}_generate_content_notice"])
|
|
679
|
+
if st.session_state.get(f"{prefix}_generate_content_error"):
|
|
680
|
+
st.error(st.session_state[f"{prefix}_generate_content_error"])
|
|
575
681
|
|
|
576
682
|
st.markdown("### Video Settings")
|
|
577
683
|
video_cols = st.columns(2)
|
|
@@ -596,6 +702,13 @@ def render_video_generation_settings(prefix: str, *, current_language: str = "")
|
|
|
596
702
|
with audio_cols[0]:
|
|
597
703
|
settings["voiceover_mode"] = st.radio("Voiceover Mode", VOICEOVER_MODE_OPTIONS, horizontal=True, key=f"{prefix}_voiceover_mode")
|
|
598
704
|
settings["voiceover_service"] = st.selectbox("Voiceover Service", VOICEOVER_SERVICE_OPTIONS, key=f"{prefix}_voiceover_service")
|
|
705
|
+
if channel is not None:
|
|
706
|
+
channel_id = str(channel.get("id") or channel.get("name") or "")
|
|
707
|
+
channel_voice = str(channel.get("default_voice") or channel.get("voice") or "").strip()
|
|
708
|
+
channel_state_key = f"{prefix}_voice_channel_id"
|
|
709
|
+
if st.session_state.get(channel_state_key) != channel_id:
|
|
710
|
+
st.session_state[f"{prefix}_voice"] = channel_voice
|
|
711
|
+
st.session_state[channel_state_key] = channel_id
|
|
599
712
|
current_voice = str(st.session_state.get(f"{prefix}_voice", ""))
|
|
600
713
|
voice_options = voice_catalog(current_voice)
|
|
601
714
|
settings["voice"] = st.selectbox("Voice (match script language)", voice_options, format_func=lambda value: value or "Sem voz seleccionada", key=f"{prefix}_voice")
|
|
@@ -1611,32 +1724,13 @@ def render_new_video(page_title: str = "Criação de Vídeos"):
|
|
|
1611
1724
|
generation_settings = render_video_generation_settings(
|
|
1612
1725
|
"new_video",
|
|
1613
1726
|
current_language=str(st.session_state.get("video_language") or ""),
|
|
1727
|
+
channel=selected_one,
|
|
1728
|
+
generate_content_callback=lambda: _generate_video_content_callback(
|
|
1729
|
+
"new_video",
|
|
1730
|
+
selected_one,
|
|
1731
|
+
str(st.session_state.get("video_language") or "pt"),
|
|
1732
|
+
),
|
|
1614
1733
|
)
|
|
1615
|
-
topic = st.text_area(
|
|
1616
|
-
"Tópico ou briefing",
|
|
1617
|
-
value=st.session_state.get("new_video_topic", ""),
|
|
1618
|
-
key="new_video_topic",
|
|
1619
|
-
placeholder="Escreva um briefing ou gere-o com IA; não é obrigatório escrever manualmente.",
|
|
1620
|
-
help="Pode escrever o tema ou usar o botão abaixo para gerar um briefing específico com o Blueprint e o nicho do canal.",
|
|
1621
|
-
)
|
|
1622
|
-
topic_cols = st.columns([1, 1.8])
|
|
1623
|
-
with topic_cols[0]:
|
|
1624
|
-
if st.button("Gerar tópico/briefing com IA", key="new_video_generate_topic", use_container_width=True):
|
|
1625
|
-
if selected_one is None:
|
|
1626
|
-
st.error("Seleccione primeiro um canal.")
|
|
1627
|
-
else:
|
|
1628
|
-
try:
|
|
1629
|
-
result = generate_topic_for_ui(read_json("settings.json", {}), selected_one, topic)
|
|
1630
|
-
st.session_state["new_video_topic"] = result["topic"]
|
|
1631
|
-
st.session_state["new_video_topic_meta"] = result
|
|
1632
|
-
st.success("Briefing gerado; reveja e edite o texto antes de criar as tarefas.")
|
|
1633
|
-
st.rerun()
|
|
1634
|
-
except CreativeGenerationError as exc:
|
|
1635
|
-
st.error(str(exc))
|
|
1636
|
-
with topic_cols[1]:
|
|
1637
|
-
if st.session_state.get("new_video_topic_meta"):
|
|
1638
|
-
meta = st.session_state["new_video_topic_meta"]
|
|
1639
|
-
st.caption(f"Origem: IA · Nicho: {meta.get('niche', '—')} · Ângulo: {meta.get('angle', '—')}")
|
|
1640
1734
|
|
|
1641
1735
|
if not generation_settings:
|
|
1642
1736
|
generation_settings = render_video_generation_settings(
|
|
@@ -1753,7 +1847,7 @@ def render_new_video(page_title: str = "Criação de Vídeos"):
|
|
|
1753
1847
|
st.caption("A imagem ainda não foi gerada. Configure a API key em Configuração API > API Keys.")
|
|
1754
1848
|
st.caption(f"Estado da thumbnail: {payload.get('thumbnail_status', 'prompt_ready')} · texto: {payload.get('thumbnail_text') or 'sem texto'}")
|
|
1755
1849
|
else:
|
|
1756
|
-
topic_for_creative = str(
|
|
1850
|
+
topic_for_creative = str(generation_settings.get("video_subject") or "").strip()
|
|
1757
1851
|
if st.button("Gerar títulos e thumbnails com IA", key="new_video_generate_creative", use_container_width=True):
|
|
1758
1852
|
if selected_one is None:
|
|
1759
1853
|
st.error("Seleccione primeiro um canal.")
|
|
@@ -1764,7 +1858,13 @@ def render_new_video(page_title: str = "Criação de Vídeos"):
|
|
|
1764
1858
|
topic_for_creative = str(topic_result["topic"]).strip()
|
|
1765
1859
|
st.session_state["new_video_topic"] = topic_for_creative
|
|
1766
1860
|
st.session_state["new_video_topic_meta"] = topic_result
|
|
1767
|
-
generated = generate_creative_for_ui(
|
|
1861
|
+
generated = generate_creative_for_ui(
|
|
1862
|
+
read_json("settings.json", {}),
|
|
1863
|
+
selected_one,
|
|
1864
|
+
topic_for_creative,
|
|
1865
|
+
topic_source=_video_topic_source(topic_for_creative),
|
|
1866
|
+
)
|
|
1867
|
+
|
|
1768
1868
|
st.session_state["new_video_creative_payload"] = generated
|
|
1769
1869
|
st.success("Tema, título e thumbnails gerados; escolha a variante antes de criar as tarefas.")
|
|
1770
1870
|
st.rerun()
|
|
@@ -1866,28 +1966,27 @@ def render_new_video(page_title: str = "Criação de Vídeos"):
|
|
|
1866
1966
|
tasks = create_tasks_for_batch(batch)
|
|
1867
1967
|
st.success(f"Lote geral {batch['id']} criado com {len(tasks)} tarefas independentes, uma por canal.")
|
|
1868
1968
|
else:
|
|
1869
|
-
topic_value = str(
|
|
1969
|
+
topic_value = str(generation_settings.get("video_subject") or "").strip()
|
|
1870
1970
|
if not selected:
|
|
1871
1971
|
st.error("Seleccione um canal.")
|
|
1872
1972
|
else:
|
|
1873
1973
|
if not topic_value:
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
topic_value = str(topic_result["topic"]).strip()
|
|
1877
|
-
st.session_state["new_video_topic"] = topic_value
|
|
1878
|
-
st.session_state["new_video_topic_meta"] = topic_result
|
|
1879
|
-
except CreativeGenerationError as exc:
|
|
1880
|
-
st.error(f"Não foi possível gerar automaticamente o tema: {exc}")
|
|
1881
|
-
st.stop()
|
|
1974
|
+
st.error("Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.")
|
|
1975
|
+
st.stop()
|
|
1882
1976
|
quantity_value = int(quantity if mode == "same_channel" else 1)
|
|
1883
1977
|
payload = dict(st.session_state.get("new_video_creative_payload") or {})
|
|
1884
1978
|
if not payload.get("title") or not payload.get("thumbnail_variants"):
|
|
1885
1979
|
try:
|
|
1886
|
-
payload = generate_creative_for_ui(
|
|
1980
|
+
payload = generate_creative_for_ui(
|
|
1981
|
+
read_json("settings.json", {}),
|
|
1982
|
+
selected_one or {},
|
|
1983
|
+
topic_value,
|
|
1984
|
+
topic_source=_video_topic_source(topic_value),
|
|
1985
|
+
)
|
|
1887
1986
|
except CreativeGenerationError as exc:
|
|
1888
1987
|
st.warning(f"Título/thumbnail automáticos pendentes: {exc} A tarefa será criada com o tópico como título e sem ficheiro de thumbnail.")
|
|
1889
1988
|
payload = {"topic": topic_value, "title": topic_value, "topic_source": "manual", "thumbnail_status": "pending_provider", "thumbnail_variants": [], "thumbnail_variant": {}, "thumbnail_prompt": "", "thumbnail_text": ""}
|
|
1890
|
-
payload.update({"topic": topic_value, "topic_source": payload.get("topic_source") or (
|
|
1989
|
+
payload.update({"topic": topic_value, "topic_source": payload.get("topic_source") or _video_topic_source(topic_value), "language": language, "format": fmt, "style_wide": style, "style_ia": style_ia, "music_mode": style == "music", "background_mode": "none" if style == "music" else ("ai" if style == "full_ia" else "stock"), "music_path": music_path, "music_source": music_source, "generation_settings": generation_settings})
|
|
1891
1990
|
batch = create_batch(mode, selected, topic_value, quantity_value, {"language": language, "format": fmt, "style_wide": style, "style_ia": style_ia, "music_mode": style == "music", "background_mode": "none" if style == "music" else ("ai" if style == "full_ia" else "stock"), "music_path": music_path, "music_source": music_source, "generation_settings": generation_settings, "topic_source": payload.get("topic_source", "manual"), "channel_payloads": {selected[0]: payload}})
|
|
1892
1991
|
tasks = create_tasks_for_batch(batch)
|
|
1893
1992
|
st.success(f"Lote {batch['id']} criado com {len(tasks)} tarefa(s). Abra {ui_text('Backlog Vídeos', current_ui_language())} para acompanhar.")
|
|
@@ -166,6 +166,66 @@ def generate_topic_for_channel(
|
|
|
166
166
|
return result
|
|
167
167
|
|
|
168
168
|
|
|
169
|
+
def generate_video_keywords(
|
|
170
|
+
settings: dict[str, Any],
|
|
171
|
+
channel: dict[str, Any],
|
|
172
|
+
topic: str,
|
|
173
|
+
script: str,
|
|
174
|
+
blueprint: dict[str, Any] | None = None,
|
|
175
|
+
language: str = "",
|
|
176
|
+
) -> list[str]:
|
|
177
|
+
"""Generate SEO keywords for a video subject and script using the configured LLM.
|
|
178
|
+
|
|
179
|
+
This mirrors MoneyPrinterTurbo's ``llm.generate_terms`` step while keeping the
|
|
180
|
+
Thunderbolt provider configuration and blueprint context in one place.
|
|
181
|
+
"""
|
|
182
|
+
topic = str(topic or "").strip()
|
|
183
|
+
script = str(script or "").strip()
|
|
184
|
+
if not topic:
|
|
185
|
+
raise CreativeGenerationError("É necessário um Video Subject antes de gerar palavras-chave.")
|
|
186
|
+
if not script:
|
|
187
|
+
raise CreativeGenerationError("É necessário um roteiro antes de gerar palavras-chave.")
|
|
188
|
+
|
|
189
|
+
context = channel_context(channel, blueprint)
|
|
190
|
+
system = (
|
|
191
|
+
"És um especialista de SEO para vídeos faceless e pesquisa de materiais. "
|
|
192
|
+
"Extrai entre 8 e 15 palavras-chave curtas, concretas e úteis para o vídeo. "
|
|
193
|
+
"Devolve as palavras-chave em inglês, sem hashtags, sem frases longas, sem duplicados "
|
|
194
|
+
"e sem comentários adicionais. Responde apenas com JSON válido na chave keywords."
|
|
195
|
+
)
|
|
196
|
+
user = json.dumps(
|
|
197
|
+
{
|
|
198
|
+
"channel": context,
|
|
199
|
+
"language": language or context["language"],
|
|
200
|
+
"video_subject": topic,
|
|
201
|
+
"video_script": script,
|
|
202
|
+
"reference_rules": reference_bundle(),
|
|
203
|
+
"requirements": {
|
|
204
|
+
"count": "8 to 15",
|
|
205
|
+
"language": "English",
|
|
206
|
+
"format": "short keyword phrases without hashtags",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
ensure_ascii=False,
|
|
210
|
+
)
|
|
211
|
+
result = _chat_json(settings, system, user)
|
|
212
|
+
raw_keywords = result.get("keywords")
|
|
213
|
+
if isinstance(raw_keywords, str):
|
|
214
|
+
raw_keywords = re.split(r"[,\n;|]+", raw_keywords)
|
|
215
|
+
keywords = []
|
|
216
|
+
seen = set()
|
|
217
|
+
if isinstance(raw_keywords, list):
|
|
218
|
+
for item in raw_keywords:
|
|
219
|
+
value = re.sub(r"^#+", "", str(item or "").strip())
|
|
220
|
+
normalized = value.casefold()
|
|
221
|
+
if value and normalized not in seen:
|
|
222
|
+
keywords.append(value)
|
|
223
|
+
seen.add(normalized)
|
|
224
|
+
if not keywords:
|
|
225
|
+
keywords = _keywords_from_text(topic, script)
|
|
226
|
+
return keywords[:15]
|
|
227
|
+
|
|
228
|
+
|
|
169
229
|
def _score(value: Any) -> int:
|
|
170
230
|
try:
|
|
171
231
|
return max(0, min(3, int(value)))
|
package/hermes_ui/languages.py
CHANGED
|
@@ -180,6 +180,73 @@ for _language_code, _navigation_values in UI_NAV_TRANSLATIONS.items():
|
|
|
180
180
|
UI_TRANSLATIONS[_language_code].update(_navigation_values)
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
VIDEO_GENERATION_TRANSLATIONS: dict[str, dict[str, str]] = {
|
|
184
|
+
"pt": {
|
|
185
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "Gerar tópico, roteiro e palavras-chave com IA",
|
|
186
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "A gerar tópico, roteiro e palavras-chave com IA…",
|
|
187
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "Tema, roteiro e palavras-chave gerados com IA.",
|
|
188
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.",
|
|
189
|
+
},
|
|
190
|
+
"en": {
|
|
191
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "Generate topic, script and keywords with AI",
|
|
192
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "Generating topic, script and keywords with AI…",
|
|
193
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "Topic, script and keywords generated with AI.",
|
|
194
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "Fill in Video Subject or use the automatic generation button below the keywords.",
|
|
195
|
+
},
|
|
196
|
+
"zh": {
|
|
197
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "使用 AI 生成主题、脚本和关键词",
|
|
198
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "正在使用 AI 生成主题、脚本和关键词…",
|
|
199
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "已使用 AI 生成主题、脚本和关键词。",
|
|
200
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "请填写 Video Subject,或使用关键词下方的自动生成按钮。",
|
|
201
|
+
},
|
|
202
|
+
"de": {
|
|
203
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "Thema, Skript und Keywords mit KI generieren",
|
|
204
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "Thema, Skript und Keywords werden mit KI generiert…",
|
|
205
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "Thema, Skript und Keywords wurden mit KI generiert.",
|
|
206
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "Füllen Sie Video Subject aus oder verwenden Sie die automatische Schaltfläche unter den Keywords.",
|
|
207
|
+
},
|
|
208
|
+
"vi": {
|
|
209
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "Tạo chủ đề, kịch bản và từ khóa bằng AI",
|
|
210
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "Đang tạo chủ đề, kịch bản và từ khóa bằng AI…",
|
|
211
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "Đã tạo chủ đề, kịch bản và từ khóa bằng AI.",
|
|
212
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "Hãy điền Video Subject hoặc dùng nút tạo tự động bên dưới từ khóa.",
|
|
213
|
+
},
|
|
214
|
+
"tr": {
|
|
215
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "Yapay zekâ ile konu, senaryo ve anahtar kelimeler oluştur",
|
|
216
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "Konu, senaryo ve anahtar kelimeler yapay zekâ ile oluşturuluyor…",
|
|
217
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "Konu, senaryo ve anahtar kelimeler yapay zekâ ile oluşturuldu.",
|
|
218
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "Video Subject alanını doldurun veya anahtar kelimelerin altındaki otomatik oluşturma düğmesini kullanın.",
|
|
219
|
+
},
|
|
220
|
+
"ru": {
|
|
221
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "Создать тему, сценарий и ключевые слова с помощью ИИ",
|
|
222
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "Создание темы, сценария и ключевых слов с помощью ИИ…",
|
|
223
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "Тема, сценарий и ключевые слова созданы с помощью ИИ.",
|
|
224
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "Заполните Video Subject или используйте кнопку автоматической генерации под ключевыми словами.",
|
|
225
|
+
},
|
|
226
|
+
"es": {
|
|
227
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "Generar tema, guion y palabras clave con IA",
|
|
228
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "Generando tema, guion y palabras clave con IA…",
|
|
229
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "Tema, guion y palabras clave generados con IA.",
|
|
230
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "Completa Video Subject o utiliza el botón de generación automática debajo de las palabras clave.",
|
|
231
|
+
},
|
|
232
|
+
"id": {
|
|
233
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "Buat topik, skrip, dan kata kunci dengan AI",
|
|
234
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "Membuat topik, skrip, dan kata kunci dengan AI…",
|
|
235
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "Topik, skrip, dan kata kunci dibuat dengan AI.",
|
|
236
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "Isi Video Subject atau gunakan tombol pembuatan otomatis di bawah kata kunci.",
|
|
237
|
+
},
|
|
238
|
+
"it": {
|
|
239
|
+
"Gerar tópico, roteiro e palavras-chave com IA": "Genera argomento, copione e parole chiave con l'IA",
|
|
240
|
+
"A gerar tópico, roteiro e palavras-chave com IA…": "Generazione di argomento, copione e parole chiave con l'IA…",
|
|
241
|
+
"Tema, roteiro e palavras-chave gerados com IA.": "Argomento, copione e parole chiave generati con l'IA.",
|
|
242
|
+
"Preencha o campo Video Subject ou use o botão de geração automática abaixo das keywords.": "Compila Video Subject oppure usa il pulsante di generazione automatica sotto le parole chiave.",
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for _language_code, _video_generation_translation in VIDEO_GENERATION_TRANSLATIONS.items():
|
|
247
|
+
UI_TRANSLATIONS[_language_code].update(_video_generation_translation)
|
|
248
|
+
|
|
249
|
+
|
|
183
250
|
_TAB_LABELS = (
|
|
184
251
|
"Blueprints", "Brandings", "Pesquisa pública", "Cadastro manual", "Contas cadastradas", "Biblioteca",
|
|
185
252
|
"Importar do YouTube", "Canais em lote gmail", "Criar vídeo", "Vídeos", "Novo roteiro/letra", "Histórico guardado",
|
|
@@ -174,17 +174,34 @@ def _run_task(task: dict[str, Any]) -> dict[str, Any]:
|
|
|
174
174
|
_update(task_id, topic=topic, topic_source="llm", ai_generation={"topic": topic_result}, progress=12)
|
|
175
175
|
|
|
176
176
|
_update(task_id, stage="script", state="doing", progress=18, error=None)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
177
|
+
generation_settings = task.get("generation_settings") if isinstance(task.get("generation_settings"), dict) else {}
|
|
178
|
+
provided_script = str(generation_settings.get("video_script") or "").strip()
|
|
179
|
+
if provided_script:
|
|
180
|
+
script = {
|
|
181
|
+
"document_type": "video_script",
|
|
182
|
+
"title": str(task.get("title") or topic),
|
|
183
|
+
"summary": topic,
|
|
184
|
+
"content": provided_script,
|
|
185
|
+
"language": str(task.get("language") or channel.get("language") or "Português"),
|
|
186
|
+
"blueprint_id": str(blueprint.get("id") or ""),
|
|
187
|
+
"blueprint_name": str(blueprint.get("name") or "SEM BLUEPRINT CONFIGURADO"),
|
|
188
|
+
"channel_id": str(channel.get("id") or ""),
|
|
189
|
+
"channel_name": str(channel.get("name") or "Canal sem nome"),
|
|
190
|
+
"generation_settings": generation_settings,
|
|
191
|
+
"generated_by": "video_creation_form",
|
|
192
|
+
}
|
|
193
|
+
else:
|
|
194
|
+
script = generate_script_document(
|
|
195
|
+
settings,
|
|
196
|
+
document_type="Roteiro de vídeo",
|
|
197
|
+
title=str(task.get("title") or topic),
|
|
198
|
+
brief=topic,
|
|
199
|
+
language=str(task.get("language") or channel.get("language") or "Português"),
|
|
200
|
+
channel=channel,
|
|
201
|
+
blueprint=blueprint,
|
|
202
|
+
structure_notes=str(generation_settings.get("script_structure_notes") or ""),
|
|
203
|
+
generation_settings=generation_settings,
|
|
204
|
+
)
|
|
188
205
|
script_record = save_script_document(script)
|
|
189
206
|
artifacts = dict(task.get("artifacts") or {})
|
|
190
207
|
artifacts["script"] = script_record.get("path", "")
|
|
@@ -193,7 +210,11 @@ def _run_task(task: dict[str, Any]) -> dict[str, Any]:
|
|
|
193
210
|
_update(task_id, stage="title", state="doing", progress=35)
|
|
194
211
|
creative = generate_creative_package(settings, channel, topic, blueprint, language=str(task.get("language") or channel.get("language") or "Português"))
|
|
195
212
|
title = str(creative.get("title") or topic).strip()
|
|
196
|
-
|
|
213
|
+
provided_keywords = generation_settings.get("video_keywords")
|
|
214
|
+
if isinstance(provided_keywords, str):
|
|
215
|
+
provided_keywords = re.split(r"[,\n;|]+", provided_keywords)
|
|
216
|
+
provided_keywords = [str(item).strip() for item in provided_keywords or [] if str(item).strip()]
|
|
217
|
+
keywords = provided_keywords[:15] or (creative.get("keywords") if isinstance(creative.get("keywords"), list) else _keywords(topic, title, str(channel.get("niche") or "")))
|
|
197
218
|
title_artifact = _save_json_artifact(task_id, "title-keywords", {"topic": topic, "title": title, "keywords": keywords, "title_candidates": creative.get("title_candidates", [])})
|
|
198
219
|
_update(task_id, title=title, tags=keywords, artifacts={**artifacts, "title_keywords": title_artifact}, title_candidates=creative.get("title_candidates", []), progress=45)
|
|
199
220
|
|
package/package.json
CHANGED