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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +291 -381
app.py CHANGED
@@ -6,44 +6,57 @@ import os
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
@@ -53,11 +66,7 @@ class CanvasArtifact:
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:
@@ -67,7 +76,6 @@ class LLMMessage:
67
  conversation_id: str = None
68
  timestamp: float = None
69
  metadata: Dict[str, Any] = None
70
-
71
  def __post_init__(self):
72
  if self.message_id is None:
73
  self.message_id = str(uuid.uuid4())
@@ -81,7 +89,6 @@ class LLMRequest:
81
  message: LLMMessage
82
  response_event: str = None
83
  callback: Callable = None
84
-
85
  def __post_init__(self):
86
  if self.response_event is None:
87
  self.response_event = f"llm_response_{self.message.message_id}"
@@ -93,21 +100,18 @@ class LLMResponse:
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:
104
  self._handlers[event].append(handler)
105
-
106
  def unregister(self, event: str, handler: Callable):
107
  with self._lock:
108
  if event in self._handlers and handler in self._handlers[event]:
109
  self._handlers[event].remove(handler)
110
-
111
  def raise_event(self, event: str, data: Any):
112
  with self._lock:
113
  handlers = self._handlers[event][:]
@@ -118,7 +122,6 @@ class EventManager:
118
  console.log(f"Error in event handler for {event}: {e}", style="bold red")
119
 
120
  EVENT_MANAGER = EventManager()
121
-
122
  def RegisterEvent(event: str, handler: Callable):
123
  EVENT_MANAGER.register(event, handler)
124
 
@@ -129,9 +132,9 @@ def UnregisterEvent(event: str, handler: Callable):
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,9 +145,9 @@ class LLMAgent:
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,25 +157,22 @@ class LLMAgent:
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()
@@ -186,7 +186,6 @@ class LLMAgent:
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
 
@@ -210,19 +209,19 @@ class LLMAgent:
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"""
@@ -240,12 +239,10 @@ class LLMAgent:
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:
@@ -287,10 +284,9 @@ class LLMAgent:
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,7 +294,6 @@ class LLMAgent:
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,7 +303,6 @@ class LLMAgent:
308
  request.message.conversation_id or "default",
309
  response_message
310
  )
311
-
312
  # Create and send response
313
  response = LLMResponse(
314
  message=response_message,
@@ -317,7 +311,6 @@ class LLMAgent:
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()
@@ -352,7 +345,7 @@ class LLMAgent:
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"""
@@ -383,7 +376,6 @@ class LLMAgent:
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,19 +383,16 @@ class LLMAgent:
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)
@@ -424,14 +413,13 @@ class LLMAgent:
424
  # Create future for the response
425
  loop = asyncio.get_event_loop()
426
  response_future = loop.create_future()
427
-
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:
@@ -440,7 +428,6 @@ class LLMAgent:
440
  loop.call_soon_threadsafe(response_future.set_result, error_msg)
441
  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 = ""
@@ -450,7 +437,6 @@ class LLMAgent:
450
  break
451
  if not user_message.strip():
452
  return ""
453
-
454
  # Send message with callback using the queue system
455
  try:
