LeroyDyer commited on
Commit
6e507b9
Β·
verified Β·
1 Parent(s): be6ceb5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +306 -614
app.py CHANGED
@@ -1,5 +1,4 @@
1
  # File: enhanced_gradio_interface.py
2
-
3
  import asyncio
4
  from collections import defaultdict
5
  import json
@@ -7,46 +6,59 @@ import os
7
  import re
8
  import time
9
  import uuid
10
- from typing import List, Dict, Any, Optional
11
  from dataclasses import dataclass
12
- from threading import Lock
13
- import threading
14
- import json
15
- import os
16
  import queue
17
  import traceback
18
- import uuid
19
- from typing import Coroutine, Dict, List, Any, Optional, Callable
20
- from dataclasses import dataclass
21
  from queue import Queue, Empty
22
- from threading import Lock, Event, Thread
23
- import threading
24
  from concurrent.futures import ThreadPoolExecutor
25
- import time
26
-
27
  import gradio as gr
28
  from openai import AsyncOpenAI, OpenAI
29
  import pyttsx3
30
  from rich.console import Console
31
- BASE_URL="http://localhost:1234/v1"
32
- BASE_API_KEY="not-needed"
33
- BASE_CLIENT = AsyncOpenAI(
34
- base_url=BASE_URL,
35
- api_key=BASE_API_KEY
36
- ) # Global state for client
37
- BASEMODEL_ID = "leroydyer/qwen/qwen3-0.6b-q4_k_m.gguf" # Global state for selected model ID
38
- CLIENT =OpenAI(
39
- base_url=BASE_URL,
40
- api_key=BASE_API_KEY
41
- ) # Global state for client
42
-
43
- # --- Global Variables (if needed) ---
44
  console = Console()
45
- # Example global client if needed elsewhere, adjust based on your setup
46
- # BASE_CLIENT = AsyncOpenAI(base_url=DEFAULT_BASE_URL, api_key=DEFAULT_API_KEY)
47
- # CLIENT = OpenAI(base_url=DEFAULT_BASE_URL, api_key=DEFAULT_API_KEY)
48
 
49
- # --- Dataclasses (copied from your original code or imported) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  @dataclass
51
  class LLMMessage:
52
  role: str
@@ -81,11 +93,11 @@ class LLMResponse:
81
  success: bool = True
82
  error: str = None
83
 
84
- # --- Event Manager (copied from your original code or imported) ---
85
  class EventManager:
86
  def __init__(self):
87
  self._handlers = defaultdict(list)
88
- self._lock = threading.Lock()
89
 
90
  def register(self, event: str, handler: Callable):
91
  with self._lock:
@@ -117,9 +129,9 @@ def UnregisterEvent(event: str, handler: Callable):
117
  EVENT_MANAGER.unregister(event, handler)
118
 
119
  class LLMAgent:
120
- """Main Agent Driver !
121
- Agent For Multiple messages at once ,
122
- has a message queing service as well as agenerator method for easy intergration with console
123
  applications as well as ui !"""
124
  def __init__(
125
  self,
@@ -130,9 +142,9 @@ class LLMAgent:
130
  timeout: int = 30000,
131
  max_tokens: int = 5000,
132
  temperature: float = 0.3,
133
- base_url: str = "http://localhost:1234/v1",
134
- api_key: str = "not-needed",
135
- generate_fn: Callable[[List[Dict[str, str]]], Coroutine[Any, Any, str]] = None
136
  ):
137
  self.model_id = model_id
138
  self.system_prompt = system_prompt or "You are a helpful AI assistant."
@@ -142,23 +154,29 @@ class LLMAgent:
142
  self.is_running = False
143
  self._stop_event = Event()
144
  self.processing_thread = None
145
-
146
  # Conversation tracking
147
  self.conversations: Dict[str, List[LLMMessage]] = {}
148
  self.max_history_length = 20
149
- self._generate = generate_fn or self._default_generate
 
150
  self.api_key = api_key
151
- self.base_url = base_url
152
  self.max_tokens = max_tokens
153
  self.temperature = temperature
154
- self.async_client = self.CreateClient(base_url, api_key)
155
-
 
156
  # Active requests waiting for responses
157
  self.pending_requests: Dict[str, LLMRequest] = {}
158
  self.pending_requests_lock = Lock()
159
-
 
 
 
160
  # Register internal event handlers
161
  self._register_event_handlers()
 
162
  # Speech synthesis
163
  try:
164
  self.tts_engine = pyttsx3.init()
@@ -167,11 +185,11 @@ class LLMAgent:
167
  except Exception as e:
168
  console.log(f"[yellow]TTS not available: {e}[/yellow]")
169
  self.speech_enabled = False
170
-
171
  console.log("[bold green]πŸš€ Enhanced LLM Agent Initialized[/bold green]")
172
-
173
  # Start the processing thread immediately
174
  self.start()
 
175
  def setup_tts(self):
176
  """Configure text-to-speech engine"""
177
  if hasattr(self, 'tts_engine'):
@@ -185,7 +203,6 @@ class LLMAgent:
185
  """Convert text to speech in a non-blocking way"""
186
  if not hasattr(self, 'speech_enabled') or not self.speech_enabled:
187
  return
188
-
189
  def _speak():
190
  try:
191
  # Clean text for speech (remove markdown, code blocks)
@@ -193,29 +210,27 @@ class LLMAgent:
193
  clean_text = re.sub(r'`.*?`', '', clean_text)
194
  clean_text = clean_text.strip()
195
  if clean_text:
196
- self.tts_engine.say(clean_text)
197
  self.tts_engine.runAndWait()
198
  else:
199
- self.tts_engine.say(text)
200
- self.tts_engine.runAndWait()
201
  except Exception as e:
202
  console.log(f"[red]TTS Error: {e}[/red]")
203
-
204
- thread = threading.Thread(target=_speak, daemon=True)
205
  thread.start()
206
-
207
- async def _default_generate(self, messages: List[Dict[str, str]]) -> str:
208
- """Default generate function if none provided"""
209
- return await self.openai_generate(messages)
210
-
211
  def _register_event_handlers(self):
212
  """Register internal event handlers for response routing"""
213
  RegisterEvent("llm_internal_response", self._handle_internal_response)
214
-
215
  def _handle_internal_response(self, response: LLMResponse):
216
  """Route responses to the appropriate request handlers"""
217
  console.log(f"[bold cyan]Handling internal response for: {response.request_id}[/bold cyan]")
218
-
219
  request = None
220
  with self.pending_requests_lock:
221
  if response.request_id in self.pending_requests:
@@ -225,12 +240,12 @@ class LLMAgent:
225
  else:
226
  console.log(f"No pending request found for: {response.request_id}", style="yellow")
227
  return
228
-
229
  # Raise the specific response event
230
  if request.response_event:
231
  console.log(f"[bold green]Raising event: {request.response_event}[/bold green]")
232
  RaiseEvent(request.response_event, response)
233
-
234
  # Call callback if provided
235
  if request.callback:
236
  try:
@@ -238,36 +253,30 @@ class LLMAgent:
238
  request.callback(response)
239
  except Exception as e:
240
  console.log(f"Error in callback: {e}", style="bold red")
241
-
242
  def _add_to_conversation_history(self, conversation_id: str, message: LLMMessage):
243
  """Add message to conversation history"""
244
  if conversation_id not in self.conversations:
245
  self.conversations[conversation_id] = []
246
-
247
  self.conversations[conversation_id].append(message)
248
-
249
  # Trim history if too long
250
  if len(self.conversations[conversation_id]) > self.max_history_length * 2:
251
  self.conversations[conversation_id] = self.conversations[conversation_id][-(self.max_history_length * 2):]
252
-
253
  def _build_messages_from_conversation(self, conversation_id: str, new_message: LLMMessage) -> List[Dict[str, str]]:
254
  """Build message list from conversation history"""
255
  messages = []
256
-
257
  # Add system prompt
258
  if self.system_prompt:
259
  messages.append({"role": "system", "content": self.system_prompt})
260
-
261
  # Add conversation history
262
  if conversation_id in self.conversations:
263
  for msg in self.conversations[conversation_id][-self.max_history_length:]:
264
  messages.append({"role": msg.role, "content": msg.content})
265
-
266
  # Add the new message
267
  messages.append({"role": new_message.role, "content": new_message.content})
268
-
269
  return messages
270
-
271
  def _process_llm_request(self, request: LLMRequest):
272
  """Process a single LLM request"""
273
  console.log(f"[bold green]Processing LLM request: {request.message.message_id}[/bold green]")
@@ -277,14 +286,11 @@ class LLMAgent:
277
  request.message.conversation_id or "default",
278
  request.message
279
  )
280
-
281
  console.log(f"Calling LLM with {len(messages)} messages")
282
-
283
- # Call LLM - Use sync call for thread compatibility
284
- response_content = self._call_llm_sync(messages)
285
-
286
- console.log(f"[bold green]LLM response received: {response_content}...[/bold green]")
287
-
288
  # Create response message
289
  response_message = LLMMessage(
290
  role="assistant",
@@ -292,7 +298,7 @@ class LLMAgent:
292
  conversation_id=request.message.conversation_id,
293
  metadata={"request_id": request.message.message_id}
294
  )
295
-
296
  # Update conversation history
297
  self._add_to_conversation_history(
298
  request.message.conversation_id or "default",
@@ -302,17 +308,16 @@ class LLMAgent:
302
  request.message.conversation_id or "default",
303
  response_message
304
  )
305
-
306
  # Create and send response
307
  response = LLMResponse(
308
  message=response_message,
309
  request_id=request.message.message_id,
310
  success=True
311
  )
312
-
313
  console.log(f"[bold blue]Sending internal response for: {request.message.message_id}[/bold blue]")
314
  RaiseEvent("llm_internal_response", response)
315
-
316
  except Exception as e:
317
  console.log(f"[bold red]Error processing LLM request: {e}[/bold red]")
318
  traceback.print_exc()
@@ -327,9 +332,8 @@ class LLMAgent:
327
  success=False,
328
  error=str(e)
329
  )
330
-
331
  RaiseEvent("llm_internal_response", error_response)
332
-
333
  def _call_llm_sync(self, messages: List[Dict[str, str]]) -> str:
334
  """Sync call to the LLM with retry logic"""
335
  console.log(f"Making LLM call to {self.model_id}")
@@ -348,8 +352,8 @@ class LLMAgent:
348
  console.log(f"LLM call attempt {attempt + 1} failed: {e}")
349
  if attempt == self.max_retries - 1:
350
  raise e
351
- # Wait before retry
352
-
353
  def _process_queue(self):
354
  """Main queue processing loop"""
355
  console.log("[bold cyan]LLM Agent queue processor started[/bold cyan]")
@@ -366,7 +370,7 @@ class LLMAgent:
366
  console.log(f"Error in queue processing: {e}", style="bold red")
367
  traceback.print_exc()
368
  console.log("[bold cyan]LLM Agent queue processor stopped[/bold cyan]")
