archivartaunik commited on
Commit
8c462c2
Β·
verified Β·
1 Parent(s): 406ed04

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +42 -53
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # HF Spaces / Gradio app: Vochi CRM call logs + Gemini analysis
2
  # ─────────────────────────────────────────────────────────────────────────────
3
  # How to deploy (short):
4
  # 1) Create a new Space (Python + Gradio).
@@ -7,25 +7,11 @@
7
  # 4) In the Space β†’ Settings β†’ Repository secrets, add:
8
  # - VOCHI_BASE_URL (e.g. https://crm.vochi.by/api)
9
  # - VOCHI_CLIENT_ID (client id string)
10
- # - GOOGLE_API_KEY (Gemini API key)
11
- # 5) Tokens/keys are NEVER accepted via UI; the app reads ONLY from Space Secrets.
12
- #
13
- # requirements.txt (copy these lines into a separate file):
14
- # -----------------------------------------------------------------------------
15
- # gradio>=4.40.0
16
- # requests>=2.31.0
17
- # pandas>=2.2.2
18
- # numpy>=1.26.4
19
- # soundfile>=0.12.1
20
- # google-genai>=0.6.0
21
- # -----------------------------------------------------------------------------
22
- #
23
- # Notes:
24
- # - Audio playback & download are handled inside the app; the MP3 is saved under /tmp.
25
- # - Gemini accepts MP3 uploads directly, so WAV conversion is not strictly required.
26
- # - If your Vochi instance needs auth, set an Authorization header below.
27
  #
28
- # UI language: Belarusian.
29
 
30
  from __future__ import annotations
31
  import os
@@ -50,7 +36,7 @@ except Exception:
50
  # Config
51
  # ─────────────────────────────────────────────────────────────────────────────
52
  BASE_URL = os.environ.get("VOCHI_BASE_URL", "https://crm.vochi.by/api")
53
- CLIENT_ID = os.environ.get("VOCHI_CLIENT_ID", "elq3tE3JhZWuKi8HsGI6PkipkYiUbjJN")
54
 
55
  # If your API needs auth, fill it here (or via VOCHI_BEARER in Secrets)
56
  _AUTH_TOKEN = os.environ.get("VOCHI_BEARER", "").strip()