456
  message_id = self.send_message(
@@ -503,51 +489,11 @@ class LLMAgent:
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(
@@ -563,6 +509,45 @@ class LLMAgent:
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()
@@ -582,208 +567,74 @@ class LLMAgent:
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
602
- Args:
603
- messages: List of message dicts with 'role' and 'content'
604
- use_history: Whether to include conversation history
605
- Returns:
606
- str: LLM 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"""
662
- return {
663
- "conversation_id": self.conversation_id,
664
- "total_messages": len(self.history),
665
- "user_messages": len([msg for msg in self.history if msg.get('role') == 'user']),
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
693
- context_text: Plain text context (will be converted to system message)
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 = []
716
- for file_path in file_paths:
717
- try:
718
- if os.path.exists(file_path):
719
- with open(file_path, 'r', encoding='utf-8') as f:
720
- content = f.read()
721
- contexts.append(f"--- {os.path.basename(file_path)} ---\n{content}")
722
- else:
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:
740
- if file_path.endswith(('.py', '.js', '.java', '.cpp', '.c', '.html', '.css')):
741
- code_context += f"\nFile: {file_path}\n```\n"
742
- try:
743
- with open(file_path, 'r') as f:
744
- code_context += f.read()
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
756
- - 'messages': List of message dicts
757
- - 'text': Plain text context
758
- - 'files': List of file paths
759
- - 'urls': List of URLs
760
- - 'code': List of code snippets or files
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"""
@@ -885,10 +736,13 @@ class LcarsInterface:
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%;">
@@ -906,11 +760,40 @@ class LcarsInterface:
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
  )
@@ -924,16 +807,16 @@ class LcarsInterface:
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=""
@@ -945,107 +828,133 @@ class LcarsInterface:
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"
964
  )
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 ---
@@ -1056,10 +965,11 @@ def main():
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()
 
6
  import re
7
  import time
8
  import uuid
9
+ from typing import List, Dict, Any, Optional
10
  from dataclasses import dataclass
11
+ from threading import Lock
12
+ import threading
13
+ import json
14
+ import os
15
  import queue
16
  import traceback
17
+ import uuid
18
+ from typing import Coroutine, Dict, List, Any, Optional, Callable
19
+ from dataclasses import dataclass
20
  from queue import Queue, Empty
21
+ from threading import Lock, Event, Thread
22
+ import threading
23
  from concurrent.futures import ThreadPoolExecutor
24
+ import time
25
  import gradio as gr
26
  from openai import AsyncOpenAI, OpenAI
27
  import pyttsx3
28
  from rich.console import Console
29
 
30
+ BASE_URL="http://localhost:1234/v1"
31
+ BASE_API_KEY="not-needed"
32
+ BASE_CLIENT = AsyncOpenAI(
33
+ base_url=BASE_URL,
34
+ api_key=BASE_API_KEY
35
+ ) # Global state for client
36
+ BASEMODEL_ID = "leroydyer/qwen/qwen3-0.6b-q4_k_m.gguf" # Global state for selected model ID
37
+ CLIENT =OpenAI(
38
+ base_url=BASE_URL,
39
+ api_key=BASE_API_KEY
40
+ ) # Global state for client
41
+ # --- Global Variables (if needed) ---
42
  console = Console()
43
+ # --- Configuration ---
44
+ LOCAL_BASE_URL = "http://localhost:1234/v1"
45
+ LOCAL_API_KEY = "not-needed"
46
+ # HuggingFace Spaces configuration
47
  HF_INFERENCE_URL = "https://api-inference.huggingface.co/models/"
48
  HF_API_KEY = os.getenv("HF_API_KEY", "")
49
+ # Available model options
 
50
  MODEL_OPTIONS = {
51
+ "Local LM Studio": LOCAL_BASE_URL,
52
  "Codellama 7B": "codellama/CodeLlama-7b-hf",
53
+ "Mistral 7B": "mistralai/Mistral-7B-v0.1",
54
  "Llama 2 7B": "meta-llama/Llama-2-7b-chat-hf",
55
  "Falcon 7B": "tiiuae/falcon-7b-instruct"
56
  }
 
57
  DEFAULT_TEMPERATURE = 0.7
58
  DEFAULT_MAX_TOKENS = 5000
59
+ console = Console()
60
 
61
  # --- Canvas Artifact Support ---
62
  @dataclass
 
66
  content: str
67
  title: str
68
  timestamp: float
69
+ metadata: Dict[str, Any]
 
 
 
 
70
 
71
  @dataclass
72
  class LLMMessage:
 
76
  conversation_id: str = None
77
  timestamp: float = None
78
  metadata: Dict[str, Any] = None
 
79
  def __post_init__(self):
80
  if self.message_id is None:
81
  self.message_id = str(uuid.uuid4())
 
89
  message: LLMMessage
90
  response_event: str = None
91
  callback: Callable = None
 
92
  def __post_init__(self):
93
  if self.response_event is None:
94
  self.response_event = f"llm_response_{self.message.message_id}"
 
100
  success: bool = True
101
  error: str = None
102
 
103
+ # --- Event Manager (copied from your original code or imported) ---
104
  class EventManager:
105
  def __init__(self):
106
  self._handlers = defaultdict(list)
107
+ self._lock = threading.Lock()
 
108
  def register(self, event: str, handler: Callable):
109
  with self._lock:
110
  self._handlers[event].append(handler)
 
111
  def unregister(self, event: str, handler: Callable):
112
  with self._lock:
113
  if event in self._handlers and handler in self._handlers[event]:
114
  self._handlers[event].remove(handler)
 
115
  def raise_event(self, event: str, data: Any):
116
  with self._lock:
117
  handlers = self._handlers[event][:]
 
122
  console.log(f"Error in event handler for {event}: {e}", style="bold red")
123
 
124
  EVENT_MANAGER = EventManager()
 
125
  def RegisterEvent(event: str, handler: Callable):
126
  EVENT_MANAGER.register(event, handler)
127
 
 
132
  EVENT_MANAGER.unregister(event, handler)
133
 
134
  class LLMAgent:
135
+ """Main Agent Driver !
136
+ Agent For Multiple messages at once ,
137
+ has a message queing service as well as agenerator method for easy intergration with console
138
  applications as well as ui !"""
139
  def __init__(
140
  self,
 
145
  timeout: int = 30000,
146
  max_tokens: int = 5000,
147
  temperature: float = 0.3,
148
+ base_url: str = "http://localhost:1234/v1",
149
+ api_key: str = "not-needed",
150
+ generate_fn: Callable[[List[Dict[str, str]]], Coroutine[Any, Any, str]] = None
151
  ):
152
  self.model_id = model_id
153
  self.system_prompt = system_prompt or "You are a helpful AI assistant."
 
157
  self.is_running = False
158
  self._stop_event = Event()
159
  self.processing_thread = None
 
160
  # Conversation tracking
161
  self.conversations: Dict[str, List[LLMMessage]] = {}
162
  self.max_history_length = 20
163
+ self._generate = generate_fn or self._default_generate
 
164
  self.api_key = api_key
165
+ self.base_url = base_url
166
  self.max_tokens = max_tokens
167
  self.temperature = temperature
168
+ self.async_client = self.CreateClient(base_url, api_key)
 
 
169
  # Active requests waiting for responses
170
  self.pending_requests: Dict[str, LLMRequest] = {}
171
  self.pending_requests_lock = Lock()
172
 
173
+ # Canvas Artifacts - NEW
174
+ self.canvas_artifacts: Dict[str, List[CanvasArtifact]] = {}
175
+ self.canvas_lock = Lock()
176
 
177
  # Register internal event handlers
178
  self._register_event_handlers()
 
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
  # Start the processing thread immediately
190
  self.start()
191
 
 
209
  clean_text = re.sub(r'`.*?`', '', clean_text)
210
  clean_text = clean_text.strip()
211
  if clean_text:
212
+ self.tts_engine.say(clean_text)
213
  self.tts_engine.runAndWait()
214
  else:
215
+ self.tts_engine.say(text)
216
+ self.tts_engine.runAndWait()
217
  except Exception as e:
218
  console.log(f"[red]TTS Error: {e}[/red]")
219
+ thread = threading.Thread(target=_speak, daemon=True)
220
  thread.start()
221
 
222
+ async def _default_generate(self, messages: List[Dict[str, str]]) -> str:
223
+ """Default generate function if none provided"""
224
+ return await self.openai_generate(messages)
225
 
226
  def _register_event_handlers(self):
227
  """Register internal event handlers for response routing"""
 