369
-
370
  def send_message(
371
  self,
372
  content: str,
@@ -379,7 +383,7 @@ class LLMAgent:
379
  """Send a message to the LLM and get response via events"""
380
  if not self.is_running:
381
  raise RuntimeError("LLM Agent is not running. Call start() first.")
382
-
383
  # Create message
384
  message = LLMMessage(
385
  role=role,
@@ -387,19 +391,19 @@ class LLMAgent:
387
  conversation_id=conversation_id,
388
  metadata=metadata or {}
389
  )
390
-
391
  # Create request
392
  request = LLMRequest(
393
  message=message,
394
  response_event=response_event,
395
  callback=callback
396
  )
397
-
398
  # Store in pending requests BEFORE adding to queue
399
  with self.pending_requests_lock:
400
  self.pending_requests[message.message_id] = request
401
  console.log(f"Added to pending requests: {message.message_id}")
402
-
403
  # Add to queue
404
  try:
405
  self.request_queue.put(request, timeout=5.0)
@@ -411,7 +415,7 @@ class LLMAgent:
411
  if message.message_id in self.pending_requests:
412
  del self.pending_requests[message.message_id]
413
  raise RuntimeError("LLM Agent queue is full")
414
-
415
  async def chat(self, messages: List[Dict[str, str]]) -> str:
416
  """
417
  Async chat method that sends message via queue and returns response string.
@@ -424,11 +428,10 @@ class LLMAgent:
424
  def chat_callback(response: LLMResponse):
425
  """Callback when LLM responds - thread-safe"""
426
  console.log(f"[bold yellow]βœ“ CHAT CALLBACK TRIGGERED![/bold yellow]")
427
-
428
  if not response_future.done():
429
  if response.success:
430
  content = response.message.content
431
- console.log(f"Callback received content: {content}...")
432
  # Schedule setting the future result on the main event loop
433
  loop.call_soon_threadsafe(response_future.set_result, content)
434
  else:
@@ -439,14 +442,12 @@ class LLMAgent:
439
  console.log(f"[bold red]Future already done, ignoring callback[/bold red]")
440
 
441
  console.log(f"Sending message to LLM agent...")
442
-
443
  # Extract the actual message content from the messages list
444
  user_message = ""
445
  for msg in messages:
446
  if msg.get("role") == "user":
447
  user_message = msg.get("content", "")
448
  break
449
-
450
  if not user_message.strip():
451
  return ""
452
 
@@ -457,15 +458,12 @@ class LLMAgent:
457
  conversation_id="default",
458
  callback=chat_callback
459
  )
460
-
461
  console.log(f"Message sent with ID: {message_id}, waiting for response...")
462
-
463
  # Wait for the response and return it
464
  try:
465
  response = await asyncio.wait_for(response_future, timeout=self.timeout)
466
  console.log(f"[bold green]βœ“ Chat complete! Response length: {len(response)}[/bold green]")
467
  return response
468
-
469
  except asyncio.TimeoutError:
470
  console.log("[bold red]Response timeout[/bold red]")
471
  # Clean up the pending request
@@ -473,12 +471,11 @@ class LLMAgent:
473
  if message_id in self.pending_requests:
474
  del self.pending_requests[message_id]
475
  return "❌ Response timeout - check if LLM server is running"
476
-
477
  except Exception as e:
478
  console.log(f"[bold red]Error sending message: {e}[/bold red]")
479
  traceback.print_exc()
480
  return f"❌ Error sending message: {e}"
481
-
482
  def start(self):
483
  """Start the LLM agent"""
484
  if not self.is_running:
@@ -487,7 +484,7 @@ class LLMAgent:
487
  self.processing_thread = Thread(target=self._process_queue, daemon=True)
488
  self.processing_thread.start()
489
  console.log("[bold green]LLM Agent started[/bold green]")
490
-
491
  def stop(self):
492
  """Stop the LLM agent"""
493
  console.log("Stopping LLM Agent...")
@@ -496,22 +493,61 @@ class LLMAgent:
496
  self.processing_thread.join(timeout=10)
497
  self.is_running = False
498
  console.log("LLM Agent stopped")
499
-
500
  def get_conversation_history(self, conversation_id: str = "default") -> List[LLMMessage]:
501
  """Get conversation history"""
502
  return self.conversations.get(conversation_id, [])[:]
503
-
504
  def clear_conversation(self, conversation_id: str = "default"):
505
  """Clear conversation history"""
506
  if conversation_id in self.conversations:
507
  del self.conversations[conversation_id]
508
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
 
510
- async def _chat(self, messages: List[Dict[str, str]]) -> str:
511
- return await self._generate(messages)
512
-
513
  @staticmethod
514
- async def openai_generate(messages: List[Dict[str, str]], max_tokens: int = 8096, temperature: float = 0.4, model: str = BASEMODEL_ID,tools=None) -> str:
515
  """Static method for generating responses using OpenAI API"""
516
  try:
517
  resp = await BASE_CLIENT.chat.completions.create(
@@ -526,56 +562,16 @@ class LLMAgent:
526
  except Exception as e:
527
  console.log(f"[bold red]Error in openai_generate: {e}[/bold red]")
528
  return f"[LLM_Agent Error - openai_generate: {str(e)}]"
529
-
530
- async def _call_(self, messages: List[Dict[str, str]]) -> str:
531
- """Internal call method using instance client"""
532
- try:
533
- resp = await self.async_client.chat.completions.create(
534
- model=self.model_id,
535
- messages=messages,
536
- temperature=self.temperature,
537
- max_tokens=self.max_tokens
538
- )
539
- response_text = resp.choices[0].message.content or ""
540
- return response_text
541
- except Exception as e:
542
- console.log(f"[bold red]Error in _call_: {e}[/bold red]")
543
- return f"[LLM_Agent Error - _call_: {str(e)}]"
544
-
545
- @staticmethod
546
- def CreateClient(base_url: str, api_key: str) -> AsyncOpenAI:
547
- '''Create async OpenAI Client required for multi tasking'''
548
- return AsyncOpenAI(
549
- base_url=base_url,
550
- api_key=api_key
551
- )
552
-
553
- @staticmethod
554
- async def fetch_available_models(base_url: str, api_key: str) -> List[str]:
555
- """Fetches available models from the OpenAI API."""
556
- try:
557
- async_client = AsyncOpenAI(base_url=base_url, api_key=api_key)
558
- models = await async_client.models.list()
559
- model_choices = [model.id for model in models.data]
560
- return model_choices
561
- except Exception as e:
562
- console.log(f"[bold red]LLM_Agent Error fetching models: {e}[/bold red]")
563
- return ["LLM_Agent Error fetching models"]
564
-
565
- def get_models(self) -> List[str]:
566
- """Get available models using instance credentials"""
567
- return asyncio.run(self.fetch_available_models(self.base_url, self.api_key))
568
-
569
 
570
  def get_queue_size(self) -> int:
571
  """Get current queue size"""
572
  return self.request_queue.qsize()
573
-
574
  def get_pending_requests_count(self) -> int:
575
  """Get number of pending requests"""
576
  with self.pending_requests_lock:
577
  return len(self.pending_requests)
578
-
579
  def get_status(self) -> Dict[str, Any]:
580
  """Get agent status information"""
581
  return {
@@ -585,22 +581,21 @@ class LLMAgent:
585
  "conversations_count": len(self.conversations),
586
  "model": self.model_id
587
  }
 
588
  class AI_Agent:
589
  def __init__(self, model_id: str, system_prompt: str = "You are a helpful assistant. Respond concisely in 1-2 sentences.", history: List[Dict] = None):
590
  self.model_id = model_id
591
  self.system_prompt = system_prompt
592
  self.history = history or []
593
  self.conversation_id = f"conv_{uuid.uuid4().hex[:8]}"
594
-
595
- # Create agent instance
596
  self.client = LLMAgent(
597
  model_id=model_id,
598
  system_prompt=self.system_prompt,
599
- generate_fn=LLMAgent.openai_generate
600
  )
601
-
602
  console.log(f"[bold green]βœ“ MyAgent initialized with model: {model_id}[/bold green]")
603
-
604
  async def call_llm(self, messages: List[Dict], use_history: bool = True) -> str:
605
  """
606
  Send messages to LLM and get response
@@ -612,67 +607,55 @@ class AI_Agent:
612
  """
613
  try:
614
  console.log(f"[bold yellow]Sending {len(messages)} messages to LLM (use_history: {use_history})...[/bold yellow]")
615
-
616
  # Enhance messages based on history setting
617
  enhanced_messages = await self._enhance_messages(messages, use_history)
618
-
619
  response = await self.client.chat(enhanced_messages)
620
  console.log(f"[bold green]βœ“ Response received ({len(response)} chars)[/bold green]")
621
-
622
  # Update conversation history ONLY if we're using history
623
  if use_history:
624
  self._update_history(messages, response)
625
-
626
  return response
627
-
628
  except Exception as e:
629
  console.log(f"[bold red]βœ— ERROR: {e}[/bold red]")
630
  traceback.print_exc()
631
  return f"Error: {str(e)}"
632
-
633
  async def _enhance_messages(self, messages: List[Dict], use_history: bool) -> List[Dict]:
634
  """Enhance messages with system prompt and optional history"""
635
  enhanced = []
636
-
637
  # Add system prompt if not already in messages
638
  has_system = any(msg.get('role') == 'system' for msg in messages)
639
  if not has_system and self.system_prompt:
640
  enhanced.append({"role": "system", "content": self.system_prompt})
641
-
642
  # Add conversation history only if requested
643
  if use_history and self.history:
644
  enhanced.extend(self.history[-10:]) # Last 10 messages for context
645
-
646
  # Add current messages
647
  enhanced.extend(messages)
648
-
649
  return enhanced
650
-
651
  def _update_history(self, messages: List[Dict], response: str):
652
  """Update conversation history with new exchange"""
653
  # Add user messages to history
654
  for msg in messages:
655
  if msg.get('role') in ['user', 'assistant']:
656
  self.history.append(msg)
657
-
658
  # Add assistant response to history
659
  self.history.append({"role": "assistant", "content": response})
660
-
661
  # Keep history manageable (last 20 exchanges)
662
  if len(self.history) > 40: # 20 user + 20 assistant messages
663
  self.history = self.history[-40:]
664
-
665
  async def simple_query(self, query: str) -> str:
666
  """Simple one-shot query method - NO history/context"""
667
  messages = [{"role": "user", "content": query}]
668
  return await self.call_llm(messages, use_history=False)
669
-
670
  async def multi_turn_chat(self, user_input: str) -> str:
671
  """Multi-turn chat that maintains context across calls"""
672
  messages = [{"role": "user", "content": user_input}]
673
  response = await self.call_llm(messages, use_history=True)
674
  return response
675
-
676
 
677
  def get_conversation_summary(self) -> Dict:
678
  """Get conversation summary"""
@@ -683,27 +666,27 @@ class AI_Agent:
683
  "assistant_messages": len([msg for msg in self.history if msg.get('role') == 'assistant']),
684
  "recent_exchanges": self.history[-4:] if self.history else []
685
  }
686
-
687
  def clear_history(self):
688
  """Clear conversation history"""
689
  self.history.clear()
690
  console.log("[bold yellow]Conversation history cleared[/bold yellow]")
691
-
692
  def update_system_prompt(self, new_prompt: str):
693
  """Update the system prompt"""
694
  self.system_prompt = new_prompt
695
  console.log(f"[bold blue]System prompt updated[/bold blue]")
696
-
697
  def stop(self):
698
  """Stop the client gracefully"""
699
  if hasattr(self, 'client') and self.client:
700
  self.client.stop()
701
- console.log("[bold yellow]MyAgent client stopped[/bold yellow]")
702
- async def contextual_query(self, query: str, context_messages: List[Dict] = None,
 
703
  context_text: str = None, context_files: List[str] = None) -> str:
704
  """
705
  Query with specific context but doesn't update main history
706
-
707
  Args:
708
  query: The user question
709
  context_messages: List of message dicts for context
@@ -711,28 +694,22 @@ class AI_Agent:
711
  context_files: List of file paths to read and include as context
712
  """
713
  messages = []
714
-
715
  # Add system prompt
716
  if self.system_prompt:
717
  messages.append({"role": "system", "content": self.system_prompt})
718
-
719
  # Handle different context types
720
  if context_messages:
721
  messages.extend(context_messages)
722
-
723
  if context_text:
724
  messages.append({"role": "system", "content": f"Additional context: {context_text}"})
725
-
726
  if context_files:
727
  file_context = await self._read_files_context(context_files)
728
  if file_context:
729
  messages.append({"role": "system", "content": f"File contents:\n{file_context}"})
730
-
731
  # Add the actual query
732
  messages.append({"role": "user", "content": query})
733
-
734
  return await self.call_llm(messages, use_history=False)
735
-
736
  async def _read_files_context(self, file_paths: List[str]) -> str:
737
  """Read multiple files and return as context string"""
738
  contexts = []
@@ -746,21 +723,17 @@ class AI_Agent:
746
  console.log(f"[bold yellow]File not found: {file_path}[/bold yellow]")
747
  except Exception as e:
748
  console.log(f"[bold red]Error reading file {file_path}: {e}[/bold red]")
749
-
750
- return "\n\n".join(contexts) if contexts else ""
751
-
752
-
753
  async def query_with_code_context(self, query: str, code_snippets: List[str] = None,
754
  code_files: List[str] = None) -> str:
755
  """
756
  Specialized contextual query for code-related questions
757
  """
758
  code_context = "CODE CONTEXT:\n"
759
-
760
  if code_snippets:
761
  for i, snippet in enumerate(code_snippets, 1):
762
  code_context += f"\nSnippet {i}:\n```\n{snippet}\n```\n"
763
-
764
  if code_files:
765
  # Read code files and include them
766
  for file_path in code_files:
@@ -772,13 +745,11 @@ class AI_Agent:
772
  except Exception as e:
773
  code_context += f"Error reading file: {e}"
774
  code_context += "\n```\n"
775
-
776
  return await self.contextual_query(query, context_text=code_context)
777
-
778
  async def multi_context_query(self, query: str, contexts: Dict[str, Any]) -> str:
779
  """
780
  Advanced contextual query with multiple context types
781
-
782
  Args:
783
  query: The user question
784
  contexts: Dict with various context types
@@ -790,190 +761,32 @@ class AI_Agent:
790
  - 'metadata': Any additional metadata
791
  """
792
  all_context_messages = []
793
-
794
  # Build context from different sources
795
  if contexts.get('text'):
796
  all_context_messages.append({"role": "system", "content": f"Context: {contexts['text']}"})
797
-
798
  if contexts.get('messages'):
799
  all_context_messages.extend(contexts['messages'])
800
-
801
  if contexts.get('files'):
802
  file_context = await self._read_files_context(contexts['files'])
803
  if file_context:
804
  all_context_messages.append({"role": "system", "content": f"File Contents:\n{file_context}"})
805
-
806
  if contexts.get('code'):
807
- code_context = "\n".join([f"Code snippet {i}:\n```\n{code}\n```"
808
  for i, code in enumerate(contexts['code'], 1)])
809
  all_context_messages.append({"role": "system", "content": f"Code Context:\n{code_context}"})
810
-
811
  if contexts.get('metadata'):
812
  all_context_messages.append({"role": "system", "content": f"Metadata: {contexts['metadata']}"})
813
-
814
  return await self.contextual_query(query, context_messages=all_context_messages)
815
-
816
-
817
- console = Console()
818
-
819
- # --- Canvas Artifact Support ---
820
- @dataclass
821
- class CanvasArtifact:
822
- id: str
823
- type: str # 'code', 'diagram', 'text', 'image'
824
- content: str
825
- title: str
826
- timestamp: float
827
- metadata: Dict[str, Any]
828
-
829
- class EnhancedAIAgent:
830
- """
831
- Wrapper around your AI_Agent that adds canvas/artifact management
832
- without modifying the original agent.
833
- """
834
- def __init__(self, ai_agent):
835
- self.agent = ai_agent
836
- self.canvas_artifacts: Dict[str, List[CanvasArtifact]] = {}
837
- self.max_canvas_artifacts = 50
838
- console.log("[bold green]βœ“ Enhanced AI Agent wrapper initialized[/bold green]")
839
-
840
- def add_artifact_to_canvas(self, conversation_id: str, content: str,
841
- artifact_type: str = "code", title: str = None):
842
- """Add artifacts to the collaborative canvas"""
843
- if conversation_id not in self.canvas_artifacts:
844
- self.canvas_artifacts[conversation_id] = []
845
-
846
- artifact = CanvasArtifact(
847
- id=str(uuid.uuid4())[:8],
848
- type=artifact_type,
849
- content=content,
850
- title=title or f"{artifact_type}_{len(self.canvas_artifacts[conversation_id]) + 1}",
851
- timestamp=time.time(),
852
- metadata={"conversation_id": conversation_id}
853
- )
854
-
855
- self.canvas_artifacts[conversation_id].append(artifact)
856
-
857
- # Keep only recent artifacts
858
- if len(self.canvas_artifacts[conversation_id]) > self.max_canvas_artifacts:
859
- self.canvas_artifacts[conversation_id] = self.canvas_artifacts[conversation_id][-self.max_canvas_artifacts:]
860
-
861
- console.log(f"[green]Added artifact to canvas: {artifact.title}[/green]")
862
- return artifact
863
-
864
- def get_canvas_context(self, conversation_id: str) -> str:
865
- """Get formatted canvas context for LLM prompts"""
866
- if conversation_id not in self.canvas_artifacts or not self.canvas_artifacts[conversation_id]:
867
- return ""
868
-
869
- context_lines = ["\n=== COLLABORATIVE CANVAS ARTIFACTS ==="]
870
- for artifact in self.canvas_artifacts[conversation_id][-10:]: # Last 10 artifacts
871
- context_lines.append(f"\n--- {artifact.title} [{artifact.type.upper()}] ---")
872
- preview = artifact.content[:500] + "..." if len(artifact.content) > 500 else artifact.content
873
- context_lines.append(preview)
874
-
875
- return "\n".join(context_lines) + "\n=================================\n"
876
-
877
- async def chat_with_canvas(self, message: str, conversation_id: str = "default",
878
- include_canvas: bool = True) -> str:
879
- """Enhanced chat that includes canvas context"""
880
- # Build context with canvas artifacts if requested
881
- full_message = message
882
- if include_canvas:
883
- canvas_context = self.get_canvas_context(conversation_id)
884
- if canvas_context:
885
- full_message = f"{canvas_context}\n\nUser Query: {message}"
886
-
887
- try:
888
- # Use your original agent's multi_turn_chat method
889
- response = await self.agent.multi_turn_chat(full_message)
890
-
891
- # Auto-extract and add code artifacts to canvas
892
- self._extract_artifacts_to_canvas(response, conversation_id)
893
-
894
- return response
895
-
896
- except Exception as e:
897
- error_msg = f"Error in chat_with_canvas: {str(e)}"
898
- console.log(f"[red]{error_msg}[/red]")
899
- return error_msg
900
-
901
- def _extract_artifacts_to_canvas(self, response: str, conversation_id: str):
902
- """Automatically extract code blocks and add to canvas"""
903
- # Find all code blocks with optional language specification
904
- code_blocks = re.findall(r'```(?:(\w+)\n)?(.*?)```', response, re.DOTALL)
905
- for i, (lang, code_block) in enumerate(code_blocks):
906
- if len(code_block.strip()) > 10: # Only add substantial code blocks
907
- self.add_artifact_to_canvas(
908
- conversation_id,
909
- code_block.strip(),
910
- "code",
911
- f"code_snippet_{lang or 'unknown'}_{len(self.canvas_artifacts.get(conversation_id, [])) + 1}"
912
- )
913
-
914
- def get_canvas_summary(self, conversation_id: str) -> List[Dict]:
915
- """Get summary of canvas artifacts for display"""
916
- if conversation_id not in self.canvas_artifacts:
917
- return []
918
-
919
- artifacts = []
920
- for artifact in reversed(self.canvas_artifacts[conversation_id]): # Newest first
921
- artifacts.append({
922
- "id": artifact.id,
923
- "type": artifact.type.upper(),
924
- "title": artifact.title,
925
- "preview": artifact.content[:100] + "..." if len(artifact.content) > 100 else artifact.content,
926
- "timestamp": time.strftime("%H:%M:%S", time.localtime(artifact.timestamp))
927
- })
928
-
929
- return artifacts
930
-
931
- def get_artifact_by_id(self, conversation_id: str, artifact_id: str) -> Optional[CanvasArtifact]:
932
- """Get specific artifact by ID"""
933
- if conversation_id not in self.canvas_artifacts:
934
- return None
935
-
936
- for artifact in self.canvas_artifacts[conversation_id]:
937
- if artifact.id == artifact_id:
938
- return artifact
939
- return None
940
-
941
- def clear_canvas(self, conversation_id: str = "default"):
942
- """Clear canvas artifacts"""
943
- if conversation_id in self.canvas_artifacts:
944
- self.canvas_artifacts[conversation_id] = []
945
- console.log(f"[yellow]Cleared canvas: {conversation_id}[/yellow]")
946
-
947
- def get_latest_code_artifact(self, conversation_id: str) -> Optional[str]:
948
- """Get the most recent code artifact content"""
949
- if conversation_id not in self.canvas_artifacts:
950
- return None
951
-
952
- for artifact in reversed(self.canvas_artifacts[conversation_id]):
953
- if artifact.type == "code":
954
- return artifact.content
955
- return None
956
 
957
 
 
958
  class LcarsInterface:
959
- """LCARS-styled Gradio interface for your AI_Agent"""
960
-
961
- def __init__(self, ai_agent):
962
- """
963
- Initialize interface with your AI_Agent instance
964
-
965
- Args:
966
- ai_agent: Instance of your AI_Agent class
967
- """
968
- self.enhanced_agent = EnhancedAIAgent(ai_agent)
969
- self.current_conversation = "default"
970
- self.processing_lock = Lock()
971
- console.log("[bold cyan]βœ“ LCARS Interface initialized[/bold cyan]")
972
-
973
  def create_interface(self):
974
  """Create the full LCARS-styled interface"""
975
-
976
- # Enhanced LCARS CSS
977
  lcars_css = """
978
  :root {
979
  --lcars-orange: #FF9900;
@@ -986,18 +799,17 @@ class LcarsInterface:
986
  --lcars-gray: #424242;
987
  --lcars-yellow: #FFFF66;
988
  }
989
-
990
  body {
991
  background: var(--lcars-black);
992
  color: var(--lcars-orange);
993
  font-family: 'Antonio', 'LCD', 'Courier New', monospace;
 
 
994
  }
995
-
996
  .gradio-container {
997
  background: var(--lcars-black) !important;
998
  min-height: 100vh;
999
  }
1000
-
1001
  .lcars-container {
1002
  background: var(--lcars-black);
1003
  border: 4px solid var(--lcars-orange);
@@ -1005,193 +817,147 @@ class LcarsInterface:
1005
  min-height: 100vh;
1006
  padding: 20px;
1007
  }
1008
-
1009
  .lcars-header {
1010
  background: linear-gradient(90deg, var(--lcars-red), var(--lcars-orange));
1011
  padding: 20px 40px;
1012
  border-radius: 0 60px 0 0;
1013
  margin: -20px -20px 20px -20px;
1014
  border-bottom: 6px solid var(--lcars-blue);
1015
- box-shadow: 0 4px 20px rgba(255, 153, 0, 0.3);
1016
  }
1017
-
1018
  .lcars-title {
1019
- font-size: 3em;
1020
  font-weight: bold;
1021
  color: var(--lcars-black);
1022
- text-shadow: 3px 3px 6px rgba(255, 255, 255, 0.4);
1023
  margin: 0;
1024
- letter-spacing: 2px;
1025
  }
1026
-
1027
  .lcars-subtitle {
1028
- font-size: 1.4em;
1029
  color: var(--lcars-black);
1030
  margin: 10px 0 0 0;
1031
- font-weight: bold;
1032
  }
1033
-
1034
  .lcars-panel {
1035
- background: linear-gradient(135deg, rgba(66, 66, 66, 0.9), rgba(40, 40, 40, 0.9));
1036
- border: 3px solid var(--lcars-orange);
1037
- border-radius: 0 25px 0 25px;
1038
- padding: 20px;
1039
- margin-bottom: 20px;
1040
- box-shadow: 0 4px 15px rgba(255, 153, 0, 0.2);
1041
  }
1042
-
1043
  .lcars-button {
1044
- background: linear-gradient(135deg, var(--lcars-orange), var(--lcars-red)) !important;
1045
  color: var(--lcars-black) !important;
1046
  border: none !important;
1047
- border-radius: 0 20px 0 20px !important;
1048
- padding: 12px 24px !important;
1049
  font-family: inherit !important;
1050
  font-weight: bold !important;
1051
- font-size: 1.1em !important;
1052
- cursor: pointer !important;
1053
- transition: all 0.3s ease !important;
1054
- margin: 8px !important;
1055
- box-shadow: 0 4px 8px rgba(255, 153, 0, 0.3) !important;
1056
  }
1057
-
1058
  .lcars-button:hover {
1059
- background: linear-gradient(135deg, var(--lcars-red), var(--lcars-orange)) !important;
1060
- transform: translateY(-2px) !important;
1061
- box-shadow: 0 6px 12px rgba(255, 153, 0, 0.4) !important;
1062
  }
1063
-
1064
  .lcars-input {
1065
  background: var(--lcars-black) !important;
1066
  color: var(--lcars-orange) !important;
1067
  border: 2px solid var(--lcars-blue) !important;
1068
- border-radius: 0 15px 0 15px !important;
1069
- padding: 12px !important;
1070
- font-family: inherit !important;
1071
- font-size: 1.1em !important;
1072
  }
1073
-
1074
  .lcars-chatbot {
1075
  background: var(--lcars-black) !important;
1076
- border: 3px solid var(--lcars-purple) !important;
1077
- border-radius: 0 20px 0 20px !important;
1078
- min-height: 400px;
1079
- max-height: 500px;
1080
- }
1081
-
1082
- .lcars-code-editor {
1083
- background: var(--lcars-black) !important;
1084
- color: var(--lcars-pale-blue) !important;
1085
- border: 3px solid var(--lcars-blue) !important;
1086
- border-radius: 0 20px 0 20px !important;
1087
- font-family: 'Fira Code', 'Courier New', monospace !important;
1088
- font-size: 1em !important;
1089
  }
1090
-
1091
  .status-indicator {
1092
  display: inline-block;
1093
- width: 16px;
1094
- height: 16px;
1095
  border-radius: 50%;
1096
  background: var(--lcars-red);
1097
- margin-right: 12px;
1098
- box-shadow: 0 0 10px currentColor;
1099
  }
1100
-
1101
  .status-online {
1102
  background: var(--lcars-blue);
1103
- animation: pulse 1.5s infinite;
1104
  }
1105
-
1106
  @keyframes pulse {
1107
- 0% { transform: scale(1); opacity: 1; }
1108
- 50% { transform: scale(1.1); opacity: 0.7; }
1109
- 100% { transform: scale(1); opacity: 1; }
1110
- }
1111
-
1112
- .panel-title {
1113
- color: var(--lcars-yellow) !important;
1114
- font-size: 1.4em !important;
1115
- font-weight: bold !important;
1116
- margin-bottom: 15px !important;
1117
- border-bottom: 2px solid var(--lcars-orange);
1118
- padding-bottom: 8px;
1119
  }
1120
  """
1121
 
1122
  with gr.Blocks(css=lcars_css, theme=gr.themes.Default(), title="LCARS Terminal") as interface:
1123
-
1124
  with gr.Column(elem_classes="lcars-container"):
1125
- # Header Section
1126
  with gr.Row(elem_classes="lcars-header"):
1127
  gr.Markdown("""
1128
  <div style="text-align: center; width: 100%;">
1129
- <div class="lcars-title">πŸš€ LCARS AI TERMINAL</div>
1130
- <div class="lcars-subtitle">ADVANCED AI DEVELOPMENT CONSOLE</div>
1131
  <div style="margin-top: 10px;">
1132
  <span class="status-indicator status-online"></span>
1133
  <span style="color: var(--lcars-black); font-weight: bold;">SYSTEM ONLINE</span>
1134
  </div>
1135
  </div>
1136
  """)
1137
-
1138
- # Main Content Area
1139
  with gr.Row():
1140
- # Left Sidebar - Canvas Artifacts
1141
- with gr.Column(scale=1, min_width=400):
 
1142
  with gr.Column(elem_classes="lcars-panel"):
1143
- gr.Markdown("### 🎨 CANVAS ARTIFACTS", elem_classes="panel-title")
1144
- artifact_display = gr.JSON(
1145
- label="",
1146
- elem_id="artifact-display"
1147
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1148
  with gr.Row():
1149
  refresh_artifacts_btn = gr.Button("πŸ”„ Refresh", elem_classes="lcars-button")
1150
  clear_canvas_btn = gr.Button("πŸ—‘οΈ Clear Canvas", elem_classes="lcars-button")
1151
- load_latest_btn = gr.Button("πŸ“₯ Load Latest", elem_classes="lcars-button")
1152
-
1153
- # Main Content - Chat and Code Canvas
1154
  with gr.Column(scale=2):
1155
- # Collaborative Code Canvas
1156
  with gr.Accordion("πŸ’» COLLABORATIVE CODE CANVAS", open=True):
1157
  code_editor = gr.Code(
1158
- value="# Welcome to LCARS Collaborative Canvas\n# Your code artifacts will appear here\n\nprint('Hello, Starfleet!')",
1159
  language="python",
1160
- lines=20,
1161
- label="",
1162
- elem_classes="lcars-code-editor"
1163
  )
1164
-
1165
  with gr.Row():
1166
- discuss_code_btn = gr.Button("πŸ’¬ Discuss This Code", elem_classes="lcars-button")
1167
- analyze_code_btn = gr.Button("πŸ” Analyze", elem_classes="lcars-button")
1168
- optimize_code_btn = gr.Button("⚑ Optimize", elem_classes="lcars-button")
1169
- document_code_btn = gr.Button("πŸ“š Document", elem_classes="lcars-button")
1170
-
1171
  # Chat Interface
1172
  with gr.Column(elem_classes="lcars-panel"):
1173
- gr.Markdown("### πŸ’¬ MISSION LOG", elem_classes="panel-title")
1174
- chatbot = gr.Chatbot(
1175
- label="",
1176
- elem_classes="lcars-chatbot",
1177
- show_label=False,
1178
- height=400
1179
- )
1180
-
1181
  with gr.Row():
1182
  message_input = gr.Textbox(
1183
  placeholder="Enter your command or query...",
1184
  show_label=False,
1185
  lines=2,
1186
- elem_classes="lcars-input",
1187
- scale=4
1188
  )
1189
- send_btn = gr.Button("πŸš€ TRANSMIT", elem_classes="lcars-button", scale=1)
1190
-
1191
- # Status and Controls
1192
  with gr.Row():
1193
  status_display = gr.Textbox(
1194
- value=f"LCARS terminal operational. Model: {self.enhanced_agent.agent.model_id}",
1195
  label="Status",
1196
  max_lines=2,
1197
  elem_classes="lcars-input"
@@ -1199,175 +965,101 @@ class LcarsInterface:
1199
  with gr.Column(scale=0):
1200
  clear_chat_btn = gr.Button("πŸ—‘οΈ Clear Chat", elem_classes="lcars-button")
1201
  new_session_btn = gr.Button("πŸ†• New Session", elem_classes="lcars-button")
1202
-
1203
  # === EVENT HANDLERS ===
1204
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1205
  def get_artifacts():
1206
- """Get current canvas artifacts"""
1207
- return self.enhanced_agent.get_canvas_summary(self.current_conversation)
1208
-
1209
  def clear_canvas():
1210
- """Clear the canvas"""
1211
- self.enhanced_agent.clear_canvas(self.current_conversation)
1212
  return [], "βœ… Canvas cleared"
1213
-
1214
- def load_latest_artifact_to_canvas():
1215
- """Load the most recent code artifact to the canvas"""
1216
- latest_code = self.enhanced_agent.get_latest_code_artifact(self.current_conversation)
1217
- if latest_code:
1218
- return latest_code, "βœ… Latest artifact loaded"
1219
- return "# No code artifacts available", "⚠️ No artifacts found"
1220
-
1221
- async def process_message(message, history):
1222
- """Process a chat message"""
 
1223
  if not message.strip():
1224
- return "", history, "Please enter a message"
1225
-
1226
- # Add user message to history
1227
  history = history + [[message, None]]
1228
-
1229
  try:
1230
- # Get AI response using the enhanced agent
1231
- response = await self.enhanced_agent.chat_with_canvas(
1232
- message,
1233
- self.current_conversation,
1234
- include_canvas=True
1235
- )
1236
-
1237
- # Update history with response
1238
  history[-1][1] = response
1239
-
1240
- # Get updated artifacts
1241
- artifacts = get_artifacts()
1242
-
1243
  status = f"βœ… Response received. Canvas artifacts: {len(artifacts)}"
1244
  return "", history, status, artifacts
1245
-
1246
  except Exception as e:
1247
  error_msg = f"❌ Error: {str(e)}"
1248
  history[-1][1] = error_msg
1249
- return "", history, error_msg, get_artifacts()
1250
-
1251
- def create_code_query(code, query_template):
1252
- """Create a query about code"""
1253
- if not code.strip():
1254
- return "Please provide some code first"
1255
- return query_template.format(code=code)
1256
-
1257
- def discuss_code(code):
1258
- return create_code_query(code, "Please analyze this code:\n```python\n{code}\n```")
1259
-
1260
- def analyze_code(code):
1261
- return create_code_query(code, "Perform a comprehensive analysis of this code:\n```python\n{code}\n```")
1262
-
1263
- def optimize_code(code):
1264
- return create_code_query(code, "Optimize this code for performance and best practices:\n```python\n{code}\n```")
1265
-
1266
- def document_code(code):
1267
- return create_code_query(code, "Generate comprehensive documentation for this code:\n```python\n{code}\n```")
1268
-
1269
- def clear_chat():
1270
- """Clear chat history"""
1271
- self.enhanced_agent.agent.clear_history()
1272
- return [], "βœ… Chat cleared"
1273
-
1274
- def new_session():
1275
- """Start new session"""
1276
- self.enhanced_agent.agent.clear_history()
1277
- self.enhanced_agent.clear_canvas(self.current_conversation)
1278
- return [], "# New collaborative session started\n\nprint('Ready for development!')", "πŸ†• New session started", []
1279
-
1280
- # Connect event handlers
1281
  send_btn.click(
1282
  process_message,
1283
- inputs=[message_input, chatbot],
1284
  outputs=[message_input, chatbot, status_display, artifact_display]
1285
  )
1286
-
1287
  message_input.submit(
1288
  process_message,
1289
- inputs=[message_input, chatbot],
1290
  outputs=[message_input, chatbot, status_display, artifact_display]
1291
  )
1292
-
1293
- discuss_code_btn.click(
1294
- discuss_code,
1295
- inputs=code_editor,
1296
- outputs=message_input
1297
- )
1298
-
1299
- analyze_code_btn.click(
1300
- analyze_code,
1301
- inputs=code_editor,
1302
- outputs=message_input
1303
- )
1304
-
1305
- optimize_code_btn.click(
1306
- optimize_code,
1307
- inputs=code_editor,
1308
- outputs=message_input
1309
- )
1310
-
1311
- document_code_btn.click(
1312
- document_code,
1313
- inputs=code_editor,
1314
- outputs=message_input
1315
- )
1316
-
1317
- refresh_artifacts_btn.click(
1318
- get_artifacts,
1319
- outputs=artifact_display
1320
- )
1321
-
1322
- clear_canvas_btn.click(
1323
- clear_canvas,
1324
- outputs=[artifact_display, status_display]
1325
- )
1326
-
1327
- load_latest_btn.click(
1328
- load_latest_artifact_to_canvas,
1329
- outputs=[code_editor, status_display]
1330
- )
1331
-
1332
- clear_chat_btn.click(
1333
- clear_chat,
1334
- outputs=[chatbot, status_display]
1335
- )
1336
-
1337
- new_session_btn.click(
1338
- new_session,
1339
- outputs=[chatbot, code_editor, status_display, artifact_display]
1340
- )
1341
-
1342
- # Initialize artifacts on load
1343
  interface.load(get_artifacts, outputs=artifact_display)
1344
-
1345
  return interface
1346
 
 
 
 
 
 
 
 
 
1347
 
1348
- # --- Example Usage ---
1349
- if __name__ == "__main__":
1350
- """
1351
- Example of how to use this interface with your AI_Agent
1352
-
1353
- Uncomment and modify based on your actual import paths:
1354
- """
1355
-
1356
- # Create your agent instance
1357
- my_agent = AI_Agent(
1358
- model_id="leroydyer/qwen/qwen3-0.6b-q4_k_m.gguf",
1359
- system_prompt="You are a helpful AI development assistant."
1360
- )
1361
-
1362
- # Create and launch the interface
1363
- interface = LcarsInterface(my_agent)
1364
  demo = interface.create_interface()
1365
- demo.launch(share=False, show_error=True)
1366
-
1367
- console.log("[bold yellow]⚠️ Please uncomment and configure the main block with your AI_Agent[/bold yellow]")
1368
- console.log("[bold cyan]Example:[/bold cyan]")
1369
- console.log(" from your_module import AI_Agent")
1370
- console.log(" my_agent = AI_Agent(model_id='your-model', system_prompt='...')")
1371
- console.log(" interface = LcarsInterface(my_agent)")
1372
- console.log(" demo = interface.create_interface()")
1373
- console.log(" demo.launch()")
 
1
  # File: enhanced_gradio_interface.py
 
2
  import asyncio
3
  from collections import defaultdict
4
  import json
 
6
  import re
7
  import time
8
  import uuid
9
+ from typing import List, Dict, Any, Optional, Callable
10
  from dataclasses import dataclass
11
+ from threading import Lock, Event, Thread
 
 
 
12
  import queue
13
  import traceback
 
 
 
14
  from queue import Queue, Empty
 
 
15
  from concurrent.futures import ThreadPoolExecutor
 
 
16
  import gradio as gr
17
  from openai import AsyncOpenAI, OpenAI
18
  import pyttsx3
19
  from rich.console import Console
20
+
21
+ # --- Configuration ---
22
+ BASE_URL = "http://localhost:1234/v1"
23
+ BASE_API_KEY = "not-needed"
24
+ # Using the sync client for the agent's internal sync calls
25
+ CLIENT = OpenAI(base_url=BASE_URL, api_key=BASE_API_KEY)
26
+ # Using the async client for the static async methods and instance methods
27
+ BASE_CLIENT = AsyncOpenAI(base_url=BASE_URL, api_key=BASE_API_KEY)
28
+ BASEMODEL_ID = "leroydyer/qwen/qwen3-0.6b-q4_k_m.gguf"
29
+
 
 
 
30
  console = Console()
 
 
 
31
 
32
+ # HuggingFace Spaces configuration (if needed)
33
+ HF_INFERENCE_URL = "https://api-inference.huggingface.co/models/"
34
+ HF_API_KEY = os.getenv("HF_API_KEY", "")
35
+
36
+ # Available model options (for UI reference, actual client is configured separately)
37
+ MODEL_OPTIONS = {
38
+ "Local LM Studio": BASE_URL, # This is a URL, not a model ID
39
+ "Codellama 7B": "codellama/CodeLlama-7b-hf",
40
+ "Mistral 7B": "mistralai/Mistral-7B-v0.1",
41
+ "Llama 2 7B": "meta-llama/Llama-2-7b-chat-hf",
42
+ "Falcon 7B": "tiiuae/falcon-7b-instruct"
43
+ }
44
+
45
+ DEFAULT_TEMPERATURE = 0.7
46
+ DEFAULT_MAX_TOKENS = 5000
47
+
48
+ # --- Canvas Artifact Support ---
49
+ @dataclass
50
+ class CanvasArtifact:
51
+ id: str
52
+ type: str # 'code', 'diagram', 'text', 'image'
53
+ content: str
54
+ title: str
55
+ timestamp: float
56
+ metadata: Dict[str, Any] = None
57
+
58
+ def __post_init__(self):
59
+ if self.metadata is None:
60
+ self.metadata = {}
61
+
62
  @dataclass
63
  class LLMMessage:
64
  role: str
 
93
  success: bool = True
94
  error: str = None
95
 
96
+ # --- Event Manager ---
97
  class EventManager:
98
  def __init__(self):
99
  self._handlers = defaultdict(list)
100
+ self._lock = Lock()
101
 
102
  def register(self, event: str, handler: Callable):
103
  with self._lock:
 
129
  EVENT_MANAGER.unregister(event, handler)
130
 
131
  class LLMAgent:
132
+ """Main Agent Driver !
133
+ Agent For Multiple messages at once ,
134
+ has a message queing service as well as a generator method for easy integration with console
135
  applications as well as ui !"""
136
  def __init__(
137
  self,
 
142
  timeout: int = 30000,
143
  max_tokens: int = 5000,
144
  temperature: float = 0.3,
145
+ base_url: str = BASE_URL,
146
+ api_key: str = BASE_API_KEY,
147
+ generate_fn: Callable[[List[Dict[str, str]]], str] = None # Changed to sync function
148
  ):
149
  self.model_id = model_id
150
  self.system_prompt = system_prompt or "You are a helpful AI assistant."
 
154
  self.is_running = False
155
  self._stop_event = Event()
156
  self.processing_thread = None
157
+
158
  # Conversation tracking
159
  self.conversations: Dict[str, List[LLMMessage]] = {}
160
  self.max_history_length = 20
161
+ # Use the provided generate function or the default sync one
162
+ self._generate = generate_fn or self._default_generate_sync
163
  self.api_key = api_key
164
+ self.base_url = base_url
165
  self.max_tokens = max_tokens
166
  self.temperature = temperature
167
+ # Use the global async client for instance methods if needed
168
+ self.async_client = BASE_CLIENT
169
+
170
  # Active requests waiting for responses
171
  self.pending_requests: Dict[str, LLMRequest] = {}
172
  self.pending_requests_lock = Lock()
173
+
174
+ # Canvas artifacts
175
+ self.canvas_artifacts: Dict[str, List[CanvasArtifact]] = defaultdict(list)
176
+
177
  # Register internal event handlers
178
  self._register_event_handlers()
179
+
180
  # Speech synthesis
181
  try:
182
  self.tts_engine = pyttsx3.init()
 
185
  except Exception as e:
186
  console.log(f"[yellow]TTS not available: {e}[/yellow]")
187
  self.speech_enabled = False
 
188
  console.log("[bold green]πŸš€ Enhanced LLM Agent Initialized[/bold green]")
189
+
190
  # Start the processing thread immediately
191
  self.start()
192
+
193
  def setup_tts(self):
194
  """Configure text-to-speech engine"""
195
  if hasattr(self, 'tts_engine'):
 
203
  """Convert text to speech in a non-blocking way"""
204
  if not hasattr(self, 'speech_enabled') or not self.speech_enabled:
205
  return
 
206
  def _speak():
207
  try:
208
  # Clean text for speech (remove markdown, code blocks)
 
210
  clean_text = re.sub(r'`.*?`', '', clean_text)
211
  clean_text = clean_text.strip()
212
  if clean_text:
213
+ self.tts_engine.say(clean_text)
214
  self.tts_engine.runAndWait()
215
  else:
216
+ self.tts_engine.say(text)
217
+ self.tts_engine.runAndWait()
218
  except Exception as e:
219
  console.log(f"[red]TTS Error: {e}[/red]")
220
+ thread = Thread(target=_speak, daemon=True)
 
221
  thread.start()
222
+
223
+ def _default_generate_sync(self, messages: List[Dict[str, str]]) -> str:
224
+ """Default sync generate function if none provided"""
225
+ return self._call_llm_sync(messages)
226
+
227
  def _register_event_handlers(self):
228
  """Register internal event handlers for response routing"""
229
  RegisterEvent("llm_internal_response", self._handle_internal_response)
230
+
231
  def _handle_internal_response(self, response: LLMResponse):
232
  """Route responses to the appropriate request handlers"""
233
  console.log(f"[bold cyan]Handling internal response for: {response.request_id}[/bold cyan]")
 
234
  request = None
235
  with self.pending_requests_lock:
236
  if response.request_id in self.pending_requests:
 
240
  else:
241
  console.log(f"No pending request found for: {response.request_id}", style="yellow")
242
  return
243
+
244
  # Raise the specific response event
245
  if request.response_event:
246
  console.log(f"[bold green]Raising event: {request.response_event}[/bold green]")
247
  RaiseEvent(request.response_event, response)
248
+
249
  # Call callback if provided
250
  if request.callback:
251
  try:
 
253
  request.callback(response)
254
  except Exception as e:
255
  console.log(f"Error in callback: {e}", style="bold red")
256
+
257
  def _add_to_conversation_history(self, conversation_id: str, message: LLMMessage):
258
  """Add message to conversation history"""
259
  if conversation_id not in self.conversations:
260
  self.conversations[conversation_id] = []
 
261
  self.conversations[conversation_id].append(message)
 
262
  # Trim history if too long
263
  if len(self.conversations[conversation_id]) > self.max_history_length * 2:
264
  self.conversations[conversation_id] = self.conversations[conversation_id][-(self.max_history_length * 2):]
265
+
266
  def _build_messages_from_conversation(self, conversation_id: str, new_message: LLMMessage) -> List[Dict[str, str]]:
267
  """Build message list from conversation history"""
268
  messages = []
 
269
  # Add system prompt
270
  if self.system_prompt:
271
  messages.append({"role": "system", "content": self.system_prompt})
 
272
  # Add conversation history
273
  if conversation_id in self.conversations:
274
  for msg in self.conversations[conversation_id][-self.max_history_length:]:
275
  messages.append({"role": msg.role, "content": msg.content})
 
276
  # Add the new message
277
  messages.append({"role": new_message.role, "content": new_message.content})
 
278
  return messages
279
+
280
  def _process_llm_request(self, request: LLMRequest):
281
  """Process a single LLM request"""
282
  console.log(f"[bold green]Processing LLM request: {request.message.message_id}[/bold green]")
 
286
  request.message.conversation_id or "default",
287
  request.message
288
  )
 
289
  console.log(f"Calling LLM with {len(messages)} messages")
290
+ # Call LLM using the sync generate function
291
+ response_content = self._generate(messages)
292
+ console.log(f"[bold green]LLM response received: {response_content[:50]}...[/bold green]")
293
+
 
 
294
  # Create response message
295
  response_message = LLMMessage(
296
  role="assistant",
 
298
  conversation_id=request.message.conversation_id,
299
  metadata={"request_id": request.message.message_id}
300
  )
301
+
302
  # Update conversation history
303
  self._add_to_conversation_history(
304
  request.message.conversation_id or "default",
 
308
  request.message.conversation_id or "default",
309
  response_message
310
  )
311
+
312
  # Create and send response
313
  response = LLMResponse(
314
  message=response_message,
315
  request_id=request.message.message_id,
316
  success=True
317
  )
 
318
  console.log(f"[bold blue]Sending internal response for: {request.message.message_id}[/bold blue]")
319
  RaiseEvent("llm_internal_response", response)
320
+
321
  except Exception as e:
322
  console.log(f"[bold red]Error processing LLM request: {e}[/bold red]")
323
  traceback.print_exc()
 
332
  success=False,
333
  error=str(e)
334
  )
 
335
  RaiseEvent("llm_internal_response", error_response)
336
+
337
  def _call_llm_sync(self, messages: List[Dict[str, str]]) -> str:
338
  """Sync call to the LLM with retry logic"""
339
  console.log(f"Making LLM call to {self.model_id}")
 
352
  console.log(f"LLM call attempt {attempt + 1} failed: {e}")
353
  if attempt == self.max_retries - 1:
354
  raise e
355
+ time.sleep(1) # Wait before retry
356
+
357
  def _process_queue(self):
358
  """Main queue processing loop"""
359
  console.log("[bold cyan]LLM Agent queue processor started[/bold cyan]")
 
370
  console.log(f"Error in queue processing: {e}", style="bold red")
371
  traceback.print_exc()
372
  console.log("[bold cyan]LLM Agent queue processor stopped[/bold cyan]")
373
+
374
  def send_message(
375
  self,
376
  content: str,
 
383
  """Send a message to the LLM and get response via events"""
384
  if not self.is_running:
385
  raise RuntimeError("LLM Agent is not running. Call start() first.")
386
+
387
  # Create message
388
  message = LLMMessage(
389
  role=role,
 
391
  conversation_id=conversation_id,
392
  metadata=metadata or {}
393
  )
394
+
395
  # Create request
396
  request = LLMRequest(
397
  message=message,
398
  response_event=response_event,
399
  callback=callback
400
  )
401
+
402
  # Store in pending requests BEFORE adding to queue
403
  with self.pending_requests_lock:
404
  self.pending_requests[message.message_id] = request
405
  console.log(f"Added to pending requests: {message.message_id}")
406
+
407
  # Add to queue
408
  try:
409
  self.request_queue.put(request, timeout=5.0)
 
415
  if message.message_id in self.pending_requests:
416
  del self.pending_requests[message.message_id]
417
  raise RuntimeError("LLM Agent queue is full")
418
+
419
  async def chat(self, messages: List[Dict[str, str]]) -> str:
420
  """
421
  Async chat method that sends message via queue and returns response string.
 
428
  def chat_callback(response: LLMResponse):
429
  """Callback when LLM responds - thread-safe"""
430
  console.log(f"[bold yellow]βœ“ CHAT CALLBACK TRIGGERED![/bold yellow]")
 
431
  if not response_future.done():
432
  if response.success:
433
  content = response.message.content
434
+ console.log(f"Callback received content: {content[:50]}...")
435
  # Schedule setting the future result on the main event loop
436
  loop.call_soon_threadsafe(response_future.set_result, content)
437
  else:
 
442
  console.log(f"[bold red]Future already done, ignoring callback[/bold red]")
443
 
444
  console.log(f"Sending message to LLM agent...")
 
445
  # Extract the actual message content from the messages list
446
  user_message = ""
447
  for msg in messages:
448
  if msg.get("role") == "user":
449
  user_message = msg.get("content", "")
450
  break
 
451
  if not user_message.strip():
452
  return ""
453
 
 
458
  conversation_id="default",
459
  callback=chat_callback
460
  )
 
