apirrone commited on
Commit
66cd043
·
1 Parent(s): 8a09e0f

persist api key entered in gradio interface

Browse files
src/reachy_mini_conversation_app/openai_realtime.py CHANGED
@@ -4,6 +4,7 @@ import random
4
  import asyncio
5
  import logging
6
  from typing import Any, Final, Tuple, Literal
 
7
  from datetime import datetime
8
 
9
  import cv2
@@ -58,6 +59,9 @@ class OpenaiRealtimeHandler(AsyncStreamHandler):
58
  self.start_time = asyncio.get_event_loop().time()
59
  self.is_idle_tool_call = False
60
  self.gradio_mode = gradio_mode
 
 
 
61
 
62
  # Debouncing for partial transcripts
63
  self.partial_transcript_task: asyncio.Task[None] | None = None
@@ -90,6 +94,8 @@ class OpenaiRealtimeHandler(AsyncStreamHandler):
90
  textbox_api_key = args[3] if len(args[3]) > 0 else None
91
  if textbox_api_key is not None:
92
  openai_api_key = textbox_api_key
 
 
93
  else:
94
  openai_api_key = config.OPENAI_API_KEY
95
  else:
@@ -157,6 +163,9 @@ class OpenaiRealtimeHandler(AsyncStreamHandler):
157
  "tool_choice": "auto",
158
  },
159
  )
 
 
 
160
  except Exception:
161
  logger.exception("Realtime session.update failed; aborting startup")
162
  return
@@ -456,3 +465,73 @@ class OpenaiRealtimeHandler(AsyncStreamHandler):
456
  "tool_choice": "required",
457
  },
458
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import asyncio
5
  import logging
6
  from typing import Any, Final, Tuple, Literal
7
+ from pathlib import Path
8
  from datetime import datetime
9
 
10
  import cv2
 
59
  self.start_time = asyncio.get_event_loop().time()
60
  self.is_idle_tool_call = False
61
  self.gradio_mode = gradio_mode
62
+ # Track how the API key was provided (env vs textbox) and its value
63
+ self._key_source: Literal["env", "textbox"] = "env"
64
+ self._provided_api_key: str | None = None
65
 
66
  # Debouncing for partial transcripts
67
  self.partial_transcript_task: asyncio.Task[None] | None = None
 
94
  textbox_api_key = args[3] if len(args[3]) > 0 else None
95
  if textbox_api_key is not None:
96
  openai_api_key = textbox_api_key
97
+ self._key_source = "textbox"
98
+ self._provided_api_key = textbox_api_key
99
  else:
100
  openai_api_key = config.OPENAI_API_KEY
101
  else:
 
163
  "tool_choice": "auto",
164
  },
165
  )
166
+ # If we reached here, the session update succeeded which implies the API key worked.
167
+ # Persist the key to a newly created .env (copied from .env.example) if needed.
168
+ self._persist_api_key_if_needed()
169
  except Exception:
170
  logger.exception("Realtime session.update failed; aborting startup")
171
  return
 
465
  "tool_choice": "required",
466
  },
467
  )
468
+
469
+ def _persist_api_key_if_needed(self) -> None:
470
+ """Create a .env file and store the API key if provided via Gradio and valid.
471
+
472
+ - Only acts when running with Gradio and the key came from the textbox.
473
+ - Creates `.env` only if it does not already exist.
474
+ - If `.env.example` exists in any parent directory of CWD, uses that directory as target and
475
+ copies its contents, overriding the OPENAI_API_KEY line. Otherwise, creates a minimal .env.
476
+ """
477
+ try:
478
+ if not self.gradio_mode:
479
+ return
480
+ if self._key_source != "textbox":
481
+ return
482
+ key = (self._provided_api_key or "").strip()
483
+ if not key:
484
+ return
485
+
486
+ # Update the current process environment for downstream consumers
487
+ try:
488
+ import os
489
+
490
+ os.environ["OPENAI_API_KEY"] = key
491
+ except Exception: # best-effort
492
+ pass
493
+
494
+ cwd = Path.cwd()
495
+ # Search upwards for a .env.example to decide where to place .env
496
+ target_dir = cwd
497
+ example_dir: Path | None = None
498
+ for parent in [cwd, *cwd.parents]:
499
+ candidate = parent / ".env.example"
500
+ if candidate.exists():
501
+ example_dir = parent
502
+ break
503
+ if example_dir is not None:
504
+ target_dir = example_dir
505
+
506
+ env_path = target_dir / ".env"
507
+ if env_path.exists():
508
+ # Respect existing user configuration
509
+ logger.info(".env already exists at %s; not overwriting.", env_path)
510
+ return
511
+
512
+ example_path = target_dir / ".env.example"
513
+ content_lines: list[str] = []
514
+ if example_path.exists():
515
+ try:
516
+ content = example_path.read_text(encoding="utf-8")
517
+ content_lines = content.splitlines()
518
+ except Exception as e:
519
+ logger.warning("Failed to read .env.example at %s: %s", example_path, e)
520
+
521
+ # Replace or append the OPENAI_API_KEY line
522
+ replaced = False
523
+ for i, line in enumerate(content_lines):
524
+ if line.strip().startswith("OPENAI_API_KEY="):
525
+ content_lines[i] = f"OPENAI_API_KEY={key}"
526
+ replaced = True
527
+ break
528
+ if not replaced:
529
+ content_lines.append(f"OPENAI_API_KEY={key}")
530
+
531
+ # Ensure file ends with newline
532
+ final_text = "\n".join(content_lines) + "\n"
533
+ env_path.write_text(final_text, encoding="utf-8")
534
+ logger.info("Created %s and stored OPENAI_API_KEY for future runs.", env_path)
535
+ except Exception as e:
536
+ # Never crash the app for QoL persistence; just log.
537
+ logger.warning("Could not persist OPENAI_API_KEY to .env: %s", e)