239
  else:
240
  console.log(f"No pending request found for: {response.request_id}", style="yellow")
241
  return
 
242
  # Raise the specific response event
243
  if request.response_event:
244
  console.log(f"[bold green]Raising event: {request.response_event}[/bold green]")
245
  RaiseEvent(request.response_event, response)
 
246
  # Call callback if provided
247
  if request.callback:
248
  try:
 
284
  request.message
285
  )
286
  console.log(f"Calling LLM with {len(messages)} messages")
287
+ # Call LLM - Use sync call for thread compatibility
288
+ response_content = self._call_llm_sync(messages)
289
+ console.log(f"[bold green]LLM response received: {response_content}...[/bold green]")
 
290
  # Create response message
291
  response_message = LLMMessage(
292
  role="assistant",
 
294
  conversation_id=request.message.conversation_id,
295
  metadata={"request_id": request.message.message_id}
296
  )
 
297
  # Update conversation history
298
  self._add_to_conversation_history(
299
  request.message.conversation_id or "default",
 
303
  request.message.conversation_id or "default",
304
  response_message
305
  )
 
306
  # Create and send response
307
  response = LLMResponse(
308
  message=response_message,
 
311
  )
312
  console.log(f"[bold blue]Sending internal response for: {request.message.message_id}[/bold blue]")
313
  RaiseEvent("llm_internal_response", response)
 
314
  except Exception as e:
315
  console.log(f"[bold red]Error processing LLM request: {e}[/bold red]")
316
  traceback.print_exc()
 
345
  console.log(f"LLM call attempt {attempt + 1} failed: {e}")
346
  if attempt == self.max_retries - 1:
347
  raise e
348
+ # Wait before retry
349
 
350
  def _process_queue(self):
351
  """Main queue processing loop"""
 
376
  """Send a message to the LLM and get response via events"""
377
  if not self.is_running:
378
  raise RuntimeError("LLM Agent is not running. Call start() first.")
 
379
  # Create message
380
  message = LLMMessage(
381
  role=role,
 
383
  conversation_id=conversation_id,
384
  metadata=metadata or {}
385
  )
 
386
  # Create request
387
  request = LLMRequest(
388
  message=message,
389
  response_event=response_event,
390
  callback=callback
391
  )
 
392
  # Store in pending requests BEFORE adding to queue
393
  with self.pending_requests_lock:
394
  self.pending_requests[message.message_id] = request
395
  console.log(f"Added to pending requests: {message.message_id}")
 
396
  # Add to queue
397
  try:
398
  self.request_queue.put(request, timeout=5.0)
 
413
  # Create future for the response
414
  loop = asyncio.get_event_loop()
415
  response_future = loop.create_future()
 
416
  def chat_callback(response: LLMResponse):
417
  """Callback when LLM responds - thread-safe"""
418
  console.log(f"[bold yellow]βœ“ CHAT CALLBACK TRIGGERED![/bold yellow]")
419
  if not response_future.done():
420
  if response.success:
421
  content = response.message.content
422
+ console.log(f"Callback received content: {content}...")
423
  # Schedule setting the future result on the main event loop
424
  loop.call_soon_threadsafe(response_future.set_result, content)
425
  else:
 
428
  loop.call_soon_threadsafe(response_future.set_result, error_msg)
429
  else:
430
  console.log(f"[bold red]Future already done, ignoring callback[/bold red]")
 
431
  console.log(f"Sending message to LLM agent...")
432
  # Extract the actual message content from the messages list
433
  user_message = ""
 
437
  break
438
  if not user_message.strip():
439
  return ""
 
440
  # Send message with callback using the queue system
441
  try:
442
  message_id = self.send_message(
 
489
  if conversation_id in self.conversations:
490
  del self.conversations[conversation_id]
491
 
492
+ async def _chat(self, messages: List[Dict[str, str]]) -> str:
493
+ return await self._generate(messages)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
 
495
  @staticmethod
496
+ async def openai_generate(messages: List[Dict[str, str]], max_tokens: int = 8096, temperature: float = 0.4, model: str = BASEMODEL_ID,tools=None) -> str:
497
  """Static method for generating responses using OpenAI API"""
498
  try:
499
  resp = await BASE_CLIENT.chat.completions.create(
 
509
  console.log(f"[bold red]Error in openai_generate: {e}[/bold red]")
510
  return f"[LLM_Agent Error - openai_generate: {str(e)}]"
511
 
512
+ async def _call_(self, messages: List[Dict[str, str]]) -> str:
513
+ """Internal call method using instance client"""
514
+ try:
515
+ resp = await self.async_client.chat.completions.create(
516
+ model=self.model_id,
517
+ messages=messages,
518
+ temperature=self.temperature,
519
+ max_tokens=self.max_tokens
520
+ )
521
+ response_text = resp.choices[0].message.content or ""
522
+ return response_text
523
+ except Exception as e:
524
+ console.log(f"[bold red]Error in _call_: {e}[/bold red]")
525
+ return f"[LLM_Agent Error - _call_: {str(e)}]"
526
+
527
+ @staticmethod
528
+ def CreateClient(base_url: str, api_key: str) -> AsyncOpenAI:
529
+ '''Create async OpenAI Client required for multi tasking'''
530
+ return AsyncOpenAI(
531
+ base_url=base_url,
532
+ api_key=api_key
533
+ )
534
+
535
+ @staticmethod
536
+ async def fetch_available_models(base_url: str, api_key: str) -> List[str]:
537
+ """Fetches available models from the OpenAI API."""
538
+ try:
539
+ async_client = AsyncOpenAI(base_url=base_url, api_key=api_key)
540
+ models = await async_client.models.list()
541
+ model_choices = [model.id for model in models.data]
542
+ return model_choices
543
+ except Exception as e:
544
+ console.log(f"[bold red]LLM_Agent Error fetching models: {e}[/bold red]")
545
+ return ["LLM_Agent Error fetching models"]
546
+
547
+ def get_models(self) -> List[str]:
548
+ """Get available models using instance credentials"""
549
+ return asyncio.run(self.fetch_available_models(self.base_url, self.api_key))
550
+
551
  def get_queue_size(self) -> int:
552
  """Get current queue size"""
553
  return self.request_queue.qsize()
 
567
  "model": self.model_id
568
  }
569
 
570
+ # --- ADDED CANVAS FUNCTIONALITY ---
571
+ def add_canvas_artifact(self, conversation_id: str, artifact_type: str, content: str, title: str = ""):
572
+ """Add an artifact to the canvas for a specific conversation."""
573
+ conv_id = conversation_id or "default"
574
+ with self.canvas_lock:
575
+ if conv_id not in self.canvas_artifacts:
576
+ self.canvas_artifacts[conv_id] = []
577
+ artifact = CanvasArtifact(
578
+ id=str(uuid.uuid4()),
579
+ type=artifact_type,
580
+ content=content,
581
+ title=title,
582
+ timestamp=time.time(),
583
+ metadata={}
584
+ )
585
+ self.canvas_artifacts[conv_id].append(artifact)
586
+ console.log(f"[green]Added {artifact_type} artifact to canvas '{conv_id}'[/green]")
587
+
588
+ def get_canvas_summary(self, conversation_id: str) -> List[Dict]:
589
+ """Get a summary of artifacts on the canvas for JSON display."""
590
+ conv_id = conversation_id or "default"
591
+ with self.canvas_lock:
592
+ artifacts = self.canvas_artifacts.get(conv_id, [])
593
+ # Convert artifacts to dictionaries for JSON serialization
594
+ return [
595
+ {
596
+ "id": art.id,
597
+ "type": art.type,
598
+ "title": art.title,
599
+ "timestamp": art.timestamp,
600
+ "content_preview": art.content[:100] + "..." if len(art.content) > 100 else art.content
601
+ }
602
+ for art in artifacts
603
+ ]
604
+
605
+ def clear_canvas(self, conversation_id: str):
606
+ """Clear all artifacts from the canvas for a specific conversation."""
607
+ conv_id = conversation_id or "default"
608
+ with self.canvas_lock:
609
+ if conv_id in self.canvas_artifacts:
610
+ self.canvas_artifacts[conv_id].clear()
611
+ console.log(f"[yellow]Cleared canvas artifacts for '{conv_id}'[/yellow]")
612
+
613
+ async def chat_with_canvas(self, message: str, conversation_id: str, include_canvas: bool = False):
614
+ """Chat method that can optionally include canvas context."""
615
+ messages = [{"role": "user", "content": message}]
616
+
617
+ if include_canvas:
618
+ artifacts = self.get_canvas_summary(conversation_id)
619
+ if artifacts:
620
+ canvas_context = "Current Canvas Context:\\n" + "\\n".join([
621
+ f"- [{art['type'].upper()}] {art['title'] or 'Untitled'}: {art['content_preview']}"
622
+ for art in artifacts
623
+ ])
624
+ messages.insert(0, {"role": "system", "content": canvas_context})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
 
626
+ return await self.chat(messages)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
628
 
629
+ console = Console()
630
 
631
  # --- LCARS Styled Gradio Interface ---
632
  class LcarsInterface:
633
  def __init__(self):
634
+ # Start with HuggingFace by default for Spaces
635
+ self.use_huggingface = True
636
  self.agent = LLMAgent(generate_fn=LLMAgent.openai_generate)
637
+ self.current_conversation = "default"
638
 
639
  def create_interface(self):
640
  """Create the full LCARS-styled interface"""
 
736
  100% { opacity: 1; }
737
  }