461
  console.log(f"Message sent with ID: {message_id}, waiting for response...")
 
462
  # Wait for the response and return it
463
  try:
464
  response = await asyncio.wait_for(response_future, timeout=self.timeout)
465
  console.log(f"[bold green]βœ“ Chat complete! Response length: {len(response)}[/bold green]")
466
  return response
 
467
  except asyncio.TimeoutError:
468
  console.log("[bold red]Response timeout[/bold red]")
469
  # Clean up the pending request
 
471
  if message_id in self.pending_requests:
472
  del self.pending_requests[message_id]
473
  return "❌ Response timeout - check if LLM server is running"
 
474
  except Exception as e:
475
  console.log(f"[bold red]Error sending message: {e}[/bold red]")
476
  traceback.print_exc()
477
  return f"❌ Error sending message: {e}"
478
+
479
  def start(self):
480
  """Start the LLM agent"""
481
  if not self.is_running:
 
484
  self.processing_thread = Thread(target=self._process_queue, daemon=True)
485
  self.processing_thread.start()
486
  console.log("[bold green]LLM Agent started[/bold green]")
487
+
488
  def stop(self):
489
  """Stop the LLM agent"""
490
  console.log("Stopping LLM Agent...")
 
493
  self.processing_thread.join(timeout=10)