@@ -145,6 +131,7 @@ MODEL_OPTIONS = [
145
  # ─────────────────────────────────────────────────────────────────────────────
146
  # Utilities
147
  # ─────────────────────────────────────────────────────────────────────────────
 
148
  def label_row(row: dict) -> str:
149
  start = row.get("Start", "")
150
  src = row.get("CallerId", "")
@@ -194,12 +181,13 @@ def _system_instruction(lang_code: str) -> str:
194
  # ─────────────────────────────────────────────────────────────────────────────
195
  # Gradio handlers
196
  # ─────────────────────────────────────────────────────────────────────────────
 
197
  def ui_fetch_calls(date_str: str):
198
  try:
199
  items = fetch_calllogs(date_str.strip())
200
  df = pd.DataFrame(items)
201
  opts = [(label_row(r), i) for i, r in df.iterrows()]
202
- msg = f"Π—Π½ΠΎΠΉΠ΄Π·Π΅Π½Π° званкоў: {len(df)}"
203
  # Update dropdown choices and default value
204
  dd = gr.update(choices=[(lbl, idx) for lbl, idx in opts], value=(opts[0][1] if opts else None))
205
  return df, dd, msg
@@ -209,18 +197,18 @@ def ui_fetch_calls(date_str: str):
209
  body = e.response.text[:800]
210
  except Exception:
211
  pass
212
- return pd.DataFrame(), gr.update(choices=[], value=None), f"HTTP ΠΏΠ°ΠΌΡ‹Π»ΠΊΠ°: {e}\n{body}"
213
  except Exception as e:
214
- return pd.DataFrame(), gr.update(choices=[], value=None), f"ΠŸΠ°ΠΌΡ‹Π»ΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡ–: {e}"
215
 
216
 
217
  def ui_play_audio(selected_idx: Optional[int], df: pd.DataFrame):
218
  if selected_idx is None or df is None or df.empty:
219
- return "<em>Π‘ΠΏΠ°Ρ‡Π°Ρ‚ΠΊΡƒ Π°Ρ‚Ρ€Ρ‹ΠΌΠ°Ρ†ΡŒ спіс Ρ– Π²Ρ‹Π±Ρ€Π°Ρ†ΡŒ Ρ€Π°Π΄ΠΎΠΊ.</em>", None, None, ""
220
  try:
221
  row = df.iloc[int(selected_idx)]
222
  except Exception:
223
- return "<em>НСкарэктны Π²Ρ‹Π±Π°Ρ€ Ρ€Π°Π΄ΠΊΠ°.</em>", None, None, ""
224
  unique_id = str(row.get("UniqueId"))
225
  try:
226
  fpath = f"/tmp/call_{unique_id}.mp3"
@@ -229,16 +217,16 @@ def ui_play_audio(selected_idx: Optional[int], df: pd.DataFrame):
229
  if not os.path.exists(fpath) or os.path.getsize(fpath) == 0:
230
  fpath, url_used = fetch_mp3_by_unique_id(unique_id)
231
  html = f'URL: <a href="{url_used}" target="_blank">{url_used}</a>'
232
- return html, fpath, fpath, "Π“Π°Ρ‚ΠΎΠ²Π° βœ…"
233
  except requests.HTTPError as e:
234
  body = ""
235
  try:
236
  body = e.response.text[:800]
237
  except Exception:
238
  pass
239
- return f"HTTP ΠΏΠ°ΠΌΡ‹Π»ΠΊΠ°: {e}<br><pre>{body}</pre>", None, None, ""
240
  except Exception as e:
241
- return f"НС атрымалася ΠΏΡ€Π°ΠΉΠ³Ρ€Π°Ρ†ΡŒ: {e}", None, None, ""
242
 
243
 
244
  def ui_toggle_custom_prompt(template_key: str):
@@ -248,14 +236,14 @@ def ui_toggle_custom_prompt(template_key: str):
248
  def ui_analyze(selected_idx: Optional[int], df: pd.DataFrame,
249
  template_key: str, custom_prompt: str, lang_code: str, model_pref: str):
250
  if df is None or df.empty or selected_idx is None:
251
- return "Π‘ΠΏΠ°Ρ‡Π°Ρ‚ΠΊΡƒ Π°Ρ‚Ρ€Ρ‹ΠΌΠ°Ρ†ΡŒ спіс, Π²Ρ‹Π±Ρ€Π°Ρ†ΡŒ Π·Π²Π°Π½ΠΎΠΊ Ρ– (ΠΏΡ€Ρ‹ патрэбС) Π½Π°Ρ†Ρ–ΡΠ½ΡƒΡ†ΡŒ β€œπŸŽ§ ΠŸΡ€Π°ΠΉΠ³Ρ€Π°Ρ†ΡŒβ€."
252
  if not _HAS_GENAI:
253
- return "❌ Бібліятэка google-genai Π½Π΅ Π·Π½ΠΎΠΉΠ΄Π·Π΅Π½Π°. Π£ΠΏΡΡžΠ½Ρ–Ρ†Π΅ΡΡ, ΡˆΡ‚ΠΎ яна ў requirements.txt."
254
 
255
  try:
256
  row = df.iloc[int(selected_idx)]
257
  except Exception:
258
- return "НСкарэктны Π²Ρ‹Π±Π°Ρ€ Ρ€Π°Π΄ΠΊΠ°."
259
 
260
  unique_id = str(row.get("UniqueId"))
261
  mp3_path = f"/tmp/call_{unique_id}.mp3"
@@ -265,22 +253,22 @@ def ui_analyze(selected_idx: Optional[int], df: pd.DataFrame,
265
  if not os.path.exists(mp3_path) or os.path.getsize(mp3_path) == 0:
266
  mp3_path, _ = fetch_mp3_by_unique_id(unique_id)
267
  except Exception as e:
268
- return f"НС атрымалася Π°Ρ‚Ρ€Ρ‹ΠΌΠ°Ρ†ΡŒ Π°ΡžΠ΄Ρ‹Ρ‘ для Π°Π½Π°Π»Ρ–Π·Ρƒ: {e}"
269
 
270
  api_key = os.environ.get("GOOGLE_API_KEY", "").strip()
271
  if not api_key:
272
- return "GOOGLE_API_KEY Π½Π΅ Π·Π°Π΄Π°Π΄Π·Π΅Π½Ρ‹ ў Secrets Space. Π”Π°Π΄Π°ΠΉ яго ў Settings β†’ Secrets Ρ– пСразапусці Space."
273
 
274
  try:
275
  client = genai.Client(api_key=api_key)
276
  except Exception as e:
277
- return f"НС атрымалася Ρ–Π½Ρ–Ρ†Ρ‹ΡΠ»Ρ–Π·Π°Π²Π°Ρ†ΡŒ ΠΊΠ»Ρ–Π΅Π½Ρ‚ Gemini: {e}"
278
 
279
  # Upload file
280
  try:
281
  uploaded_file = client.files.upload(file=mp3_path)
282
  except Exception as e:
283
- return f"ΠŸΠ°ΠΌΡ‹Π»ΠΊΠ° Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡ– Ρ„Π°ΠΉΠ»Π° ў Gemini: {e}"
284
 
285
  # Prepare prompt
286
  if template_key == "custom":
@@ -299,8 +287,8 @@ def ui_analyze(selected_idx: Optional[int], df: pd.DataFrame,
299
  resp = client.models.generate_content(model=model_name, contents=[uploaded_file, merged])
300
  text = getattr(resp, "text", None)
301
  if not text:
302
- return "Аналіз Π·Π°Π²Π΅Ρ€ΡˆΠ°Π½Ρ‹ Π±Π΅Π· тэкставага Π°Π΄ΠΊΠ°Π·Ρƒ. ΠŸΡ€Π°Π²Π΅Ρ€Ρ†Π΅ Π½Π°Π»Π°Π΄Ρ‹ мадэлі Ρ– Ρ„Π°Ρ€ΠΌΠ°Ρ‚ Ρ„Π°ΠΉΠ»Π°."
303
- return f"### Π’Ρ‹Π½Ρ–ΠΊ Π°Π½Π°Π»Ρ–Π·Ρƒ\n\n{text}"
304
  except Exception as e:
305
  # Try to attach more error details
306
  msg = str(e)
@@ -309,7 +297,7 @@ def ui_analyze(selected_idx: Optional[int], df: pd.DataFrame,
309
  msg = msg + "\n\n" + str(e.args[0])
310
  except Exception:
311
  pass
312
- return f"ΠŸΠ°ΠΌΡ‹Π»ΠΊΠ° падчас Π²Ρ‹ΠΊΠ»Ρ–ΠΊΡƒ мадэлі: {msg}"
313
  finally:
314
  # Best-effort cleanup of remote file
315
  try:
@@ -322,14 +310,15 @@ def ui_analyze(selected_idx: Optional[int], df: pd.DataFrame,
322
  # ─────────────────────────────────────────────────────────────────────────────
323
  # Build Gradio UI
324
  # ─────────────────────────────────────────────────────────────────────────────
 
325
  def _today_str():
326
  return _dt.date.today().strftime("%Y-%m-%d")
327
 
328
- with gr.Blocks(title="Vochi CRM + Gemini (Gradio)") as demo:
329
  gr.Markdown(
330
  """
331
- # Vochi CRM β†’ MP3 β†’ Gemini Π°Π½οΏ½οΏ½Π»Ρ–Π·
332
- *ΠΡ‚Ρ€Ρ‹ΠΌΠ°Ρ†ΡŒ Π·Π²Π°Π½ΠΊΡ– Π·Π° дзСнь, ΠΏΡ€Π°ΠΉΠ³Ρ€Π°Ρ†ΡŒ/ΡΠΊΠ°Ρ‡Π°Ρ†ΡŒ MP3 Ρ– ΠΏΡ€Π°Π°Π½Π°Π»Ρ–Π·Π°Π²Π°Ρ†ΡŒ Π·Π²Π°Π½ΠΎΠΊ мадэллю GoogleΒ Gemini.*
333
 
334
  """
335
  )
@@ -337,24 +326,24 @@ with gr.Blocks(title="Vochi CRM + Gemini (Gradio)") as demo:
337
  with gr.Tabs():
338
  with gr.Tab("Vochi CRM"):
339
  with gr.Row():
340
- date_inp = gr.Textbox(label="Π”Π°Ρ‚Π°", value=_today_str(), scale=1)
341
- fetch_btn = gr.Button("ΠΡ‚Ρ€Ρ‹ΠΌΠ°Ρ†ΡŒ спіс", variant="primary", scale=0)
342
- calls_df = gr.Dataframe(value=pd.DataFrame(), label="Бпіс званкоў", interactive=False)
343
- row_dd = gr.Dropdown(choices=[], label="Π—Π²Π°Π½ΠΎΠΊ", info="АбярыцС Ρ€Π°Π΄ΠΎΠΊ для прайгравання/Π°Π½Π°Π»Ρ–Π·Ρƒ")
344
  with gr.Row():
345
- play_btn = gr.Button("🎧 ΠŸΡ€Π°ΠΉΠ³Ρ€Π°Ρ†ΡŒ")
346
  url_html = gr.HTML()
347
- audio_out = gr.Audio(label="ΠΡžΠ΄Ρ‹Ρ‘", type="filepath")
348
- file_out = gr.File(label="MP3 для сцягвання")
349
  status_fetch = gr.Markdown()
350
 
351
- with gr.Tab("AI Analysis (Gemini)"):
352
  with gr.Row():
353
- tpl_dd = gr.Dropdown(choices=TPL_OPTIONS, value="simple", label="Π¨Π°Π±Π»ΠΎΠ½")
354
- lang_dd = gr.Dropdown(choices=LANG_OPTIONS, value="default", label="Мова")
355
- model_dd = gr.Dropdown(choices=MODEL_OPTIONS, value="models/gemini-2.5-flash", label="Мадэль")
356
  custom_prompt_tb = gr.Textbox(label="Custom prompt", lines=8, visible=False)
357
- analyze_btn = gr.Button("🧠 Аналіз", variant="primary")
358
  analysis_md = gr.Markdown()
359
 
360
  # Wire events
 
1
+ # HF Spaces / Gradio app: Vochi CRM call logs + AI analysis
2
  # ─────────────────────────────────────────────────────────────────────────────
3
  # How to deploy (short):
4
  # 1) Create a new Space (Python + Gradio).
 
7
  # 4) In the Space β†’ Settings β†’ Repository secrets, add:
8
  # - VOCHI_BASE_URL (e.g. https://crm.vochi.by/api)
9
  # - VOCHI_CLIENT_ID (client id string)
10
+ # - GOOGLE_API_KEY (API key)
11
+
12
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  #
14
+ # UI language: English.
15
 
16
  from __future__ import annotations
17
  import os
 
36
  # Config
37
  # ─────────────────────────────────────────────────────────────────────────────
38
  BASE_URL = os.environ.get("VOCHI_BASE_URL", "https://crm.vochi.by/api")
39
+ CLIENT_ID = os.environ.get("VOCHI_CLIENT_ID")
40
 
41
  # If your API needs auth, fill it here (or via VOCHI_BEARER in Secrets)
42
  _AUTH_TOKEN = os.environ.get("VOCHI_BEARER", "").strip()
 
131
  # ─────────────────────────────────────────────────────────────────────────────
132
  # Utilities
133
  # ─────────────────────────────────────────────────────────────────────────────
134
+
135
  def label_row(row: dict) -> str:
136
  start = row.get("Start", "")
137
  src = row.get("CallerId", "")
 
181
  # ─────────────────────────────────────────────────────────────────────────────
182
  # Gradio handlers
183
  # ─────────────────────────────────────────────────────────────────────────────
184
+
185
  def ui_fetch_calls(date_str: str):
186
  try:
187
  items = fetch_calllogs(date_str.strip())
188
  df = pd.DataFrame(items)
189
  opts = [(label_row(r), i) for i, r in df.iterrows()]
190
+ msg = f"Calls found: {len(df)}"
191
  # Update dropdown choices and default value
192
  dd = gr.update(choices=[(lbl, idx) for lbl, idx in opts], value=(opts[0][1] if opts else None))
193
  return df, dd, msg
 
197
  body = e.response.text[:800]
198
  except Exception:
199
  pass
200
+ return pd.DataFrame(), gr.update(choices=[], value=None), f"HTTP error: {e}\n{body}"
201
  except Exception as e:
202
+ return pd.DataFrame(), gr.update(choices=[], value=None), f"Load error: {e}"
203
 
204
 
205
  def ui_play_audio(selected_idx: Optional[int], df: pd.DataFrame):
206
  if selected_idx is None or df is None or df.empty:
207
+ return "<em>First fetch the list and select a row.</em>", None, None, ""
208
  try:
209
  row = df.iloc[int(selected_idx)]
210
  except Exception:
211
+ return "<em>Invalid row selection.</em>", None, None, ""
212
  unique_id = str(row.get("UniqueId"))
213
  try:
214
  fpath = f"/tmp/call_{unique_id}.mp3"
 
217
  if not os.path.exists(fpath) or os.path.getsize(fpath) == 0:
218
  fpath, url_used = fetch_mp3_by_unique_id(unique_id)
219
  html = f'URL: <a href="{url_used}" target="_blank">{url_used}</a>'
220
+ return html, fpath, fpath, "Ready βœ…"
221
  except requests.HTTPError as e:
222
  body = ""
223
  try:
224
  body = e.response.text[:800]
225
  except Exception:
226
  pass
227
+ return f"HTTP error: {e}<br><pre>{body}</pre>", None, None, ""
228
  except Exception as e:
229
+ return f"Playback failed: {e}", None, None, ""
230
 
231
 
232
  def ui_toggle_custom_prompt(template_key: str):
 
236
  def ui_analyze(selected_idx: Optional[int], df: pd.DataFrame,
237
  template_key: str, custom_prompt: str, lang_code: str, model_pref: str):
238
  if df is None or df.empty or selected_idx is None:
239
+ return "First fetch the list, choose a call, and (optionally) click β€˜πŸŽ§ Play’."
240
  if not _HAS_GENAI:
241
+ return "❌ google-genai library not found. Make sure it's in requirements.txt."
242
 
243
  try:
244
  row = df.iloc[int(selected_idx)]
245
  except Exception:
246
+ return "Invalid row selection."
247
 
248
  unique_id = str(row.get("UniqueId"))
249
  mp3_path = f"/tmp/call_{unique_id}.mp3"
 
253
  if not os.path.exists(mp3_path) or os.path.getsize(mp3_path) == 0:
254
  mp3_path, _ = fetch_mp3_by_unique_id(unique_id)
255
  except Exception as e:
256
+ return f"Failed to obtain audio for analysis: {e}"
257
 
258
  api_key = os.environ.get("GOOGLE_API_KEY", "").strip()
259
  if not api_key:
260
+ return "GOOGLE_API_KEY is not set in Space Secrets. Add it in Settings β†’ Secrets and restart the Space."
261
 
262
  try:
263
  client = genai.Client(api_key=api_key)
264
  except Exception as e:
265
+ return f"Failed to initialize the client: {e}"
266
 
267
  # Upload file
268
  try:
269
  uploaded_file = client.files.upload(file=mp3_path)
270
  except Exception as e:
271
+ return f"File upload error: {e}"
272
 
273
  # Prepare prompt
274
  if template_key == "custom":
 
287
  resp = client.models.generate_content(model=model_name, contents=[uploaded_file, merged])
288
  text = getattr(resp, "text", None)
289
  if not text:
290
+ return "Analysis finished but returned no text. Check model settings and file format."
291
+ return f"### Analysis result\n\n{text}"
292
  except Exception as e:
293
  # Try to attach more error details
294
  msg = str(e)
 
297
  msg = msg + "\n\n" + str(e.args[0])
298
  except Exception:
299
  pass
300
+ return f"Error during model call: {msg}"
301
  finally:
302
  # Best-effort cleanup of remote file
303
  try:
 
310
  # ─────────────────────────────────────────────────────────────────────────────
311
  # Build Gradio UI
312
  # ─────────────────────────────────────────────────────────────────────────────
313
+
314
  def _today_str():
315
  return _dt.date.today().strftime("%Y-%m-%d")
316
 
317
+ with gr.Blocks(title="Vochi CRM Call Logs (Gradio)") as demo:
318
  gr.Markdown(
319
  """
320
+ # Vochi CRM β†’ MP3 β†’ AI analysis
321
+ *Fetch daily calls, play/download MP3, and analyze the call with an AI model.*
322
 
323
  """
324
  )
 
326
  with gr.Tabs():
327
  with gr.Tab("Vochi CRM"):
328
  with gr.Row():
329
+ date_inp = gr.Textbox(label="Date", value=_today_str(), scale=1)
330
+ fetch_btn = gr.Button("Fetch list", variant="primary", scale=0)
331
+ calls_df = gr.Dataframe(value=pd.DataFrame(), label="Call list", interactive=False)
332
+ row_dd = gr.Dropdown(choices=[], label="Call", info="Select a row for playback/analysis")
333
  with gr.Row():
334
+ play_btn = gr.Button("🎧 Play")
335
  url_html = gr.HTML()
336
+ audio_out = gr.Audio(label="Audio", type="filepath")
337
+ file_out = gr.File(label="MP3 download")
338
  status_fetch = gr.Markdown()
339
 
340
+ with gr.Tab("AI Analysis"):
341
  with gr.Row():
342
+ tpl_dd = gr.Dropdown(choices=TPL_OPTIONS, value="simple", label="Template")
343
+ lang_dd = gr.Dropdown(choices=LANG_OPTIONS, value="default", label="Language")
344
+ model_dd = gr.Dropdown(choices=MODEL_OPTIONS, value="models/gemini-2.5-flash", label="Model")
345
  custom_prompt_tb = gr.Textbox(label="Custom prompt", lines=8, visible=False)
346
+ analyze_btn = gr.Button("🧠 Analyze", variant="primary")
347
  analysis_md = gr.Markdown()
348
 
349
  # Wire events