738
  """
 
739
  with gr.Blocks(css=lcars_css, theme=gr.themes.Default(), title="LCARS Terminal") as interface:
740
  with gr.Column(elem_classes="lcars-container"):
741
  # Header
742
+ with gr.Sidebar():
743
+ gr.LoginButton()
744
+
745
+
746
  with gr.Row(elem_classes="lcars-header"):
747
  gr.Markdown("""
748
  <div style="text-align: center; width: 100%;">
 
760
  with gr.Column(scale=1):
761
  # Configuration Panel
762
  with gr.Column(elem_classes="lcars-panel"):
763
+ # Connection Type Selector
764
+ with gr.Row(elem_classes="lcars-panel"):
765
+
766
+ connection_type = gr.Radio(label = "### 🌐 CONNECTION TYPE",
767
+ choices=["HuggingFace Inference", "Local LM Studio"],
768
+ value="HuggingFace Inference",
769
+ elem_classes="lcars-input"
770
+ )
771
  gr.Markdown("### πŸ”§ CONFIGURATION")
772
+ # Connection-specific settings
773
+ with gr.Row(visible=False) as local_settings:
774
+ base_url = gr.Textbox(
775
+ value=LOCAL_BASE_URL,
776
+ label="LM Studio URL",
777
+ elem_classes="lcars-input"
778
+ )
779
+ api_key = gr.Textbox(
780
+ value=LOCAL_API_KEY,
781
+ label="API Key",
782
+ type="password",
783
+ elem_classes="lcars-input"
784
+ )
785
+ with gr.Row(visible=True) as hf_settings:
786
+ hf_api_key = gr.Textbox(
787
+ value=HF_API_KEY,
788
+ label="HuggingFace API Key",
789
+ type="password",
790
+ elem_classes="lcars-input",
791
+ placeholder="Get from https://huggingface.co/settings/tokens"
792
+ )
793
  with gr.Row():
794
  model_dropdown = gr.Dropdown(
795
+ choices=list(MODEL_OPTIONS.keys())[1:],
796
+ value=list(MODEL_OPTIONS.keys())[1],
797
  label="AI Model",
798
  elem_classes="lcars-input"
799
  )
 