494
  self.is_running = False
495
  console.log("LLM Agent stopped")
496
+
497
  def get_conversation_history(self, conversation_id: str = "default") -> List[LLMMessage]:
498
  """Get conversation history"""
499
  return self.conversations.get(conversation_id, [])[:]
500
+
501
  def clear_conversation(self, conversation_id: str = "default"):
502
  """Clear conversation history"""
503
  if conversation_id in self.conversations:
504
  del self.conversations[conversation_id]
505
 
506
+ # --- Canvas Methods ---
507
+ def add_artifact(self, conversation_id: str, artifact_type: str, content: str, title: str = "", metadata: Dict = None):
508
+ """Add an artifact to the canvas for a conversation."""
509
+ artifact = CanvasArtifact(
510
+ id=str(uuid.uuid4()),
511
+ type=artifact_type,
512
+ content=content,
513
+ title=title,
514
+ timestamp=time.time(),
515
+ metadata=metadata or {}
516
+ )
517
+ self.canvas_artifacts[conversation_id].append(artifact)
518
+
519
+ def get_canvas_artifacts(self, conversation_id: str = "default") -> List[CanvasArtifact]:
520
+ """Get all artifacts for a conversation."""
521
+ return self.canvas_artifacts.get(conversation_id, [])
522
+
523
+ def get_canvas_summary(self, conversation_id: str = "default") -> List[Dict[str, Any]]:
524
+ """Get a summary of artifacts for display."""
525
+ artifacts = self.get_canvas_artifacts(conversation_id)
526
+ return [{"id": a.id, "type": a.type, "title": a.title, "timestamp": a.timestamp} for a in artifacts]
527
+
528
+ def clear_canvas(self, conversation_id: str = "default"):
529
+ """Clear canvas artifacts for a conversation."""
530
+ if conversation_id in self.canvas_artifacts:
531
+ self.canvas_artifacts[conversation_id] = []
532
+
533
+ async def chat_with_canvas(self, user_message: str, conversation_id: str = "default", include_canvas: bool = False) -> str:
534
+ """
535
+ Chat method that can optionally include canvas content in the prompt.
536
+ """
537
+ messages = [{"role": "user", "content": user_message}]
538
+ if include_canvas:
539
+ canvas_artifacts = self.get_canvas_artifacts(conversation_id)
540
+ if canvas_artifacts:
541
+ canvas_content = "\n\n--- CANVAS CONTENT ---\n"
542
+ for artifact in canvas_artifacts:
543
+ canvas_content += f"\n[{artifact.type}] {artifact.title or 'Untitled'}:\n{artifact.content}\n"
544
+ canvas_content += "\n--- END CANVAS CONTENT ---\n"
545
+ # Add canvas content as a system message
546
+ messages.insert(0, {"role": "system", "content": canvas_content})
547
+ return await self.chat(messages)
548
 
 
 
 
549
  @staticmethod
