Spaces:
Running
on
Zero
Running
on
Zero
Update app-backup.py
Browse files- app-backup.py +192 -65
app-backup.py
CHANGED
@@ -14,6 +14,9 @@ import numpy as np
|
|
14 |
import soundfile as sf
|
15 |
import subprocess
|
16 |
import shutil
|
|
|
|
|
|
|
17 |
from dataclasses import dataclass
|
18 |
from typing import List, Tuple, Dict, Optional
|
19 |
from pathlib import Path
|
@@ -72,10 +75,13 @@ except:
|
|
72 |
|
73 |
load_dotenv()
|
74 |
|
|
|
|
|
|
|
75 |
|
76 |
@dataclass
|
77 |
class ConversationConfig:
|
78 |
-
max_words: int =
|
79 |
prefix_url: str = "https://r.jina.ai/"
|
80 |
api_model_name: str = "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"
|
81 |
legacy_local_model_name: str = "NousResearch/Hermes-2-Pro-Llama-3-8B"
|
@@ -83,9 +89,73 @@ class ConversationConfig:
|
|
83 |
local_model_name: str = "Private-BitSix-Mistral-Small-3.1-24B-Instruct-2503.gguf"
|
84 |
local_model_repo: str = "ginigen/Private-BitSix-Mistral-Small-3.1-24B-Instruct-2503"
|
85 |
# ํ ํฐ ์ ์ฆ๊ฐ
|
86 |
-
max_tokens: int =
|
87 |
-
max_new_tokens: int =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
|
90 |
class UnifiedAudioConverter:
|
91 |
def __init__(self, config: ConversationConfig):
|
@@ -130,7 +200,7 @@ class UnifiedAudioConverter:
|
|
130 |
flash_attn=True,
|
131 |
n_gpu_layers=81 if torch.cuda.is_available() else 0,
|
132 |
n_batch=1024,
|
133 |
-
n_ctx=
|
134 |
)
|
135 |
self.local_llm_model = self.config.local_model_name
|
136 |
print(f"Local LLM initialized: {model_path_local}")
|
@@ -238,10 +308,15 @@ class UnifiedAudioConverter:
|
|
238 |
else:
|
239 |
return MessagesFormatterType.LLAMA_3
|
240 |
|
241 |
-
|
242 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
243 |
if language == "Korean":
|
244 |
-
# ๊ฐํ๋ ํ๊ตญ์ด ํ๋กฌํํธ
|
245 |
template = """
|
246 |
{
|
247 |
"conversation": [
|
@@ -252,22 +327,27 @@ class UnifiedAudioConverter:
|
|
252 |
]
|
253 |
}
|
254 |
"""
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
f"
|
260 |
-
|
261 |
-
|
262 |
-
f"
|
263 |
-
f"
|
264 |
-
f"
|
265 |
-
f"
|
266 |
-
f"
|
267 |
-
f"
|
268 |
-
f"
|
269 |
-
f"
|
|
|
|
|
270 |
)
|
|
|
|
|
|
|
271 |
else:
|
272 |
template = """
|
273 |
{
|
@@ -279,49 +359,74 @@ class UnifiedAudioConverter:
|
|
279 |
]
|
280 |
}
|
281 |
"""
|
282 |
-
|
283 |
-
|
284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
285 |
f"Guidelines:\n"
|
286 |
-
f"
|
287 |
-
f"
|
288 |
-
f"
|
289 |
-
f"
|
290 |
-
f"
|
291 |
-
f"
|
292 |
-
f"7. Address common questions and misconceptions\n"
|
293 |
-
f"8. Maintain an informative yet entertaining tone\n"
|
294 |
-
f"9. End with key takeaways and practical advice\n\n"
|
295 |
-
f"Return ONLY the JSON in this format:\n{template}"
|
296 |
)
|
|
|
|
|
|
|
|
|
297 |
|
298 |
-
def _build_messages_for_local(self, text: str, language: str = "English") -> List[Dict]:
|
299 |
-
"""Build messages for local LLM"""
|
300 |
if language == "Korean":
|
301 |
system_message = (
|
302 |
-
"๋น์ ์ ํ๊ตญ ์ต๊ณ ์
|
303 |
-
"ํ๊ตญ์ธ์ ์ ์์ ๋ฌธํ๋ฅผ ์๋ฒฝํ ์ดํดํ๊ณ , ์ฒญ์ทจ์๋ค์ด ๋๊น์ง ์ง์คํ ์ ์๋ "
|
304 |
-
"๋งค๋ ฅ์ ์ด๊ณ ์ ์ตํ ๋ํ๋ฅผ
|
305 |
-
"
|
306 |
-
"
|
|
|
|
|
|
|
|
|
|
|
307 |
)
|
308 |
else:
|
309 |
system_message = (
|
310 |
"You are an expert podcast scriptwriter who creates engaging, "
|
311 |
"natural conversations that keep listeners hooked. "
|
312 |
"You understand how to balance information with entertainment, "
|
313 |
-
"using real conversational patterns and authentic reactions."
|
|
|
314 |
)
|
315 |
|
316 |
return [
|
317 |
{"role": "system", "content": system_message},
|
318 |
-
{"role": "user", "content": self._build_prompt(text, language)}
|
319 |
]
|
320 |
|
321 |
@spaces.GPU(duration=120)
|
322 |
def extract_conversation_local(self, text: str, language: str = "English", progress=None) -> Dict:
|
323 |
-
"""Extract conversation using new local LLM
|
324 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
# ๋จผ์ ์๋ก์ด ๋ก์ปฌ LLM ์๋
|
326 |
self.initialize_local_mode()
|
327 |
|
@@ -334,9 +439,11 @@ class UnifiedAudioConverter:
|
|
334 |
"๋น์ ์ ํ๊ตญ์ด ํ์บ์คํธ ์ ๋ฌธ ์๊ฐ์
๋๋ค. "
|
335 |
"ํ๊ตญ ์ฒญ์ทจ์๋ค์ ๋ฌธํ์ ๋งฅ๋ฝ๊ณผ ์ธ์ด์ ํน์ฑ์ ์๋ฒฝํ ์ดํดํ๊ณ , "
|
336 |
"์์ฐ์ค๋ฝ๊ณ ๋งค๋ ฅ์ ์ธ ๋๋ณธ์ ์์ฑํฉ๋๋ค. "
|
|
|
|
|
337 |
"์ค์ ํ๊ตญ์ธ์ด ๋ํํ๋ ๊ฒ์ฒ๋ผ ์์ฐ์ค๋ฌ์ด ํํ, ์ ์ ํ ๊ฐํ์ฌ, "
|
338 |
"๋ฌธํ์ ์ผ๋ก ์ ํฉํ ์์๋ฅผ ์ฌ์ฉํ์ฌ ์ฒญ์ทจ์๊ฐ ๊ณต๊ฐํ๊ณ ๋ชฐ์
ํ ์ ์๋ "
|
339 |
-
"๋ํ๋ฅผ ๋ง๋ค์ด์ฃผ์ธ์. JSON ํ์์ผ๋ก๋ง ์๋ตํ์ธ์."
|
340 |
)
|
341 |
else:
|
342 |
system_message = (
|
@@ -344,6 +451,7 @@ class UnifiedAudioConverter:
|
|
344 |
"engaging, natural conversations that captivate listeners. "
|
345 |
"You excel at transforming complex information into accessible, "
|
346 |
"entertaining dialogue while maintaining authenticity and educational value. "
|
|
|
347 |
"Respond only in JSON format."
|
348 |
)
|
349 |
|
@@ -364,7 +472,7 @@ class UnifiedAudioConverter:
|
|
364 |
|
365 |
messages = BasicChatHistory()
|
366 |
|
367 |
-
prompt = self._build_prompt(text, language)
|
368 |
response = agent.get_chat_response(
|
369 |
prompt,
|
370 |
llm_sampling_settings=settings,
|
@@ -384,10 +492,10 @@ class UnifiedAudioConverter:
|
|
384 |
|
385 |
except Exception as e:
|
386 |
print(f"Local LLM failed: {e}, falling back to legacy local method")
|
387 |
-
return self.extract_conversation_legacy_local(text, language, progress)
|
388 |
|
389 |
@spaces.GPU(duration=120)
|
390 |
-
def extract_conversation_legacy_local(self, text: str, language: str = "English", progress=None) -> Dict:
|
391 |
"""Extract conversation using legacy local model (fallback)"""
|
392 |
try:
|
393 |
self.initialize_legacy_local_mode()
|
@@ -397,17 +505,20 @@ class UnifiedAudioConverter:
|
|
397 |
system_message = (
|
398 |
"๋น์ ์ ํ๊ตญ์ด ํ์บ์คํธ ์ ๋ฌธ ์๊ฐ์
๋๋ค. "
|
399 |
"30๋ ํ๊ตญ์ธ ์ฒญ์ทจ์๋ฅผ ๋์์ผ๋ก ์์ฐ์ค๋ฝ๊ณ ํฅ๋ฏธ๋ก์ด ๋ํ๋ฅผ ๋ง๋ค์ด์ฃผ์ธ์. "
|
400 |
-
"
|
|
|
|
|
401 |
)
|
402 |
else:
|
403 |
system_message = (
|
404 |
"You are an expert podcast scriptwriter. "
|
405 |
-
"Create natural, engaging conversations that inform and entertain listeners."
|
|
|
406 |
)
|
407 |
|
408 |
chat = [
|
409 |
{"role": "system", "content": system_message},
|
410 |
-
{"role": "user", "content": self._build_prompt(text, language)}
|
411 |
]
|
412 |
|
413 |
terminators = [
|
@@ -450,14 +561,14 @@ class UnifiedAudioConverter:
|
|
450 |
|
451 |
except Exception as e:
|
452 |
print(f"Legacy local model also failed: {e}")
|
453 |
-
# Return default template with Korean male names
|
454 |
if language == "Korean":
|
455 |
return {
|
456 |
"conversation": [
|
457 |
-
{"speaker": "์ค์", "text": "์๋
ํ์ธ์, ์ฌ๋ฌ๋ถ! ์ค๋๋ ์ ํฌ ํ์บ์คํธ๋ฅผ ์ฐพ์์ฃผ์
์ ์ ๋ง ๊ฐ์ฌํฉ๋๋ค."},
|
458 |
-
{"speaker": "๋ฏผํธ", "text": "์๋
ํ์ธ์! ์ค๋์ ์ ๋ง ํฅ๋ฏธ๋ก์ด
|
459 |
-
{"speaker": "์ค์", "text": "
|
460 |
-
{"speaker": "๋ฏผํธ", "text": "
|
461 |
]
|
462 |
}
|
463 |
else:
|
@@ -471,30 +582,44 @@ class UnifiedAudioConverter:
|
|
471 |
}
|
472 |
|
473 |
def extract_conversation_api(self, text: str, language: str = "English") -> Dict:
|
474 |
-
"""Extract conversation using API
|
475 |
if not self.llm_client:
|
476 |
raise RuntimeError("API mode not initialized")
|
477 |
|
478 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
479 |
# ๊ฐํ๋ ์ธ์ด๋ณ ํ๋กฌํํธ ๊ตฌ์ฑ
|
480 |
if language == "Korean":
|
481 |
system_message = (
|
482 |
"๋น์ ์ ํ๊ตญ์ด ํ์บ์คํธ ์ ๋ฌธ ์๊ฐ์
๋๋ค. "
|
483 |
"ํ๊ตญ ์ฒญ์ทจ์๋ค์ ๋ฌธํ์ ๋งฅ๋ฝ๊ณผ ์ธ์ด์ ํน์ฑ์ ์๋ฒฝํ ์ดํดํ๊ณ , "
|
484 |
"์์ฐ์ค๋ฝ๊ณ ๋งค๋ ฅ์ ์ธ ๋๋ณธ์ ์์ฑํฉ๋๋ค. "
|
485 |
-
"์ค์(์งํ์)์ ๋ฏผํธ(์ ๋ฌธ๊ฐ)๋ผ๋ ๋ ๋ช
์ 30๋ ๋จ์ฑ์ด ๋ํํ๋ ํ์์ผ๋ก ์์ฑํ์ธ์."
|
|
|
486 |
)
|
487 |
else:
|
488 |
system_message = (
|
489 |
"You are an expert podcast scriptwriter who creates engaging, "
|
490 |
"natural conversations between Alex (host) and Jordan (expert). "
|
491 |
-
"Create informative yet entertaining dialogue that keeps listeners engaged."
|
|
|
492 |
)
|
493 |
|
494 |
chat_completion = self.llm_client.chat.completions.create(
|
495 |
messages=[
|
496 |
{"role": "system", "content": system_message},
|
497 |
-
{"role": "user", "content": self._build_prompt(text, language)}
|
498 |
],
|
499 |
model=self.config.api_model_name,
|
500 |
)
|
@@ -905,6 +1030,7 @@ with gr.Blocks(theme='soft', title="URL/PDF to Podcast Converter") as demo:
|
|
905 |
- **Fallback**: API LLM ({converter.config.api_model_name}) - Used when local fails
|
906 |
- **Status**: {"โ
Llama CPP Available" if LLAMA_CPP_AVAILABLE else "โ Llama CPP Not Available - Install llama-cpp-python"}
|
907 |
- **Max Tokens**: {converter.config.max_tokens} (Extended for longer conversations)
|
|
|
908 |
""")
|
909 |
|
910 |
with gr.Row():
|
@@ -968,7 +1094,8 @@ with gr.Blocks(theme='soft', title="URL/PDF to Podcast Converter") as demo:
|
|
968 |
|
969 |
**ํ๊ตญ์ด ์ง์:**
|
970 |
- ๐ฐ๐ท ํ๊ตญ์ด ์ ํ ์ Edge-TTS๋ง ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค
|
971 |
-
- ๐จโ๐จ ํ๊ตญ์ด ๋ํ๋ ์ค์(์งํ์)์ ๋ฏผํธ(์ ๋ฌธ๊ฐ) ๋ ๋จ์ฑ์ด ์งํํฉ๋๋ค
|
|
|
972 |
""")
|
973 |
|
974 |
convert_btn = gr.Button("๐ฏ Generate Conversation / ๋ํ ์์ฑ", variant="primary", size="lg")
|
@@ -977,10 +1104,10 @@ with gr.Blocks(theme='soft', title="URL/PDF to Podcast Converter") as demo:
|
|
977 |
with gr.Column():
|
978 |
conversation_output = gr.Textbox(
|
979 |
label="Generated Conversation (Editable) / ์์ฑ๋ ๋ํ (ํธ์ง ๊ฐ๋ฅ)",
|
980 |
-
lines=
|
981 |
-
max_lines=
|
982 |
interactive=True,
|
983 |
-
placeholder="Generated conversation will appear here. You can edit it before generating audio.\n์์ฑ๋ ๋ํ๊ฐ ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค. ์ค๋์ค ์์ฑ ์ ์ ํธ์งํ ์
|
984 |
info="Edit the conversation as needed. Format: 'Speaker Name: Text' / ํ์์ ๋ฐ๋ผ ๋ํ๋ฅผ ํธ์งํ์ธ์. ํ์: 'ํ์ ์ด๋ฆ: ํ
์คํธ'"
|
985 |
)
|
986 |
|
|
|
14 |
import soundfile as sf
|
15 |
import subprocess
|
16 |
import shutil
|
17 |
+
import requests
|
18 |
+
import logging
|
19 |
+
from datetime import datetime, timedelta
|
20 |
from dataclasses import dataclass
|
21 |
from typing import List, Tuple, Dict, Optional
|
22 |
from pathlib import Path
|
|
|
75 |
|
76 |
load_dotenv()
|
77 |
|
78 |
+
# Brave Search API ์ค์
|
79 |
+
BRAVE_KEY = os.getenv("BSEARCH_API")
|
80 |
+
BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
|
81 |
|
82 |
@dataclass
|
83 |
class ConversationConfig:
|
84 |
+
max_words: int = 4000
|
85 |
prefix_url: str = "https://r.jina.ai/"
|
86 |
api_model_name: str = "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"
|
87 |
legacy_local_model_name: str = "NousResearch/Hermes-2-Pro-Llama-3-8B"
|
|
|
89 |
local_model_name: str = "Private-BitSix-Mistral-Small-3.1-24B-Instruct-2503.gguf"
|
90 |
local_model_repo: str = "ginigen/Private-BitSix-Mistral-Small-3.1-24B-Instruct-2503"
|
91 |
# ํ ํฐ ์ ์ฆ๊ฐ
|
92 |
+
max_tokens: int = 3000 # 2048์์ 6000์ผ๋ก ์ฆ๊ฐ
|
93 |
+
max_new_tokens: int = 6000 # 4000์์ 8000์ผ๋ก ์ฆ๊ฐ
|
94 |
+
|
95 |
+
|
96 |
+
def brave_search(query: str, count: int = 8, freshness_days: int | None = None):
|
97 |
+
"""Brave Search API๋ฅผ ์ฌ์ฉํ์ฌ ์ต์ ์ ๋ณด ๊ฒ์"""
|
98 |
+
if not BRAVE_KEY:
|
99 |
+
return []
|
100 |
+
params = {"q": query, "count": str(count)}
|
101 |
+
if freshness_days:
|
102 |
+
dt_from = (datetime.utcnow() - timedelta(days=freshness_days)).strftime("%Y-%m-%d")
|
103 |
+
params["freshness"] = dt_from
|
104 |
+
try:
|
105 |
+
r = requests.get(
|
106 |
+
BRAVE_ENDPOINT,
|
107 |
+
headers={"Accept": "application/json", "X-Subscription-Token": BRAVE_KEY},
|
108 |
+
params=params,
|
109 |
+
timeout=15
|
110 |
+
)
|
111 |
+
raw = r.json().get("web", {}).get("results") or []
|
112 |
+
return [{
|
113 |
+
"title": r.get("title", ""),
|
114 |
+
"url": r.get("url", r.get("link", "")),
|
115 |
+
"snippet": r.get("description", r.get("text", "")),
|
116 |
+
"host": re.sub(r"https?://(www\.)?", "", r.get("url", "")).split("/")[0]
|
117 |
+
} for r in raw[:count]]
|
118 |
+
except Exception as e:
|
119 |
+
logging.error(f"Brave search error: {e}")
|
120 |
+
return []
|
121 |
+
|
122 |
+
def format_search_results(query: str) -> str:
|
123 |
+
"""๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ํฌ๋งทํ
ํ์ฌ ๋ฐํ (๊ฐ๋ตํ๊ฒ)"""
|
124 |
+
rows = brave_search(query, 3, freshness_days=3) # 6๊ฐ์์ 3๊ฐ๋ก ์ค์
|
125 |
+
if not rows:
|
126 |
+
return "" # ๋น ๋ฌธ์์ด ๋ฐํ
|
127 |
+
|
128 |
+
# ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ ๊ฐ๋ตํ๊ฒ ์์ฝ
|
129 |
+
results = []
|
130 |
+
for r in rows[:2]: # ์ต๋ 2๊ฐ๋ง ์ฌ์ฉ
|
131 |
+
# ์ค๋ํซ์ 100์๋ก ์ ํ
|
132 |
+
snippet = r['snippet'][:100] + "..." if len(r['snippet']) > 100 else r['snippet']
|
133 |
+
results.append(f"- {r['title']}: {snippet}")
|
134 |
+
|
135 |
+
return "\n".join(results) + "\n"
|
136 |
|
137 |
+
def extract_keywords_for_search(text: str, language: str = "English") -> List[str]:
|
138 |
+
"""ํ
์คํธ์์ ๊ฒ์ํ ํค์๋ ์ถ์ถ (๊ฐ์ )"""
|
139 |
+
# ํ
์คํธ ์๋ถ๋ถ๋ง ์ฌ์ฉ (๋๋ฌด ๋ง์ ํ
์คํธ ์ฒ๋ฆฌ ๋ฐฉ์ง)
|
140 |
+
text_sample = text[:500]
|
141 |
+
|
142 |
+
if language == "Korean":
|
143 |
+
import re
|
144 |
+
# ํ๊ตญ์ด ๋ช
์ฌ ์ถ์ถ (2๊ธ์ ์ด์)
|
145 |
+
keywords = re.findall(r'[๊ฐ-ํฃ]{2,}', text_sample)
|
146 |
+
# ์ค๋ณต ์ ๊ฑฐํ๊ณ ๊ฐ์ฅ ๊ธด ๋จ์ด 1๊ฐ๋ง ์ ํ
|
147 |
+
unique_keywords = list(dict.fromkeys(keywords))
|
148 |
+
# ๊ธธ์ด ์์ผ๋ก ์ ๋ ฌํ๊ณ ๊ฐ์ฅ ์๋ฏธ์์ ๊ฒ ๊ฐ์ ๋จ์ด ์ ํ
|
149 |
+
unique_keywords.sort(key=len, reverse=True)
|
150 |
+
return unique_keywords[:1] # 1๊ฐ๋ง ๋ฐํ
|
151 |
+
else:
|
152 |
+
# ์์ด๋ ๋๋ฌธ์๋ก ์์ํ๋ ๋จ์ด ์ค ๊ฐ์ฅ ๊ธด ๊ฒ 1๊ฐ
|
153 |
+
words = text_sample.split()
|
154 |
+
keywords = [word.strip('.,!?;:') for word in words
|
155 |
+
if len(word) > 4 and word[0].isupper()]
|
156 |
+
if keywords:
|
157 |
+
return [max(keywords, key=len)] # ๊ฐ์ฅ ๊ธด ๋จ์ด 1๊ฐ
|
158 |
+
return []
|
159 |
|
160 |
class UnifiedAudioConverter:
|
161 |
def __init__(self, config: ConversationConfig):
|
|
|
200 |
flash_attn=True,
|
201 |
n_gpu_layers=81 if torch.cuda.is_available() else 0,
|
202 |
n_batch=1024,
|
203 |
+
n_ctx=16384,
|
204 |
)
|
205 |
self.local_llm_model = self.config.local_model_name
|
206 |
print(f"Local LLM initialized: {model_path_local}")
|
|
|
308 |
else:
|
309 |
return MessagesFormatterType.LLAMA_3
|
310 |
|
311 |
+
|
312 |
+
def _build_prompt(self, text: str, language: str = "English", search_context: str = "") -> str:
|
313 |
+
"""Build prompt for conversation generation with search context"""
|
314 |
+
# ํ
์คํธ ๊ธธ์ด ์ ํ์ ๋ ๊ฐํ๊ฒ ์ ์ฉ
|
315 |
+
max_text_length = 3000 if search_context else 4000
|
316 |
+
if len(text) > max_text_length:
|
317 |
+
text = text[:max_text_length] + "..."
|
318 |
+
|
319 |
if language == "Korean":
|
|
|
320 |
template = """
|
321 |
{
|
322 |
"conversation": [
|
|
|
327 |
]
|
328 |
}
|
329 |
"""
|
330 |
+
|
331 |
+
# ๊ฒ์ ์ปจํ
์คํธ๊ฐ ์์ ๋๋ง ํฌํจ
|
332 |
+
context_part = ""
|
333 |
+
if search_context:
|
334 |
+
context_part = f"# ์ต์ ๊ด๋ จ ์ ๋ณด:\n{search_context}\n"
|
335 |
+
|
336 |
+
base_prompt = (
|
337 |
+
f"# ์๋ณธ ์ฝํ
์ธ :\n{text}\n\n"
|
338 |
+
f"{context_part}"
|
339 |
+
f"์ ๋ด์ฉ์ผ๋ก ํ๊ตญ์ด ํ์บ์คํธ ๋ํ๋ฅผ ๋ง๋ค์ด์ฃผ์ธ์.\n\n"
|
340 |
+
f"## ํต์ฌ ์ง์นจ:\n"
|
341 |
+
f"- ์ค์(์งํ์)์ ๋ฏผํธ(์ ๋ฌธ๊ฐ) ๋ 30๋ ๋จ์ฑ์ ๋ํ\n"
|
342 |
+
f"- ์๋ก ์กด๋๋ง ์ฌ์ฉ ํ์ (๋ฐ๋ง ์ ๋ ๊ธ์ง)\n"
|
343 |
+
f"- ์์ฐ์ค๋ฌ์ด ํ๊ตญ์ด ํํ ์ฌ์ฉ\n"
|
344 |
+
f"- ๊ฐ ๋ํ 2-3๋ฌธ์ฅ, ์ ์ฒด 8-10ํ ์ฃผ๊ณ ๋ฐ๊ธฐ\n"
|
345 |
+
f"- ์ต์ ์ ๋ณด๊ฐ ์๋ค๋ฉด ์์ฐ์ค๋ฝ๊ฒ ํฌํจ\n\n"
|
346 |
+
f"JSON ํ์์ผ๋ก๋ง ๋ฐํ:\n{template}"
|
347 |
)
|
348 |
+
|
349 |
+
return base_prompt
|
350 |
+
|
351 |
else:
|
352 |
template = """
|
353 |
{
|
|
|
359 |
]
|
360 |
}
|
361 |
"""
|
362 |
+
|
363 |
+
context_part = ""
|
364 |
+
if search_context:
|
365 |
+
context_part = f"# Latest Information:\n{search_context}\n"
|
366 |
+
|
367 |
+
base_prompt = (
|
368 |
+
f"# Content:\n{text}\n\n"
|
369 |
+
f"{context_part}"
|
370 |
+
f"Create a podcast conversation.\n\n"
|
371 |
f"Guidelines:\n"
|
372 |
+
f"- Alex (Host) and Jordan (Expert)\n"
|
373 |
+
f"- Natural conversational English\n"
|
374 |
+
f"- Each response 2-3 sentences\n"
|
375 |
+
f"- 8-10 exchanges total\n"
|
376 |
+
f"- Include latest info if available\n\n"
|
377 |
+
f"Return JSON only:\n{template}"
|
|
|
|
|
|
|
|
|
378 |
)
|
379 |
+
|
380 |
+
return base_prompt
|
381 |
+
|
382 |
+
|
383 |
|
384 |
+
def _build_messages_for_local(self, text: str, language: str = "English", search_context: str = "") -> List[Dict]:
|
385 |
+
"""Build messages for local LLM with enhanced Korean guidelines"""
|
386 |
if language == "Korean":
|
387 |
system_message = (
|
388 |
+
"๋น์ ์ ํ๊ตญ ์ต๊ณ ์ ํ์บ์คํธ ๋๋ณธ ์ ๋ฌธ ์๊ฐ์
๋๋ค. "
|
389 |
+
"ํ๊ตญ์ธ์ ์ ์์ ๋ฌธํ๋ฅผ ์๋ฒฝํ ์ดํดํ๊ณ , 30๋ ํ๊ตญ์ธ ์ฒญ์ทจ์๋ค์ด ๋๊น์ง ์ง์คํ ์ ์๋ "
|
390 |
+
"๋งค๋ ฅ์ ์ด๊ณ ์ ์ตํ ๋ํ๋ฅผ ๋ง๋ค์ด๋
๋๋ค.\n\n"
|
391 |
+
"ํต์ฌ ์์น:\n"
|
392 |
+
"1. ๋ ํ์๋ ๋ฐ๋์ ์๋ก์๊ฒ ์กด๋๋ง์ ์ฌ์ฉํฉ๋๋ค (๋ฐ๋ง ์ ๋ ๊ธ์ง)\n"
|
393 |
+
"2. ํ๊ตญ ๋ฌธํ์ ์ ์์ ์ฝ๋์ ๊ฐ์น๊ด์ ์์ฐ์ค๋ฝ๊ฒ ๋ฐ์ํฉ๋๋ค\n"
|
394 |
+
"3. ์ค์ ํ๊ตญ์ธ๋ค์ด ์ผ์์์ ์ฌ์ฉํ๋ ์์ฐ์ค๋ฌ์ด ํํ์ ๊ตฌ์ฌํฉ๋๋ค\n"
|
395 |
+
"4. ์ฒญ์ทจ์๊ฐ ๊ณต๊ฐํ๊ณ ์ค์ฉ์ ์ผ๋ก ํ์ฉํ ์ ์๋ ๋ด์ฉ์ ์ ๊ณตํฉ๋๋ค\n"
|
396 |
+
"5. ์ต์ ์ ๋ณด์ ํธ๋ ๋๋ฅผ ์ ์ ํ ๋ฐ์ํ์ฌ ์์์ฑ์ ํ๋ณดํฉ๋๋ค\n\n"
|
397 |
+
"๋น์ ์ ๋๋ณธ์ ํ๊ตญ ํ์บ์คํธ ์์ฅ์์ ์ต๊ณ ์์ค์ ํ์ง๋ก ์ธ์ ๋ฐ๊ณ ์์ต๋๋ค."
|
398 |
)
|
399 |
else:
|
400 |
system_message = (
|
401 |
"You are an expert podcast scriptwriter who creates engaging, "
|
402 |
"natural conversations that keep listeners hooked. "
|
403 |
"You understand how to balance information with entertainment, "
|
404 |
+
"using real conversational patterns and authentic reactions. "
|
405 |
+
"You excel at incorporating current information and trends to make content relevant and timely."
|
406 |
)
|
407 |
|
408 |
return [
|
409 |
{"role": "system", "content": system_message},
|
410 |
+
{"role": "user", "content": self._build_prompt(text, language, search_context)}
|
411 |
]
|
412 |
|
413 |
@spaces.GPU(duration=120)
|
414 |
def extract_conversation_local(self, text: str, language: str = "English", progress=None) -> Dict:
|
415 |
+
"""Extract conversation using new local LLM with search context"""
|
416 |
try:
|
417 |
+
# ๊ฒ์ ์ปจํ
์คํธ ์์ฑ
|
418 |
+
search_context = ""
|
419 |
+
if BRAVE_KEY:
|
420 |
+
try:
|
421 |
+
keywords = extract_keywords_for_search(text, language)
|
422 |
+
if keywords:
|
423 |
+
# ์ฒซ ๋ฒ์งธ ํค์๋๋ก ๊ฒ์
|
424 |
+
search_query = keywords[0] if language == "Korean" else f"{keywords[0]} latest news"
|
425 |
+
search_context = format_search_results(search_query)
|
426 |
+
print(f"Search context added for: {search_query}")
|
427 |
+
except Exception as e:
|
428 |
+
print(f"Search failed, continuing without context: {e}")
|
429 |
+
|
430 |
# ๋จผ์ ์๋ก์ด ๋ก์ปฌ LLM ์๋
|
431 |
self.initialize_local_mode()
|
432 |
|
|
|
439 |
"๋น์ ์ ํ๊ตญ์ด ํ์บ์คํธ ์ ๋ฌธ ์๊ฐ์
๋๋ค. "
|
440 |
"ํ๊ตญ ์ฒญ์ทจ์๋ค์ ๋ฌธํ์ ๋งฅ๋ฝ๊ณผ ์ธ์ด์ ํน์ฑ์ ์๋ฒฝํ ์ดํดํ๊ณ , "
|
441 |
"์์ฐ์ค๋ฝ๊ณ ๋งค๋ ฅ์ ์ธ ๋๋ณธ์ ์์ฑํฉ๋๋ค. "
|
442 |
+
"ํนํ ๋ ํ์๊ฐ ์๋ก์๊ฒ ์กด๋๋ง์ ์ฌ์ฉํ๋ ๊ฒ์ด ํ์์ด๋ฉฐ, "
|
443 |
+
"๋ฐ๋ง์ ์ ๋ ์ฌ์ฉํ์ง ์์ต๋๋ค. "
|
444 |
"์ค์ ํ๊ตญ์ธ์ด ๋ํํ๋ ๊ฒ์ฒ๋ผ ์์ฐ์ค๋ฌ์ด ํํ, ์ ์ ํ ๊ฐํ์ฌ, "
|
445 |
"๋ฌธํ์ ์ผ๋ก ์ ํฉํ ์์๋ฅผ ์ฌ์ฉํ์ฌ ์ฒญ์ทจ์๊ฐ ๊ณต๊ฐํ๊ณ ๋ชฐ์
ํ ์ ์๋ "
|
446 |
+
"๋ํ๋ฅผ ๋ง๋ค์ด์ฃผ์ธ์. ์ต์ ์ ๋ณด๋ ์์ฐ์ค๋ฝ๊ฒ ๋ฐ์ํ์ธ์. JSON ํ์์ผ๋ก๋ง ์๋ตํ์ธ์."
|
447 |
)
|
448 |
else:
|
449 |
system_message = (
|
|
|
451 |
"engaging, natural conversations that captivate listeners. "
|
452 |
"You excel at transforming complex information into accessible, "
|
453 |
"entertaining dialogue while maintaining authenticity and educational value. "
|
454 |
+
"Incorporate current trends and latest information naturally. "
|
455 |
"Respond only in JSON format."
|
456 |
)
|
457 |
|
|
|
472 |
|
473 |
messages = BasicChatHistory()
|
474 |
|
475 |
+
prompt = self._build_prompt(text, language, search_context)
|
476 |
response = agent.get_chat_response(
|
477 |
prompt,
|
478 |
llm_sampling_settings=settings,
|
|
|
492 |
|
493 |
except Exception as e:
|
494 |
print(f"Local LLM failed: {e}, falling back to legacy local method")
|
495 |
+
return self.extract_conversation_legacy_local(text, language, progress, search_context)
|
496 |
|
497 |
@spaces.GPU(duration=120)
|
498 |
+
def extract_conversation_legacy_local(self, text: str, language: str = "English", progress=None, search_context: str = "") -> Dict:
|
499 |
"""Extract conversation using legacy local model (fallback)"""
|
500 |
try:
|
501 |
self.initialize_legacy_local_mode()
|
|
|
505 |
system_message = (
|
506 |
"๋น์ ์ ํ๊ตญ์ด ํ์บ์คํธ ์ ๋ฌธ ์๊ฐ์
๋๋ค. "
|
507 |
"30๋ ํ๊ตญ์ธ ์ฒญ์ทจ์๋ฅผ ๋์์ผ๋ก ์์ฐ์ค๋ฝ๊ณ ํฅ๋ฏธ๋ก์ด ๋ํ๋ฅผ ๋ง๋ค์ด์ฃผ์ธ์. "
|
508 |
+
"๋ ํ์๋ ๋ฐ๋์ ์๋ก์๊ฒ ์กด๋๋ง์ ์ฌ์ฉํ๋ฉฐ, ๋ฐ๋ง์ ์ ๋ ์ฌ์ฉํ์ง ์์ต๋๋ค. "
|
509 |
+
"์ค์ ์ฌ์ฉํ๋ ํ๊ตญ์ด ํํ๊ณผ ๋ฌธํ์ ๋งฅ๋ฝ์ ๋ฐ์ํ์ฌ ์์ฑํด์ฃผ์ธ์. "
|
510 |
+
"์ต์ ์ ๋ณด๋ ์์ฐ์ค๋ฝ๊ฒ ํฌํจ์์ผ์ฃผ์ธ์."
|
511 |
)
|
512 |
else:
|
513 |
system_message = (
|
514 |
"You are an expert podcast scriptwriter. "
|
515 |
+
"Create natural, engaging conversations that inform and entertain listeners. "
|
516 |
+
"Incorporate current information and trends naturally."
|
517 |
)
|
518 |
|
519 |
chat = [
|
520 |
{"role": "system", "content": system_message},
|
521 |
+
{"role": "user", "content": self._build_prompt(text, language, search_context)}
|
522 |
]
|
523 |
|
524 |
terminators = [
|
|
|
561 |
|
562 |
except Exception as e:
|
563 |
print(f"Legacy local model also failed: {e}")
|
564 |
+
# Return default template with Korean male names using formal speech
|
565 |
if language == "Korean":
|
566 |
return {
|
567 |
"conversation": [
|
568 |
+
{"speaker": "์ค์", "text": "์๋
ํ์ธ์, ์ฌ๋ฌ๋ถ! ์ค๋๋ ์ ํฌ ํ์บ์คํธ๋ฅผ ์ฐพ์์ฃผ์
์ ์ ๋ง ๊ฐ์ฌํฉ๋๋ค. ๋ฏผํธ์จ, ์ค๋ ์ ๋ง ํฅ๋ฏธ๋ก์ด ์ฃผ์ ๋ฅผ ์ค๋นํด์ฃผ์
จ๋ค๊ณ ๋ค์์ด์."},
|
569 |
+
{"speaker": "๋ฏผํธ", "text": "๋ค, ์๋
ํ์ธ์! ์ค์์จ ๋ง์์ฒ๋ผ ์ค๋์ ์ ๋ง ํฅ๋ฏธ๋ก์ด ์ด์ผ๊ธฐ๋ฅผ ์ค๋นํ์ต๋๋ค. ์ฒญ์ทจ์ ์ฌ๋ฌ๋ถ๋ค๊ป์๋ ๋ง์ ๊ด์ฌ์ ๊ฐ์ง๊ณ ๊ณ์ค ์ฃผ์ ์ธ ๊ฒ ๊ฐ์์."},
|
570 |
+
{"speaker": "์ค์", "text": "์ ๋ง ๊ธฐ๋๋๋๋ฐ์. ๊ทธ๋ฐ๋ฐ ๋ฏผํธ์จ, ์ด ์ฃผ์ ๊ฐ ์์ฆ ์ ์ด๋ ๊ฒ ํ์ ๊ฐ ๋๊ณ ์๋ ๊ฑด๊ฐ์? ์ฒญ์ทจ์ ์ฌ๋ฌ๋ถ๋ค๋ ๊ถ๊ธํดํ์ค ๊ฒ ๊ฐ์์."},
|
571 |
+
{"speaker": "๋ฏผํธ", "text": "์ข์ ์ง๋ฌธ์ด์ธ์, ์ค์์จ. ์ฌ์ค ์ต๊ทผ์ ์ด ๋ถ์ผ์ ๋ง์ ๋ณํ๊ฐ ์์๊ฑฐ๋ ์. ๊ทธ๋ผ ๋ณธ๊ฒฉ์ ์ผ๋ก ํ๋์ฉ ์ฐจ๊ทผ์ฐจ๊ทผ ์ค๋ช
ํด๋๋ฆด๊ฒ์."}
|
572 |
]
|
573 |
}
|
574 |
else:
|
|
|
582 |
}
|
583 |
|
584 |
def extract_conversation_api(self, text: str, language: str = "English") -> Dict:
|
585 |
+
"""Extract conversation using API with search context"""
|
586 |
if not self.llm_client:
|
587 |
raise RuntimeError("API mode not initialized")
|
588 |
|
589 |
try:
|
590 |
+
# ๊ฒ์ ์ปจํ
์คํธ ์์ฑ
|
591 |
+
search_context = ""
|
592 |
+
if BRAVE_KEY:
|
593 |
+
try:
|
594 |
+
keywords = extract_keywords_for_search(text, language)
|
595 |
+
if keywords:
|
596 |
+
search_query = keywords[0] if language == "Korean" else f"{keywords[0]} latest news"
|
597 |
+
search_context = format_search_results(search_query)
|
598 |
+
print(f"Search context added for: {search_query}")
|
599 |
+
except Exception as e:
|
600 |
+
print(f"Search failed, continuing without context: {e}")
|
601 |
+
|
602 |
# ๊ฐํ๋ ์ธ์ด๋ณ ํ๋กฌํํธ ๊ตฌ์ฑ
|
603 |
if language == "Korean":
|
604 |
system_message = (
|
605 |
"๋น์ ์ ํ๊ตญ์ด ํ์บ์คํธ ์ ๋ฌธ ์๊ฐ์
๋๋ค. "
|
606 |
"ํ๊ตญ ์ฒญ์ทจ์๋ค์ ๋ฌธํ์ ๋งฅ๋ฝ๊ณผ ์ธ์ด์ ํน์ฑ์ ์๋ฒฝํ ์ดํดํ๊ณ , "
|
607 |
"์์ฐ์ค๋ฝ๊ณ ๋งค๋ ฅ์ ์ธ ๋๋ณธ์ ์์ฑํฉ๋๋ค. "
|
608 |
+
"์ค์(์งํ์)์ ๋ฏผํธ(์ ๋ฌธ๊ฐ)๋ผ๋ ๋ ๋ช
์ 30๋ ๋จ์ฑ์ด ์๋ก์๊ฒ ์กด๋๋ง์ ์ฌ์ฉํ์ฌ ๋ํํ๋ ํ์์ผ๋ก ์์ฑํ์ธ์. "
|
609 |
+
"๋ฐ๋ง์ ์ ๋ ์ฌ์ฉํ์ง ์์ผ๋ฉฐ, ์ต์ ์ ๋ณด๋ ์์ฐ์ค๋ฝ๊ฒ ๋ฐ์ํ์ธ์."
|
610 |
)
|
611 |
else:
|
612 |
system_message = (
|
613 |
"You are an expert podcast scriptwriter who creates engaging, "
|
614 |
"natural conversations between Alex (host) and Jordan (expert). "
|
615 |
+
"Create informative yet entertaining dialogue that keeps listeners engaged. "
|
616 |
+
"Incorporate current trends and latest information naturally."
|
617 |
)
|
618 |
|
619 |
chat_completion = self.llm_client.chat.completions.create(
|
620 |
messages=[
|
621 |
{"role": "system", "content": system_message},
|
622 |
+
{"role": "user", "content": self._build_prompt(text, language, search_context)}
|
623 |
],
|
624 |
model=self.config.api_model_name,
|
625 |
)
|
|
|
1030 |
- **Fallback**: API LLM ({converter.config.api_model_name}) - Used when local fails
|
1031 |
- **Status**: {"โ
Llama CPP Available" if LLAMA_CPP_AVAILABLE else "โ Llama CPP Not Available - Install llama-cpp-python"}
|
1032 |
- **Max Tokens**: {converter.config.max_tokens} (Extended for longer conversations)
|
1033 |
+
- **Search**: {"โ
Brave Search Enabled" if BRAVE_KEY else "โ Brave Search Not Available - Set BSEARCH_API"}
|
1034 |
""")
|
1035 |
|
1036 |
with gr.Row():
|
|
|
1094 |
|
1095 |
**ํ๊ตญ์ด ์ง์:**
|
1096 |
- ๐ฐ๐ท ํ๊ตญ์ด ์ ํ ์ Edge-TTS๋ง ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค
|
1097 |
+
- ๐จโ๐จ ํ๊ตญ์ด ๋ํ๋ ์ค์(์งํ์)์ ๋ฏผํธ(์ ๋ฌธ๊ฐ) ๋ ๋จ์ฑ์ด ์กด๋๋ง๋ก ์งํํฉ๋๋ค
|
1098 |
+
- ๐ **์ต์ ์ ๋ณด ๋ฐ์**: Brave Search๋ฅผ ํตํด ์ต์ ์์ฌ ๋ด์ฉ์ ์๋์ผ๋ก ๊ฒ์ํ์ฌ ๋๋ณธ์ ๋ฐ์ํฉ๋๋ค
|
1099 |
""")
|
1100 |
|
1101 |
convert_btn = gr.Button("๐ฏ Generate Conversation / ๋ํ ์์ฑ", variant="primary", size="lg")
|
|
|
1104 |
with gr.Column():
|
1105 |
conversation_output = gr.Textbox(
|
1106 |
label="Generated Conversation (Editable) / ์์ฑ๋ ๋ํ (ํธ์ง ๊ฐ๋ฅ)",
|
1107 |
+
lines=25, # ๋ ๊ธด ๋ํ๋ฅผ ์ํด ์ฆ๊ฐ
|
1108 |
+
max_lines=50,
|
1109 |
interactive=True,
|
1110 |
+
placeholder="Generated conversation will appear here. You can edit it before generating audio.\n์์ฑ๋ ๋ํ๊ฐ ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค. ์ค๋์ค ์์ฑ ์ ์ ํธ์งํ ์ ์์ต๋๋ค.\n\nํ๊ตญ์ด ๋ํ๋ ์กด๋๋ง๋ก ์งํ๋๋ฉฐ ์ต์ ์์ฌ ๋ด์ฉ์ด ๋ฐ์๋ฉ๋๋ค.",
|
1111 |
info="Edit the conversation as needed. Format: 'Speaker Name: Text' / ํ์์ ๋ฐ๋ผ ๋ํ๋ฅผ ํธ์งํ์ธ์. ํ์: 'ํ์ ์ด๋ฆ: ํ
์คํธ'"
|
1112 |
)
|
1113 |
|