807
  # Canvas Artifacts
808
  with gr.Column(elem_classes="lcars-panel"):
809
  gr.Markdown("### 🎨 CANVAS ARTIFACTS")
810
+ artifact_display = gr.JSON(label="")
811
  with gr.Row():
812
  refresh_artifacts_btn = gr.Button("πŸ”„ Refresh", elem_classes="lcars-button")
813
  clear_canvas_btn = gr.Button("πŸ—‘οΈ Clear Canvas", elem_classes="lcars-button")
814
  # Main Content Area
815
  with gr.Column(scale=2):
816
  # Code Canvas
817
+ with gr.Accordion("πŸ’» COLLABORATIVE CODE CANVAS", open=False):
818
  code_editor = gr.Code(
819
+ value="# Welcome to LCARS Collaborative Canvas\\nprint('Hello, Starfleet!')",
820
  language="python",
821
  lines=15,
822
  label=""
 
828
  # Chat Interface
829
  with gr.Column(elem_classes="lcars-panel"):
830
  gr.Markdown("### πŸ’¬ MISSION LOG")
831
+ chatbot = gr.Chatbot(label="", height=300)
832
  with gr.Row():
833
  message_input = gr.Textbox(
834
  placeholder="Enter your command or query...",
835
  show_label=False,
836
  lines=2,
837
+ scale=4
838
  )
839
+ send_btn = gr.Button("πŸš€ SEND", elem_classes="lcars-button", scale=1)
840
  # Status
841
  with gr.Row():
842
  status_display = gr.Textbox(
843
  value="LCARS terminal operational. Awaiting commands.",
844
  label="Status",
845
+ max_lines=2
 
846
  )
847
  with gr.Column(scale=0):
848
  clear_chat_btn = gr.Button("πŸ—‘οΈ Clear Chat", elem_classes="lcars-button")
849
  new_session_btn = gr.Button("πŸ†• New Session", elem_classes="lcars-button")
850
 
851
  # === EVENT HANDLERS ===
852
+ def switch_connection(connection_type):
853
+ if connection_type == "Local LM Studio":
854
+ return [
855
+ gr.update(visible=True),
856
+ gr.update(visible=False),
857
+ gr.update(choices=list(MODEL_OPTIONS.keys())[1:], value=list(MODEL_OPTIONS.keys())[1])
858
+ ]
859
+ else:
860
+ return [
861
+ gr.update(visible=False),
862
+ gr.update(visible=True),
863
+ gr.update(choices=list(MODEL_OPTIONS.keys())[1:], value=list(MODEL_OPTIONS.keys())[1])
864
+ ]
865
+
866
+ async def fetch_models_updated(connection_type, base_url_val, api_key_val, hf_api_key_val):
867
+ # Fixed: Removed the 'use_huggingface' parameter
868
+ if connection_type == "Local LM Studio":
869
+ models = await LLMAgent.fetch_available_models(
870
+ base_url_val, api_key_val
871
+ )
872
+ else:
873
+ # Using the HF_INFERENCE_URL and the key
874
+ models = await LLMAgent.fetch_available_models(
875
+ HF_INFERENCE_URL, hf_api_key_val
876
+ )
877
+ if models:
878
+ return gr.update(choices=models, value=models[0])
879
+ return gr.update(choices=["No models found"])
880
+
881
+ def update_agent_connection(connection_type, model_id, base_url_val, api_key_val, hf_api_key_val):
882
+ # Fixed: Removed the 'use_huggingface' parameter from the constructor
883
+ use_hf = connection_type == "HuggingFace Inference"
884
+ if use_hf:
885
+ # Use the model_id directly (it's the model name like 'codellama/CodeLlama-7b-hf')
886
+ self.agent = LLMAgent(
887
+ model_id=model_id,
888
+ base_url=HF_INFERENCE_URL,
889
+ api_key=hf_api_key_val,
890
+ generate_fn=LLMAgent.openai_generate
891
+ )
892
+ return f"βœ… Switched to HuggingFace: {model_id}"
893
+ else:
894
+ self.agent = LLMAgent(
895
+ model_id=model_id,
896
+ base_url=base_url_val,
897
+ api_key=api_key_val,
898
+ generate_fn=LLMAgent.openai_generate
899
+ )
900
+ return f"βœ… Switched to Local: {base_url_val}"
901
 
902
  async def process_message(message, history, speech_enabled):
903
  if not message.strip():
904
+ return "", history, "Please enter a message"
905
  history = history + [[message, None]]
906
  try:
907
+ # Fixed: Uses the new chat_with_canvas method which includes canvas context
908
+ response = await self.agent.chat_with_canvas(
909
+ message, self.current_conversation, include_canvas=True
910
+ )
911
  history[-1][1] = response
912
  if speech_enabled and self.agent.speech_enabled:
913
  self.agent.speak(response)
914
+ artifacts = self.agent.get_canvas_summary(self.current_conversation)
915
  status = f"βœ… Response received. Canvas artifacts: {len(artifacts)}"
916
  return "", history, status, artifacts
917
  except Exception as e:
918
  error_msg = f"❌ Error: {str(e)}"
919
  history[-1][1] = error_msg
920
+ return "", history, error_msg, self.agent.get_canvas_summary(self.current_conversation)
921
+
922
+ def get_artifacts():
923
+ return self.agent.get_canvas_summary(self.current_conversation)
924
+
925
+ def clear_canvas():
926
+ self.agent.clear_canvas(self.current_conversation)
927
+ return [], "βœ… Canvas cleared"
928
+
929
+ def clear_chat():
930
+ self.agent.clear_conversation(self.current_conversation)
931
+ return [], "βœ… Chat cleared"
932
+
933
+ def new_session():
934
+ self.agent.clear_conversation(self.current_conversation)
935
+ self.agent.clear_canvas(self.current_conversation)
936
+ return [], "# New session started\\nprint('Ready!')", "πŸ†• New session started", []
937
 
938
  # Connect events
939
+ connection_type.change(switch_connection, inputs=connection_type,
940
+ outputs=[local_settings, hf_settings, model_dropdown])
941
+ fetch_models_btn.click(fetch_models_updated,
942
+ inputs=[connection_type, base_url, api_key, hf_api_key],
943
+ outputs=model_dropdown)
944
+ update_config_btn.click(update_agent_connection,
945
+ inputs=[connection_type, model_dropdown, base_url, api_key, hf_api_key],
946
+ outputs=status_display)
947
+ send_btn.click(process_message,
948
+ inputs=[message_input, chatbot, speech_toggle],
949
+ outputs=[message_input, chatbot, status_display, artifact_display])
950
+ message_input.submit(process_message,
951
+ inputs=[message_input, chatbot, speech_toggle],
952
+ outputs=[message_input, chatbot, status_display, artifact_display])
953
  refresh_artifacts_btn.click(get_artifacts, outputs=artifact_display)
954
  clear_canvas_btn.click(clear_canvas, outputs=[artifact_display, status_display])
955
  clear_chat_btn.click(clear_chat, outputs=[chatbot, status_display])
956
  new_session_btn.click(new_session, outputs=[chatbot, code_editor, status_display, artifact_display])
 
 
 
 
 
 
 
 
 
 
957
  interface.load(get_artifacts, outputs=artifact_display)
 
958
  return interface
959
 
960
  # --- Main Application ---
 
965
  console.log("[green]🌐 Detected HuggingFace Space[/green]")
966
  else:
967
  console.log("[blue]πŸ’» Running locally[/blue]")
 
968
  interface = LcarsInterface()
969
  demo = interface.create_interface()
970
+ demo.launch(
971
+ share=is_space
972
+ )
973
 
974
  if __name__ == "__main__":
975
  main()