550
+ async def openai_generate(messages: List[Dict[str, str]], max_tokens: int = 8096, temperature: float = 0.4, model: str = BASEMODEL_ID, tools=None) -> str:
551
  """Static method for generating responses using OpenAI API"""
552
  try:
553
  resp = await BASE_CLIENT.chat.completions.create(
 
562
  except Exception as e:
563
  console.log(f"[bold red]Error in openai_generate: {e}[/bold red]")
564
  return f"[LLM_Agent Error - openai_generate: {str(e)}]"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
 
566
  def get_queue_size(self) -> int:
567
  """Get current queue size"""
568
  return self.request_queue.qsize()
569
+
570
  def get_pending_requests_count(self) -> int:
571
  """Get number of pending requests"""
572
  with self.pending_requests_lock:
573
  return len(self.pending_requests)
574
+
575
  def get_status(self) -> Dict[str, Any]:
576
  """Get agent status information"""
577
  return {
 
581
  "conversations_count": len(self.conversations),
582
  "model": self.model_id
583
  }
584
+
585
  class AI_Agent:
586
  def __init__(self, model_id: str, system_prompt: str = "You are a helpful assistant. Respond concisely in 1-2 sentences.", history: List[Dict] = None):
587
  self.model_id = model_id
588
  self.system_prompt = system_prompt
589
  self.history = history or []
590
  self.conversation_id = f"conv_{uuid.uuid4().hex[:8]}"
591
+ # Create agent instance - using the static async method as the generate function
 
592
  self.client = LLMAgent(
593
  model_id=model_id,
594
  system_prompt=self.system_prompt,
595
+ generate_fn=lambda msgs: asyncio.run(LLMAgent.openai_generate(msgs, model=model_id))
596
  )
 
597
  console.log(f"[bold green]βœ“ MyAgent initialized with model: {model_id}[/bold green]")
598
+
599
  async def call_llm(self, messages: List[Dict], use_history: bool = True) -> str:
600
  """
601
  Send messages to LLM and get response
 
607
  """
608
  try:
609
  console.log(f"[bold yellow]Sending {len(messages)} messages to LLM (use_history: {use_history})...[/bold yellow]")
 
610
  # Enhance messages based on history setting
611
  enhanced_messages = await self._enhance_messages(messages, use_history)
 
612
  response = await self.client.chat(enhanced_messages)
613
  console.log(f"[bold green]βœ“ Response received ({len(response)} chars)[/bold green]")
 
614
  # Update conversation history ONLY if we're using history
615
  if use_history:
616
  self._update_history(messages, response)
 
617
  return response
 
618
  except Exception as e:
619
  console.log(f"[bold red]βœ— ERROR: {e}[/bold red]")
620
  traceback.print_exc()
621
  return f"Error: {str(e)}"
622
+
623
  async def _enhance_messages(self, messages: List[Dict], use_history: bool) -> List[Dict]:
624
  """Enhance messages with system prompt and optional history"""
625
  enhanced = []
 
626
  # Add system prompt if not already in messages
627
  has_system = any(msg.get('role') == 'system' for msg in messages)
628
  if not has_system and self.system_prompt:
629
  enhanced.append({"role": "system", "content": self.system_prompt})
 
630
  # Add conversation history only if requested
631
  if use_history and self.history:
632
  enhanced.extend(self.history[-10:]) # Last 10 messages for context
 
633
  # Add current messages
634
  enhanced.extend(messages)
 
635
  return enhanced
636
+
637
  def _update_history(self, messages: List[Dict], response: str):
638
  """Update conversation history with new exchange"""
639
  # Add user messages to history
640
  for msg in messages:
641
  if msg.get('role') in ['user', 'assistant']:
642
  self.history.append(msg)
 
643
  # Add assistant response to history
644
  self.history.append({"role": "assistant", "content": response})
 
645
  # Keep history manageable (last 20 exchanges)
646
  if len(self.history) > 40: # 20 user + 20 assistant messages
647
  self.history = self.history[-40:]
648
+
649
  async def simple_query(self, query: str) -> str:
650
  """Simple one-shot query method - NO history/context"""
651
  messages = [{"role": "user", "content": query}]
652
  return await self.call_llm(messages, use_history=False)
653
+
654
  async def multi_turn_chat(self, user_input: str) -> str:
655
  """Multi-turn chat that maintains context across calls"""
656
  messages = [{"role": "user", "content": user_input}]
657
  response = await self.call_llm(messages, use_history=True)
658
  return response
 
659
 
660
  def get_conversation_summary(self) -> Dict:
661
  """Get conversation summary"""
 
666
  "assistant_messages": len([msg for msg in self.history if msg.get('role') == 'assistant']),
667
  "recent_exchanges": self.history[-4:] if self.history else []
668
  }
