avaliev commited on
Commit
58ca1d0
·
verified ·
1 Parent(s): 9af681f

Upload 9 files

Browse files

Patch to add configurable API keys

ui/__pycache__/handlers.cpython-313.pyc CHANGED
Binary files a/ui/__pycache__/handlers.cpython-313.pyc and b/ui/__pycache__/handlers.cpython-313.pyc differ
 
ui/__pycache__/layout.cpython-313.pyc CHANGED
Binary files a/ui/__pycache__/layout.cpython-313.pyc and b/ui/__pycache__/layout.cpython-313.pyc differ
 
ui/handlers.py CHANGED
@@ -115,6 +115,28 @@ class UIHandlers:
115
  return (f"ℹ️ Using Mock AI for demo (Error: {str(e)}) 🎭",
116
  f"**AI Provider:** `MOCK AI (Error Fallback)`")
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  def process_onboarding(self, project_description: str) -> tuple:
119
  """Process onboarding and generate tasks."""
120
  # Default UI updates for failure cases (no change to timer/monitoring)
 
115
  return (f"ℹ️ Using Mock AI for demo (Error: {str(e)}) 🎭",
116
  f"**AI Provider:** `MOCK AI (Error Fallback)`")
117
 
118
+ def reconfigure_agent(self, provider: str, api_key: str, eleven_key: str) -> tuple:
119
+ """
120
+ Re-configure the agent with user-provided keys (Demo Mode).
121
+ """
122
+ # Update Environment Variables
123
+ if api_key.strip():
124
+ if provider == "openai":
125
+ os.environ["OPENAI_API_KEY"] = api_key
126
+ elif provider == "anthropic":
127
+ os.environ["ANTHROPIC_API_KEY"] = api_key
128
+ elif provider == "gemini":
129
+ os.environ["GEMINI_API_KEY"] = api_key
130
+
131
+ if eleven_key.strip():
132
+ os.environ["ELEVEN_API_KEY"] = eleven_key
133
+ # Re-init voice
134
+ from voice import voice_generator
135
+ voice_generator.initialize()
136
+
137
+ # Re-initialize Agent
138
+ return self.initialize_agent(provider)
139
+
140
  def process_onboarding(self, project_description: str) -> tuple:
141
  """Process onboarding and generate tasks."""
142
  # Default UI updates for failure cases (no change to timer/monitoring)
