Update app.py
Browse files
app.py
CHANGED
|
@@ -18,6 +18,7 @@ import os
|
|
| 18 |
import json
|
| 19 |
import datetime as _dt
|
| 20 |
from typing import List, Tuple, Optional
|
|
|
|
| 21 |
|
| 22 |
import requests
|
| 23 |
import pandas as pd
|
|
@@ -180,6 +181,11 @@ def _system_instruction(lang_code: str) -> str:
|
|
| 180 |
return "Reply in English."
|
| 181 |
return "Reply in the caller's language; if unclear, use concise professional English."
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 185 |
# Gradio handlers
|
|
@@ -309,6 +315,131 @@ def ui_analyze(selected_idx: Optional[int], df: pd.DataFrame,
|
|
| 309 |
except Exception:
|
| 310 |
pass
|
| 311 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 313 |
# Password / gating helpers
|
| 314 |
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -391,6 +522,23 @@ with gr.Blocks(title="Vochi CRM Call Logs (Gradio)") as demo:
|
|
| 391 |
analyze_btn = gr.Button("🧠 Analyze", variant="primary")
|
| 392 |
analysis_md = gr.Markdown()
|
| 393 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
# Wire events
|
| 395 |
# 1) Fetch button: gate by password
|
| 396 |
fetch_btn.click(
|
|
@@ -415,6 +563,14 @@ with gr.Blocks(title="Vochi CRM Call Logs (Gradio)") as demo:
|
|
| 415 |
outputs=[analysis_md],
|
| 416 |
)
|
| 417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
| 419 |
if __name__ == "__main__":
|
| 420 |
# On HF Spaces, just running this file is enough; launch() is fine for local dev, too.
|
|
|
|
| 18 |
import json
|
| 19 |
import datetime as _dt
|
| 20 |
from typing import List, Tuple, Optional
|
| 21 |
+
import time # ← ДАДАНА
|
| 22 |
|
| 23 |
import requests
|
| 24 |
import pandas as pd
|
|
|
|
| 181 |
return "Reply in English."
|
| 182 |
return "Reply in the caller's language; if unclear, use concise professional English."
|
| 183 |
|
| 184 |
+
# ← ДАДАНА: невялікі хелпер для выбару прампту
|
| 185 |
+
def _prepare_prompt(template_key: str, custom_prompt: str) -> str:
|
| 186 |
+
if template_key == "custom":
|
| 187 |
+
return (custom_prompt or "").strip() or PROMPT_TEMPLATES["simple"]
|
| 188 |
+
return PROMPT_TEMPLATES.get(template_key, PROMPT_TEMPLATES["simple"])
|
| 189 |
|
| 190 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 191 |
# Gradio handlers
|
|
|
|
| 315 |
except Exception:
|
| 316 |
pass
|
| 317 |
|
| 318 |
+
# ← ДАДАНА: Пакетны аналіз званкоў за дзень/час
|
| 319 |
+
def ui_analyze_calls_by_date(
|
| 320 |
+
authed: bool,
|
| 321 |
+
date_str: str,
|
| 322 |
+
template_key: str,
|
| 323 |
+
custom_prompt: str,
|
| 324 |
+
lang_code: str,
|
| 325 |
+
model_pref: str,
|
| 326 |
+
):
|
| 327 |
+
"""
|
| 328 |
+
Fetch calls for the given date and analyze them one-by-one with the same
|
| 329 |
+
settings/fields as the single-call AI Analysis tab. Save a .txt file and
|
| 330 |
+
show results on screen.
|
| 331 |
+
"""
|
| 332 |
+
# Гейт па паролі
|
| 333 |
+
if not authed:
|
| 334 |
+
return (
|
| 335 |
+
"🔒 Увядзіце пароль, каб запусціць аналіз.", # results_md
|
| 336 |
+
None, # file
|
| 337 |
+
"Доступ закрыты.", # status
|
| 338 |
+
gr.update(visible=True), # show pwd group
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
date_str = (date_str or "").strip()
|
| 342 |
+
if not date_str:
|
| 343 |
+
date_str = _today_str()
|
| 344 |
+
|
| 345 |
+
# 1) Атрымаць спіс званкоў за дату
|
| 346 |
+
try:
|
| 347 |
+
calls = fetch_calllogs(date_str)
|
| 348 |
+
df = pd.DataFrame(calls)
|
| 349 |
+
except Exception as e:
|
| 350 |
+
return (f"Не ўдалося загрузіць спіс: {e}", None, "", gr.update())
|
| 351 |
+
|
| 352 |
+
if df.empty:
|
| 353 |
+
return ("За гэты дзень званкоў не знойдзена.", None, "", gr.update())
|
| 354 |
+
|
| 355 |
+
# 2) Падрыхтоўка кліента мадэлі і прампту
|
| 356 |
+
if not _HAS_GENAI:
|
| 357 |
+
return ("❌ google-genai library not found.", None, "", gr.update())
|
| 358 |
+
|
| 359 |
+
api_key = os.environ.get("GOOGLE_API_KEY", "").strip()
|
| 360 |
+
if not api_key:
|
| 361 |
+
return ("GOOGLE_API_KEY не зададзены ў Secrets.", None, "", gr.update())
|
| 362 |
+
|
| 363 |
+
try:
|
| 364 |
+
client = genai.Client(api_key=api_key)
|
| 365 |
+
model_name = _resolve_model(client, model_pref)
|
| 366 |
+
except Exception as e:
|
| 367 |
+
return (f"Не ўдалося ініцыялізаваць кліента: {e}", None, "", gr.update())
|
| 368 |
+
|
| 369 |
+
sys_inst = _system_instruction(lang_code)
|
| 370 |
+
prompt = _prepare_prompt(template_key, custom_prompt)
|
| 371 |
+
|
| 372 |
+
# 3) Пакетны праход: па адным выкліку
|
| 373 |
+
results_blocks: List[str] = []
|
| 374 |
+
analyzed = 0
|
| 375 |
+
|
| 376 |
+
for i, row in df.iterrows():
|
| 377 |
+
unique_id = str(row.get("UniqueId", ""))
|
| 378 |
+
if not unique_id:
|
| 379 |
+
continue
|
| 380 |
+
|
| 381 |
+
# Скачваем/кэшынгуем mp3
|
| 382 |
+
try:
|
| 383 |
+
mp3_path = f"/tmp/call_{unique_id}.mp3"
|
| 384 |
+
if not os.path.exists(mp3_path) or os.path.getsize(mp3_path) == 0:
|
| 385 |
+
mp3_path, _ = fetch_mp3_by_unique_id(unique_id)
|
| 386 |
+
except Exception as e:
|
| 387 |
+
results_blocks.append(
|
| 388 |
+
f"### Call {i+1}\n"
|
| 389 |
+
f"- UniqueId: {unique_id}\n"
|
| 390 |
+
f"- Start: {row.get('Start','')}\n"
|
| 391 |
+
f"- CallerId: {row.get('CallerId','')}\n"
|
| 392 |
+
f"- Destination: {row.get('Destination','')}\n"
|
| 393 |
+
f"- Duration: {row.get('Duration','')}\n\n"
|
| 394 |
+
f"**Памылка загрузкі аўдыё:** {e}\n"
|
| 395 |
+
"---"
|
| 396 |
+
)
|
| 397 |
+
continue
|
| 398 |
+
|
| 399 |
+
# Upload + generate
|
| 400 |
+
try:
|
| 401 |
+
uploaded_file = client.files.upload(file=mp3_path)
|
| 402 |
+
merged = f"[SYSTEM INSTRUCTION: {sys_inst}]\n\n{prompt}"
|
| 403 |
+
resp = client.models.generate_content(
|
| 404 |
+
model=model_name,
|
| 405 |
+
contents=[uploaded_file, merged],
|
| 406 |
+
)
|
| 407 |
+
text = getattr(resp, "text", "") or "(пусты адказ)"
|
| 408 |
+
except Exception as e:
|
| 409 |
+
text = f"Памылка аналізу: {e}"
|
| 410 |
+
finally:
|
| 411 |
+
try:
|
| 412 |
+
if 'uploaded_file' in locals() and hasattr(uploaded_file, 'name'):
|
| 413 |
+
client.files.delete(name=uploaded_file.name)
|
| 414 |
+
except Exception:
|
| 415 |
+
pass
|
| 416 |
+
|
| 417 |
+
# Сфармаваць блок выніку для гэтага званка
|
| 418 |
+
block = (
|
| 419 |
+
f"### Call {i+1}\n"
|
| 420 |
+
f"- UniqueId: {unique_id}\n"
|
| 421 |
+
f"- Start: {row.get('Start','')}\n"
|
| 422 |
+
f"- CallerId: {row.get('CallerId','')}\n"
|
| 423 |
+
f"- Destination: {row.get('Destination','')}\n"
|
| 424 |
+
f"- Duration: {row.get('Duration','')}\n\n"
|
| 425 |
+
f"**Analysis:**\n\n{text}\n"
|
| 426 |
+
"---"
|
| 427 |
+
)
|
| 428 |
+
results_blocks.append(block)
|
| 429 |
+
analyzed += 1
|
| 430 |
+
|
| 431 |
+
if analyzed == 0:
|
| 432 |
+
return ("Не атрымалася прааналізаваць ніводнага званка.", None, "", gr.update())
|
| 433 |
+
|
| 434 |
+
# 4) Захаваць у .txt і вярнуць
|
| 435 |
+
results_text = "\n\n".join(results_blocks)
|
| 436 |
+
fname = f"/tmp/batch_analysis_{date_str}_{int(time.time())}.txt"
|
| 437 |
+
with open(fname, "w", encoding="utf-8") as f:
|
| 438 |
+
f.write(results_text)
|
| 439 |
+
|
| 440 |
+
status = f"Гатова ✅. Прааналізавана званкоў: {analyzed}."
|
| 441 |
+
return (results_text, fname, status, gr.update(visible=False))
|
| 442 |
+
|
| 443 |
# ─────────────────────────────────────────────────────────────────────────────
|
| 444 |
# Password / gating helpers
|
| 445 |
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
| 522 |
analyze_btn = gr.Button("🧠 Analyze", variant="primary")
|
| 523 |
analysis_md = gr.Markdown()
|
| 524 |
|
| 525 |
+
# ← ДАДАНА: новая ўкладка пакетнага аналізу
|
| 526 |
+
with gr.Tab("Аналіз званкоў за дзень/час"):
|
| 527 |
+
batch_date_inp = gr.Textbox(label="Date", value=_today_str(), scale=1)
|
| 528 |
+
|
| 529 |
+
with gr.Row():
|
| 530 |
+
tpl_batch_dd = gr.Dropdown(choices=TPL_OPTIONS, value="simple", label="Template")
|
| 531 |
+
lang_batch_dd = gr.Dropdown(choices=LANG_OPTIONS, value="default", label="Language")
|
| 532 |
+
model_batch_dd = gr.Dropdown(choices=MODEL_OPTIONS, value="models/gemini-2.5-flash", label="Model")
|
| 533 |
+
|
| 534 |
+
custom_batch_tb = gr.Textbox(label="Custom prompt", lines=8, visible=False)
|
| 535 |
+
|
| 536 |
+
batch_btn = gr.Button("прааналізаваць", variant="primary")
|
| 537 |
+
|
| 538 |
+
batch_status_md = gr.Markdown()
|
| 539 |
+
batch_results_md = gr.Markdown()
|
| 540 |
+
batch_file_out = gr.File(label="Вынікі (.txt)")
|
| 541 |
+
|
| 542 |
# Wire events
|
| 543 |
# 1) Fetch button: gate by password
|
| 544 |
fetch_btn.click(
|
|
|
|
| 563 |
outputs=[analysis_md],
|
| 564 |
)
|
| 565 |
|
| 566 |
+
# ← ДАДАНА: падзеі для новай укладкі
|
| 567 |
+
tpl_batch_dd.change(ui_toggle_custom_prompt, inputs=[tpl_batch_dd], outputs=[custom_batch_tb])
|
| 568 |
+
batch_btn.click(
|
| 569 |
+
ui_analyze_calls_by_date,
|
| 570 |
+
inputs=[authed, batch_date_inp, tpl_batch_dd, custom_batch_tb, lang_batch_dd, model_batch_dd],
|
| 571 |
+
outputs=[batch_results_md, batch_file_out, batch_status_md, pwd_group],
|
| 572 |
+
)
|
| 573 |
+
|
| 574 |
|
| 575 |
if __name__ == "__main__":
|
| 576 |
# On HF Spaces, just running this file is enough; launch() is fine for local dev, too.
|