669
+
670
  def clear_history(self):
671
  """Clear conversation history"""
672
  self.history.clear()
673
  console.log("[bold yellow]Conversation history cleared[/bold yellow]")
674
+
675
  def update_system_prompt(self, new_prompt: str):
676
  """Update the system prompt"""
677
  self.system_prompt = new_prompt
678
  console.log(f"[bold blue]System prompt updated[/bold blue]")
679
+
680
  def stop(self):
681
  """Stop the client gracefully"""
682
  if hasattr(self, 'client') and self.client:
683
  self.client.stop()
684
+ console.log("[bold yellow]MyAgent client stopped[/bold yellow]")
685
+
686
+ async def contextual_query(self, query: str, context_messages: List[Dict] = None,
687
  context_text: str = None, context_files: List[str] = None) -> str:
688
  """
689
  Query with specific context but doesn't update main history
 
690
  Args:
691
  query: The user question
692
  context_messages: List of message dicts for context
 
694
  context_files: List of file paths to read and include as context
695
  """
696
  messages = []
 
697
  # Add system prompt
698
  if self.system_prompt:
699
  messages.append({"role": "system", "content": self.system_prompt})
 
700
  # Handle different context types
701
  if context_messages:
702
  messages.extend(context_messages)
 
703
  if context_text:
704
  messages.append({"role": "system", "content": f"Additional context: {context_text}"})
 
705
  if context_files:
706
  file_context = await self._read_files_context(context_files)
707
  if file_context:
708
  messages.append({"role": "system", "content": f"File contents:\n{file_context}"})
 
709
  # Add the actual query
710
  messages.append({"role": "user", "content": query})
 
711
  return await self.call_llm(messages, use_history=False)
712
+
713
  async def _read_files_context(self, file_paths: List[str]) -> str:
714
  """Read multiple files and return as context string"""
715
  contexts = []
 
723
  console.log(f"[bold yellow]File not found: {file_path}[/bold yellow]")
724
  except Exception as e:
725
  console.log(f"[bold red]Error reading file {file_path}: {e}[/bold red]")
726
+ return "\n".join(contexts) if contexts else ""
727
+
 
 
728
  async def query_with_code_context(self, query: str, code_snippets: List[str] = None,
729
  code_files: List[str] = None) -> str:
730
  """
731
  Specialized contextual query for code-related questions
732
  """
733
  code_context = "CODE CONTEXT:\n"
 
734
  if code_snippets:
735
  for i, snippet in enumerate(code_snippets, 1):
736
  code_context += f"\nSnippet {i}:\n```\n{snippet}\n```\n"
 
737
  if code_files:
738
  # Read code files and include them
739
  for file_path in code_files:
 
745
  except Exception as e:
746
  code_context += f"Error reading file: {e}"
747
  code_context += "\n```\n"
 
748
  return await self.contextual_query(query, context_text=code_context)
749
+
750
  async def multi_context_query(self, query: str, contexts: Dict[str, Any]) -> str:
751
  """
752
  Advanced contextual query with multiple context types
 
753
  Args:
754
  query: The user question
755
  contexts: Dict with various context types
 
761
  - 'metadata': Any additional metadata
762
  """
763
  all_context_messages = []
 
764
  # Build context from different sources
765
  if contexts.get('text'):
766
  all_context_messages.append({"role": "system", "content": f"Context: {contexts['text']}"})
 
767
  if contexts.get('messages'):
768
  all_context_messages.extend(contexts['messages'])
 
769
  if contexts.get('files'):
770
  file_context = await self._read_files_context(contexts['files'])
771
  if file_context:
772
  all_context_messages.append({"role": "system", "content": f"File Contents:\n{file_context}"})
 
773
  if contexts.get('code'):
774
+ code_context = "\n".join([f"Code snippet {i}:\n```\n{code}\n```"
775
  for i, code in enumerate(contexts['code'], 1)])
776
  all_context_messages.append({"role": "system", "content": f"Code Context:\n{code_context}"})
 
777
  if contexts.get('metadata'):
778
  all_context_messages.append({"role": "system", "content": f"Metadata: {contexts['metadata']}"})
 
779
  return await self.contextual_query(query, context_messages=all_context_messages)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
 
781
 
782
+ # --- LCARS Styled Gradio Interface ---
783
  class LcarsInterface:
784
+ def __init__(self):
785
+ # Start with the configured local client
786
+ self.agent = LLMAgent(generate_fn=LLMAgent.openai_generate)
787
+
 
 
 
 
 
 
 
 
 
 
788
  def create_interface(self):
789
  """Create the full LCARS-styled interface"""
 
 