ui/layout.py CHANGED
@@ -95,6 +95,33 @@ def create_app(ui_handlers, pomodoro_timer: PomodoroTimer, launch_mode: str, ai_
95
  gr.Markdown("""
96
  > ℹ️ **Demo Mode**: Use the text area in Monitor tab to simulate your workspace.
97
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  else:
99
  gr.Markdown("""
100
  > ℹ️ **Local Mode**: Monitor your actual project directory.
 
95
  gr.Markdown("""
96
  > ℹ️ **Demo Mode**: Use the text area in Monitor tab to simulate your workspace.
97
  """)
98
+
99
+ with gr.Accordion("⚙️ Configure Demo (Add your keys)", open=False):
100
+ gr.Markdown("If the default demo keys are expired, enter your own here to enable AI features.")
101
+ with gr.Row():
102
+ config_provider = gr.Dropdown(
103
+ label="LLM Provider",
104
+ choices=["openai", "anthropic", "gemini"],
105
+ value=ai_provider
106
+ )
107
+ config_api_key = gr.Textbox(
108
+ label="LLM API Key",
109
+ placeholder="sk-...",
110
+ type="password"
111
+ )
112
+ config_eleven_key = gr.Textbox(
113
+ label="ElevenLabs Key (Optional)",
114
+ placeholder="sk-...",
115
+ type="password"
116
+ )
117
+ config_save_btn = gr.Button("💾 Save & Re-Initialize", variant="primary")
118
+
119
+ config_save_btn.click(
120
+ fn=ui_handlers.reconfigure_agent,
121
+ inputs=[config_provider, config_api_key, config_eleven_key],
122
+ outputs=[init_status, ai_provider_display],
123
+ api_name=False
124
+ )
125
  else:
126
  gr.Markdown("""
127
  > ℹ️ **Local Mode**: Monitor your actual project directory.
voice.py CHANGED
@@ -14,53 +14,57 @@ class VoiceGenerator:
14
  Handles text-to-speech generation using ElevenLabs API.
15
  Designed for graceful degradation - never crashes if voice unavailable.
16
  """
17
-
18
  def __init__(self):
19
  """Initialize ElevenLabs client if API key available."""
 
 
 
 
20
  self.client = None
21
  self.available = False
22
  self.voice_id = "JBFqnCBsd6RMkjVDRZzb" # George - friendly, clear voice
23
  self.model_id = "eleven_turbo_v2_5" # Fast, low-latency model
24
-
25
  try:
26
  # Check for API key (demo key first, then user key)
27
  api_key = os.getenv("DEMO_ELEVEN_API_KEY") or os.getenv("ELEVEN_API_KEY")
28
-
29
  if not api_key:
30
  print("ℹ️ ElevenLabs: No API key found. Voice feedback disabled (text-only mode).")
31
  return
32
-
33
  # Try to initialize client
34
  from elevenlabs.client import ElevenLabs
35
  self.client = ElevenLabs(api_key=api_key)
36
  self.available = True
37
-
38
  key_type = "demo" if os.getenv("DEMO_ELEVEN_API_KEY") else "user"
39
  print(f"✅ ElevenLabs voice initialized ({key_type} key)")
40
-
41
  except ImportError:
42
  print("⚠️ ElevenLabs: Package not installed. Run: pip install elevenlabs")
43
  except Exception as e:
44
  print(f"⚠️ ElevenLabs: Initialization failed: {e}")
45
-
46
  def text_to_speech(self, text: str, emotion: str = "neutral") -> Optional[str]:
47
  """
48
  Convert text to speech and return path to temporary audio file.
49
-
50
  Args:
51
  text: Text to convert to speech
52
  emotion: Emotion hint (not used in current implementation)
53
-
54
  Returns:
55
  Path to temporary MP3 file, or None if voice unavailable
56
  """
57
  # Check if voice is enabled globally
58
  if os.getenv("VOICE_ENABLED", "true").lower() == "false":
59
  return None
60
-
61
  if not self.available or not self.client:
62
  return None
63
-
64
  try:
65
  # Generate audio using ElevenLabs API
66
  audio = self.client.text_to_speech.convert(
@@ -69,10 +73,10 @@ class VoiceGenerator:
69
  model_id=self.model_id,
70
  output_format="mp3_44100_128"
71
  )
72
-
73
  # Convert generator/stream to bytes
74
  audio_bytes = b"".join(audio)
75
-
76
  # Save to temporary file (Gradio expects file path, not data URL)
77
  temp_file = tempfile.NamedTemporaryFile(
78
  delete=False,
@@ -81,63 +85,63 @@ class VoiceGenerator:
81
  )
82
  temp_file.write(audio_bytes)
83
  temp_file.close()
84
-
85
  return temp_file.name
86
-
87
  except Exception as e:
88
  # Graceful degradation - log error but don't crash
89
  print(f"⚠️ ElevenLabs: TTS failed: {e}")
90
  return None
91
-
92
  def get_focus_message_audio(self, verdict: str, message: str) -> Optional[str]:
93
  """
94
  Generate voice feedback for focus check results.
95
-
96
  Args:
97
  verdict: "On Track", "Distracted", or "Idle"
98
  message: Text message to speak
99
-
100
  Returns:
101
  Path to temporary audio file or None
102
  """
103
  if not self.available:
104
  return None
105
-
106
  # Add emotion/tone based on verdict (for future voice modulation)
107
  emotion_map = {
108
  "On Track": "cheerful",
109
  "Distracted": "concerned",
110
  "Idle": "motivating"
111
  }
112
-
113
  emotion = emotion_map.get(verdict, "neutral")
114
  return self.text_to_speech(message, emotion=emotion)
115
-
116
  def get_pomodoro_audio(self, event_type: str) -> Optional[str]:
117
  """
118
  Generate voice alerts for Pomodoro timer events.
119
-
120
  Args:
121
  event_type: "work_complete" or "break_complete"
122
-
123
  Returns:
124
  Path to temporary audio file or None
125
  """
126
  if not self.available:
127
  return None
128
-
129
  messages = {
130
  "work_complete": "Great work! Time for a 5-minute break. You've earned it!",
131
  "break_complete": "Break's over! Let's get back to work and stay focused!"
132
  }
133
-
134
  message = messages.get(event_type, "Timer complete!")
135
  return self.text_to_speech(message, emotion="cheerful")
136
-
137
  def test_voice(self) -> Dict[str, any]:
138
  """
139
  Test voice generation (for setup/debugging).
140
-
141
  Returns:
142
  Dict with status, message, and optional audio data
143
  """
@@ -147,11 +151,11 @@ class VoiceGenerator:
147
  "message": "Voice not available (no API key or initialization failed)",
148
  "audio": None
149
  }
150
-
151
  try:
152
  test_message = "Hello! FocusFlow voice is working perfectly!"
153
  audio = self.text_to_speech(test_message)
154
-
155
  if audio:
156
  return {
157
  "status": "success",
@@ -179,7 +183,7 @@ voice_generator = VoiceGenerator()
179
  def get_voice_status() -> str:
180
  """
181
  Get human-readable voice status for UI display.
182
-
183
  Returns:
184
  Status string like "✅ ElevenLabs Voice Enabled" or "ℹ️ Voice Disabled"
185
  """
 
14
  Handles text-to-speech generation using ElevenLabs API.
15
  Designed for graceful degradation - never crashes if voice unavailable.
16
  """
17
+
18
  def __init__(self):
19
  """Initialize ElevenLabs client if API key available."""
20
+ self.initialize()
21
+
22
+ def initialize(self):
23
+ """Initialize or re-initialize the client."""
24
  self.client = None
25
  self.available = False
26
  self.voice_id = "JBFqnCBsd6RMkjVDRZzb" # George - friendly, clear voice
27
  self.model_id = "eleven_turbo_v2_5" # Fast, low-latency model
28
+
29
  try:
30
  # Check for API key (demo key first, then user key)
31
  api_key = os.getenv("DEMO_ELEVEN_API_KEY") or os.getenv("ELEVEN_API_KEY")
32
+
33
  if not api_key:
34
  print("ℹ️ ElevenLabs: No API key found. Voice feedback disabled (text-only mode).")
35
  return
36
+
37
  # Try to initialize client
38
  from elevenlabs.client import ElevenLabs
39
  self.client = ElevenLabs(api_key=api_key)
40
  self.available = True
41
+
42
  key_type = "demo" if os.getenv("DEMO_ELEVEN_API_KEY") else "user"
43
  print(f"✅ ElevenLabs voice initialized ({key_type} key)")
44
+
45
  except ImportError:
46
  print("⚠️ ElevenLabs: Package not installed. Run: pip install elevenlabs")
47
  except Exception as e:
48
  print(f"⚠️ ElevenLabs: Initialization failed: {e}")
49
+
50
  def text_to_speech(self, text: str, emotion: str = "neutral") -> Optional[str]:
51
  """
52
  Convert text to speech and return path to temporary audio file.
53
+
54
  Args:
55
  text: Text to convert to speech
56
  emotion: Emotion hint (not used in current implementation)
57
+
58
  Returns:
59
  Path to temporary MP3 file, or None if voice unavailable
60
  """
61
  # Check if voice is enabled globally
62
  if os.getenv("VOICE_ENABLED", "true").lower() == "false":
63
  return None
64
+
65
  if not self.available or not self.client:
66
  return None
67
+
68
  try:
69
  # Generate audio using ElevenLabs API
70
  audio = self.client.text_to_speech.convert(
 
73
  model_id=self.model_id,
74
  output_format="mp3_44100_128"
75
  )
76
+
77
  # Convert generator/stream to bytes
78
  audio_bytes = b"".join(audio)
79
+
80
  # Save to temporary file (Gradio expects file path, not data URL)
81
  temp_file = tempfile.NamedTemporaryFile(
82
  delete=False,
 
85
  )
86
  temp_file.write(audio_bytes)
87
  temp_file.close()
88
+
89
  return temp_file.name
90
+
91
  except Exception as e:
92
  # Graceful degradation - log error but don't crash
93
  print(f"⚠️ ElevenLabs: TTS failed: {e}")
94
  return None
95
+
96
  def get_focus_message_audio(self, verdict: str, message: str) -> Optional[str]:
97
  """
98
  Generate voice feedback for focus check results.
99
+
100
  Args:
101
  verdict: "On Track", "Distracted", or "Idle"
102
  message: Text message to speak
103
+
104
  Returns:
105
  Path to temporary audio file or None
106
  """
107
  if not self.available:
108
  return None
109
+
110
  # Add emotion/tone based on verdict (for future voice modulation)
111
  emotion_map = {
112
  "On Track": "cheerful",
113
  "Distracted": "concerned",
114
  "Idle": "motivating"
115
  }
116
+
117
  emotion = emotion_map.get(verdict, "neutral")
118
  return self.text_to_speech(message, emotion=emotion)
119
+
120
  def get_pomodoro_audio(self, event_type: str) -> Optional[str]:
121
  """
122
  Generate voice alerts for Pomodoro timer events.
123
+
124
  Args:
125
  event_type: "work_complete" or "break_complete"
126
+
127
  Returns:
128
  Path to temporary audio file or None
129
  """
130
  if not self.available:
131
  return None
132
+
133
  messages = {
134
  "work_complete": "Great work! Time for a 5-minute break. You've earned it!",
135
  "break_complete": "Break's over! Let's get back to work and stay focused!"
136
  }
137
+
138
  message = messages.get(event_type, "Timer complete!")
139
  return self.text_to_speech(message, emotion="cheerful")
140
+
141
  def test_voice(self) -> Dict[str, any]:
142
  """
143
  Test voice generation (for setup/debugging).
144
+
145
  Returns:
146
  Dict with status, message, and optional audio data
147
  """
 
151
  "message": "Voice not available (no API key or initialization failed)",
152
  "audio": None
153
  }
154
+
155
  try:
156
  test_message = "Hello! FocusFlow voice is working perfectly!"
157
  audio = self.text_to_speech(test_message)
158
+
159
  if audio:
160
  return {
161
  "status": "success",
 
183
  def get_voice_status() -> str:
184
  """
185
  Get human-readable voice status for UI display.
186
+
187
  Returns:
188
  Status string like "✅ ElevenLabs Voice Enabled" or "ℹ️ Voice Disabled"
189
  """