790
  lcars_css = """
791
  :root {
792
  --lcars-orange: #FF9900;
 
799
  --lcars-gray: #424242;
800
  --lcars-yellow: #FFFF66;
801
  }
 
802
  body {
803
  background: var(--lcars-black);
804
  color: var(--lcars-orange);
805
  font-family: 'Antonio', 'LCD', 'Courier New', monospace;
806
+ margin: 0;
807
+ padding: 0;
808
  }
 
809
  .gradio-container {
810
  background: var(--lcars-black) !important;
811
  min-height: 100vh;
812
  }
 
813
  .lcars-container {
814
  background: var(--lcars-black);
815
  border: 4px solid var(--lcars-orange);
 
817
  min-height: 100vh;
818
  padding: 20px;
819
  }
 
820
  .lcars-header {
821
  background: linear-gradient(90deg, var(--lcars-red), var(--lcars-orange));
822
  padding: 20px 40px;
823
  border-radius: 0 60px 0 0;
824
  margin: -20px -20px 20px -20px;
825
  border-bottom: 6px solid var(--lcars-blue);
 
826
  }
 
827
  .lcars-title {
828
+ font-size: 2.5em;
829
  font-weight: bold;
830
  color: var(--lcars-black);
 
831
  margin: 0;
 
832
  }
 
833
  .lcars-subtitle {
834
+ font-size: 1.2em;
835
  color: var(--lcars-black);
836
  margin: 10px 0 0 0;
 
837
  }
 
838
  .lcars-panel {
839
+ background: rgba(66, 66, 66, 0.9);
840
+ border: 2px solid var(--lcars-orange);
841
+ border-radius: 0 20px 0 20px;
842
+ padding: 15px;
843
+ margin-bottom: 15px;
 
844
  }
 
845
  .lcars-button {
846
+ background: var(--lcars-orange);
847
  color: var(--lcars-black) !important;
848
  border: none !important;
849
+ border-radius: 0 15px 0 15px !important;
850
+ padding: 10px 20px !important;
851
  font-family: inherit !important;
852
  font-weight: bold !important;
853
+ margin: 5px !important;
 
 
 
 
854
  }
 
855
  .lcars-button:hover {
856
+ background: var(--lcars-red) !important;
 
 
857
  }
 
858
  .lcars-input {
859
  background: var(--lcars-black) !important;
860
  color: var(--lcars-orange) !important;
861
  border: 2px solid var(--lcars-blue) !important;
862
+ border-radius: 0 10px 0 10px !important;
863
+ padding: 10px !important;
 
 
864
  }
 
865
  .lcars-chatbot {
866
  background: var(--lcars-black) !important;
867
+ border: 2px solid var(--lcars-purple) !important;
868
+ border-radius: 0 15px 0 15px !important;
 
 
 
 
 
 
 
 
 
 
 
869
  }
 
870
  .status-indicator {
871
  display: inline-block;
872
+ width: 12px;
873
+ height: 12px;
874
  border-radius: 50%;
875
  background: var(--lcars-red);
876
+ margin-right: 8px;
 
877
  }
 
878
  .status-online {
879
  background: var(--lcars-blue);
880
+ animation: pulse 2s infinite;
881
  }
 
882
  @keyframes pulse {
883
+ 0% { opacity: 1; }
884
+ 50% { opacity: 0.5; }
885
+ 100% { opacity: 1; }
 
 
 
 
 
 
 
 
 
886
  }
887
  """
888
 
889
  with gr.Blocks(css=lcars_css, theme=gr.themes.Default(), title="LCARS Terminal") as interface:
 
890
  with gr.Column(elem_classes="lcars-container"):
891
+ # Header
892
  with gr.Row(elem_classes="lcars-header"):
893
  gr.Markdown("""
894
  <div style="text-align: center; width: 100%;">
895
+ <div class="lcars-title">πŸš€ LCARS TERMINAL</div>
896
+ <div class="lcars-subtitle">STARFLEET AI DEVELOPMENT CONSOLE</div>
897
  <div style="margin-top: 10px;">
898
  <span class="status-indicator status-online"></span>
899
  <span style="color: var(--lcars-black); font-weight: bold;">SYSTEM ONLINE</span>
900
  </div>
901
  </div>
902
  """)
903
+ # Main Content
 
904
  with gr.Row():
905
+ # Left Sidebar
906
+ with gr.Column(scale=1):
907
+ # Configuration Panel
908
  with gr.Column(elem_classes="lcars-panel"):
909
+ gr.Markdown("### πŸ”§ CONFIGURATION")
910
+ with gr.Row():
911
+ model_dropdown = gr.Dropdown(
912
+ choices=list(MODEL_OPTIONS.keys())[1:], # Exclude the 'Local LM Studio' URL entry
913
+ value=list(MODEL_OPTIONS.keys())[1], # Default to Codellama 7B
914
+ label="AI Model",
915
+ elem_classes="lcars-input"
916
+ )
917
+ fetch_models_btn = gr.Button("πŸ“‘ Fetch Models", elem_classes="lcars-button")
918
+ with gr.Row():
919
+ temperature = gr.Slider(0.0, 2.0, value=0.7, label="Temperature")
920
+ max_tokens = gr.Slider(128, 8192, value=2000, step=128, label="Max Tokens")
921
+ with gr.Row():
922
+ update_config_btn = gr.Button("πŸ’Ύ Apply Config", elem_classes="lcars-button")
923
+ speech_toggle = gr.Checkbox(value=True, label="πŸ”Š Speech Output")
924
+ # Canvas Artifacts
925
+ with gr.Column(elem_classes="lcars-panel"):
926
+ gr.Markdown("### 🎨 CANVAS ARTIFACTS")
927
+ artifact_display = gr.JSON(label="Canvas Summary")
928
  with gr.Row():
929
  refresh_artifacts_btn = gr.Button("πŸ”„ Refresh", elem_classes="lcars-button")
930
  clear_canvas_btn = gr.Button("πŸ—‘οΈ Clear Canvas", elem_classes="lcars-button")
931
+ # Main Content Area
 
 
932
  with gr.Column(scale=2):
933
+ # Code Canvas
934
  with gr.Accordion("πŸ’» COLLABORATIVE CODE CANVAS", open=True):
935
  code_editor = gr.Code(
936
+ value="# Welcome to LCARS Collaborative Canvas\nprint('Hello, Starfleet!')",
937
  language="python",
938
+ lines=15,
939
+ label=""
 
940
  )
 
941
  with gr.Row():
942
+ load_to_chat_btn = gr.Button("πŸ’¬ Discuss Code", elem_classes="lcars-button")
943
+ analyze_btn = gr.Button("πŸ” Analyze", elem_classes="lcars-button")
944
+ optimize_btn = gr.Button("⚑ Optimize", elem_classes="lcars-button")
 
 
945
  # Chat Interface
946
  with gr.Column(elem_classes="lcars-panel"):
947
+ gr.Markdown("### πŸ’¬ MISSION LOG")
948
+ chatbot = gr.Chatbot(label="", height=300, elem_classes="lcars-chatbot")
 
 
 
 
 
 
949
  with gr.Row():
950
  message_input = gr.Textbox(
951
  placeholder="Enter your command or query...",
952
  show_label=False,
953
  lines=2,
954
+ elem_classes="lcars-input"
 
955
  )
956
+ send_btn = gr.Button("πŸš€ SEND", elem_classes="lcars-button")
957
+ # Status
 
958
  with gr.Row():
959
  status_display = gr.Textbox(
960
+ value="LCARS terminal operational. Awaiting commands.",
961
  label="Status",
962
  max_lines=2,
963
  elem_classes="lcars-input"
 
965
  with gr.Column(scale=0):
966
  clear_chat_btn = gr.Button("πŸ—‘οΈ Clear Chat", elem_classes="lcars-button")
967
  new_session_btn = gr.Button("πŸ†• New Session", elem_classes="lcars-button")
968
+
969
  # === EVENT HANDLERS ===
970
+ def update_agent_config(model_key, temp_val, max_tok_val, speech_enabled):
971
+ # Map UI model key to actual model ID
972
+ model_id = MODEL_OPTIONS.get(model_key, BASEMODEL_ID)
973
+
974
+ # Update agent attributes
975
+ self.agent.model_id = model_id
976
+ self.agent.temperature = temp_val
977
+ self.agent.max_tokens = max_tok_val
978
+ self.agent.speech_enabled = speech_enabled
979
+
980
+ # Update TTS if enabled/disabled
981
+ if speech_enabled and not self.agent.speech_enabled:
982
+ try:
983
+ self.agent.tts_engine = pyttsx3.init()
984
+ self.agent.setup_tts()
985
+ self.agent.speech_enabled = True
986
+ except Exception as e:
987
+ console.log(f"[yellow]TTS re-enable failed: {e}[/yellow]")
988
+ elif not speech_enabled and self.agent.speech_enabled:
989
+ self.agent.speech_enabled = False
990
+
991
+ return f"βœ… Config updated: {model_key}, T={temp_val}, MaxTok={max_tok_val}, Speech={speech_enabled}"
992
+
993
  def get_artifacts():
994
+ return self.agent.get_canvas_summary("default") # Assuming single conversation for UI
995
+
 
996
  def clear_canvas():
997
+ self.agent.clear_canvas("default")
 
998
  return [], "βœ… Canvas cleared"
999
+
1000
+ def clear_chat():
1001
+ self.agent.clear_conversation("default")
1002
+ return [], "βœ… Chat cleared"
1003
+
1004
+ def new_session():
1005
+ self.agent.clear_conversation("default")
1006
+ self.agent.clear_canvas("default")
1007
+ return [], "# New session started\nprint('Ready!')", "πŸ†• New session started", []
1008
+
1009
+ async def process_message(message, history, speech_enabled):
1010
  if not message.strip():
1011
+ return "", history, "Please enter a message", self.agent.get_canvas_summary("default")
 
 
1012
  history = history + [[message, None]]
 
1013
  try:
1014
+ # For simplicity, use the basic chat method here. Canvas integration can be added if needed.
1015
+ response = await self.agent.chat([{"role": "user", "content": message}])
 
 
 
 
 
 
1016
  history[-1][1] = response
1017
+ if speech_enabled and self.agent.speech_enabled:
1018
+ self.agent.speak(response)
1019
+ artifacts = self.agent.get_canvas_summary("default")
 
1020
  status = f"βœ… Response received. Canvas artifacts: {len(artifacts)}"
1021
  return "", history, status, artifacts
 
1022
  except Exception as e:
1023
  error_msg = f"❌ Error: {str(e)}"
1024
  history[-1][1] = error_msg
1025
+ return "", history, error_msg, self.agent.get_canvas_summary("default")
1026
+
1027
+ # Connect events
1028
+ update_config_btn.click(
1029
+ update_agent_config,
1030
+ inputs=[model_dropdown, temperature, max_tokens, speech_toggle],
1031
+ outputs=status_display
1032
+ )
1033
+ refresh_artifacts_btn.click(get_artifacts, outputs=artifact_display)
1034
+ clear_canvas_btn.click(clear_canvas, outputs=[artifact_display, status_display])
1035
+ clear_chat_btn.click(clear_chat, outputs=[chatbot, status_display])
1036
+ new_session_btn.click(new_session, outputs=[chatbot, code_editor, status_display, artifact_display])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1037
  send_btn.click(
1038
  process_message,
1039
+ inputs=[message_input, chatbot, speech_toggle],
1040
  outputs=[message_input, chatbot, status_display, artifact_display]
1041
  )
 
1042
  message_input.submit(
1043
  process_message,
1044
+ inputs=[message_input, chatbot, speech_toggle],
1045
  outputs=[message_input, chatbot, status_display, artifact_display]
1046
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1047
  interface.load(get_artifacts, outputs=artifact_display)
1048
+
1049
  return interface
1050
 
1051
+ # --- Main Application ---
1052
+ def main():
1053
+ console.log("[bold blue]πŸš€ Starting LCARS Terminal...[/bold blue]")
1054
+ is_space = os.getenv('SPACE_ID') is not None
1055
+ if is_space:
1056
+ console.log("[green]🌐 Detected HuggingFace Space[/green]")
1057
+ else:
1058
+ console.log("[blue]πŸ’» Running locally[/blue]")
1059
 
1060
+ interface = LcarsInterface()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1061
  demo = interface.create_interface()
1062
+ demo.launch(share=is_space)
1063
+
1064
+ if __name__ == "__main__":
1065
+ main()