LeroyDyer commited on
Commit
6435a84
Β·
verified Β·
1 Parent(s): 6708fc7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +2048 -0
app.py ADDED
@@ -0,0 +1,2048 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # File: lcars_enhanced_interface.py
2
+
3
+ import asyncio
4
+ import json
5
+ import os
6
+ import time
7
+ import uuid
8
+ from typing import Dict, List, Any, Optional
9
+ from dataclasses import dataclass
10
+ import threading
11
+ import pyttsx3
12
+ import re
13
+ from pathlib import Path
14
+
15
+ import gradio as gr
16
+ from rich.console import Console
17
+ from openai import OpenAI, AsyncOpenAI
18
+ import asyncio
19
+ from collections import defaultdict
20
+ import json
21
+ import os
22
+ import queue
23
+ import traceback
24
+ import uuid
25
+ from typing import Dict, List, Any, Optional, Callable, Coroutine
26
+ from dataclasses import dataclass
27
+ from queue import Queue, Empty
28
+ from threading import Lock, Event, Thread
29
+ import threading
30
+ from concurrent.futures import ThreadPoolExecutor
31
+ import time
32
+ from openai import OpenAI, AsyncOpenAI
33
+ from rich.console import Console
34
+ import gradio as gr
35
+ import pyttsx3
36
+ import re
37
+ from pathlib import Path
38
+ #############################################################
39
+ BASE_URL="http://localhost:1234/v1"
40
+ BASE_API_KEY="not-needed"
41
+ BASE_CLIENT = AsyncOpenAI(
42
+ base_url=BASE_URL,
43
+ api_key=BASE_API_KEY
44
+ ) # Global state for client
45
+ BASEMODEL_ID = "leroydyer/qwen/qwen3-0.6b-q4_k_m.gguf" # Global state for selected model ID
46
+ CLIENT =OpenAI(
47
+ base_url=BASE_URL,
48
+ api_key=BASE_API_KEY)
49
+ # --- Configuration ---
50
+ DEFAULT_BASE_URL = "http://localhost:1234/v1"
51
+ DEFAULT_API_KEY = "not-needed"
52
+ DEFAULT_MODEL_ID = "leroydyer/qwen/qwen3-0.6b-q4_k_m.gguf"
53
+ DEFAULT_TEMPERATURE = 0.3
54
+ DEFAULT_MAX_TOKENS = 5000
55
+
56
+ # Add this configuration section at the top
57
+ import os
58
+
59
+ # Configuration that works for both local and HuggingFace Spaces
60
+ LOCAL_BASE_URL = "http://localhost:1234/v1"
61
+ LOCAL_API_KEY = "not-needed"
62
+
63
+ # HuggingFace Spaces configuration - using free inference endpoints
64
+ HF_INFERENCE_URL = "https://api-inference.huggingface.co/models/"
65
+ HF_API_KEY = os.getenv("HF_API_KEY", "") # Set this in Spaces secrets
66
+
67
+ # Available model options
68
+ MODEL_OPTIONS = {
69
+ "Local LM Studio": LOCAL_BASE_URL,
70
+ "Codellama 7B": "codellama/CodeLlama-7b-hf",
71
+ "Mistral 7B": "mistralai/Mistral-7B-v0.1",
72
+ "Llama 2 7B": "meta-llama/Llama-2-7b-chat-hf",
73
+ "Falcon 7B": "tiiuae/falcon-7b-instruct"
74
+ }
75
+ console = Console()
76
+ class EventManager:
77
+ def __init__(self):
78
+ self._handlers = defaultdict(list)
79
+ self._lock = threading.Lock()
80
+ def register(self, event: str, handler: Callable):
81
+ with self._lock:
82
+ self._handlers[event].append(handler)
83
+ def unregister(self, event: str, handler: Callable):
84
+ with self._lock:
85
+ if event in self._handlers and handler in self._handlers[event]:
86
+ self._handlers[event].remove(handler)
87
+ def raise_event(self, event: str, data: Any):
88
+ with self._lock:
89
+ handlers = self._handlers[event][:]
90
+ for handler in handlers:
91
+ try:
92
+ handler(data)
93
+ except Exception as e:
94
+ console.log(f"Error in event handler for {event}: {e}", style="bold red")
95
+
96
+ EVENT_MANAGER = EventManager()
97
+ def RegisterEvent(event: str, handler: Callable):
98
+ EVENT_MANAGER.register(event, handler)
99
+ def RaiseEvent(event: str, data: Any):
100
+ EVENT_MANAGER.raise_event(event, data)
101
+ def UnregisterEvent(event: str, handler: Callable):
102
+ EVENT_MANAGER.unregister(event, handler)
103
+ @dataclass
104
+ class LLMMessage:
105
+ role: str
106
+ content: str
107
+ message_id: str = None
108
+ conversation_id: str = None
109
+ timestamp: float = None
110
+ metadata: Dict[str, Any] = None
111
+ def __post_init__(self):
112
+ if self.message_id is None:
113
+ self.message_id = str(uuid.uuid4())
114
+ if self.timestamp is None:
115
+ self.timestamp = time.time()
116
+ if self.metadata is None:
117
+ self.metadata = {}
118
+
119
+ @dataclass
120
+ class LLMRequest:
121
+ message: LLMMessage
122
+ response_event: str = None
123
+ callback: Callable = None
124
+ def __post_init__(self):
125
+ if self.response_event is None:
126
+ self.response_event = f"llm_response_{self.message.message_id}"
127
+
128
+ @dataclass
129
+ class LLMResponse:
130
+ message: LLMMessage
131
+ request_id: str
132
+ success: bool = True
133
+ error: str = None
134
+
135
+ class LLMAgent:
136
+ """Main Agent Driver !
137
+ Agent For Multiple messages at once ,
138
+ has a message queing service as well as agenerator method for easy intergration with console
139
+ applications as well as ui !"""
140
+ def __init__(
141
+ self,
142
+ model_id: str = DEFAULT_MODEL_ID,
143
+ system_prompt: str = None,
144
+ max_queue_size: int = 1000,
145
+ max_retries: int = 3,
146
+ timeout: int = 30000,
147
+ max_tokens: int = 5000,
148
+ temperature: float = 0.3,
149
+ base_url: str = "http://localhost:1234/v1",
150
+ api_key: str = "not-needed",
151
+ generate_fn: Callable[[List[Dict[str, str]]], Coroutine[Any, Any, str]] = None
152
+ ):
153
+ self.model_id = model_id
154
+ self.system_prompt = system_prompt or "You are a helpful AI assistant."
155
+ self.request_queue = Queue(maxsize=max_queue_size)
156
+ self.max_retries = max_retries
157
+ self.timeout = timeout
158
+ self.is_running = False
159
+ self._stop_event = Event()
160
+ self.processing_thread = None
161
+ # Conversation tracking
162
+ self.conversations: Dict[str, List[LLMMessage]] = {}
163
+ self.max_history_length = 20
164
+ self._generate = generate_fn or self._default_generate
165
+ self.api_key = api_key
166
+ self.base_url = base_url
167
+ self.max_tokens = max_tokens
168
+ self.temperature = temperature
169
+ self.async_client = self.CreateClient(base_url, api_key)
170
+ # Active requests waiting for responses
171
+ self.pending_requests: Dict[str, LLMRequest] = {}
172
+ self.pending_requests_lock = Lock()
173
+ # Register internal event handlers
174
+ self._register_event_handlers()
175
+ # Start the processing thread immediately
176
+ self.start()
177
+
178
+ async def _default_generate(self, messages: List[Dict[str, str]]) -> str:
179
+ """Default generate function if none provided"""
180
+ return await self.openai_generate(messages)
181
+
182
+ def _register_event_handlers(self):
183
+ """Register internal event handlers for response routing"""
184
+ RegisterEvent("llm_internal_response", self._handle_internal_response)
185
+
186
+ def _handle_internal_response(self, response: LLMResponse):
187
+ """Route responses to the appropriate request handlers"""
188
+ console.log(f"[bold cyan]Handling internal response for: {response.request_id}[/bold cyan]")
189
+ request = None
190
+ with self.pending_requests_lock:
191
+ if response.request_id in self.pending_requests:
192
+ request = self.pending_requests[response.request_id]
193
+ del self.pending_requests[response.request_id]
194
+ console.log(f"Found pending request for: {response.request_id}")
195
+ else:
196
+ console.log(f"No pending request found for: {response.request_id}", style="yellow")
197
+ return
198
+ # Raise the specific response event
199
+ if request.response_event:
200
+ console.log(f"[bold green]Raising event: {request.response_event}[/bold green]")
201
+ RaiseEvent(request.response_event, response)
202
+ # Call callback if provided
203
+ if request.callback:
204
+ try:
205
+ console.log(f"[bold yellow]Calling callback for: {response.request_id}[/bold yellow]")
206
+ request.callback(response)
207
+ except Exception as e:
208
+ console.log(f"Error in callback: {e}", style="bold red")
209
+
210
+ def _add_to_conversation_history(self, conversation_id: str, message: LLMMessage):
211
+ """Add message to conversation history"""
212
+ if conversation_id not in self.conversations:
213
+ self.conversations[conversation_id] = []
214
+ self.conversations[conversation_id].append(message)
215
+ # Trim history if too long
216
+ if len(self.conversations[conversation_id]) > self.max_history_length * 2:
217
+ self.conversations[conversation_id] = self.conversations[conversation_id][-(self.max_history_length * 2):]
218
+
219
+ def _build_messages_from_conversation(self, conversation_id: str, new_message: LLMMessage) -> List[Dict[str, str]]:
220
+ """Build message list from conversation history"""
221
+ messages = []
222
+ # Add system prompt
223
+ if self.system_prompt:
224
+ messages.append({"role": "system", "content": self.system_prompt})
225
+ # Add conversation history
226
+ if conversation_id in self.conversations:
227
+ for msg in self.conversations[conversation_id][-self.max_history_length:]:
228
+ messages.append({"role": msg.role, "content": msg.content})
229
+ # Add the new message
230
+ messages.append({"role": new_message.role, "content": new_message.content})
231
+ return messages
232
+
233
+ def _process_llm_request(self, request: LLMRequest):
234
+ """Process a single LLM request"""
235
+ console.log(f"[bold green]Processing LLM request: {request.message.message_id}[/bold green]")
236
+ try:
237
+ # Build messages for LLM
238
+ messages = self._build_messages_from_conversation(
239
+ request.message.conversation_id or "default",
240
+ request.message
241
+ )
242
+ console.log(f"Calling LLM with {len(messages)} messages")
243
+ # Call LLM - Use sync call for thread compatibility
244
+ response_content = self._call_llm_sync(messages)
245
+ console.log(f"[bold green]LLM response received: {response_content}...[/bold green]")
246
+ # Create response message
247
+ response_message = LLMMessage(
248
+ role="assistant",
249
+ content=response_content,
250
+ conversation_id=request.message.conversation_id,
251
+ metadata={"request_id": request.message.message_id}
252
+ )
253
+ # Update conversation history
254
+ self._add_to_conversation_history(
255
+ request.message.conversation_id or "default",
256
+ request.message
257
+ )
258
+ self._add_to_conversation_history(
259
+ request.message.conversation_id or "default",
260
+ response_message
261
+ )
262
+ # Create and send response
263
+ response = LLMResponse(
264
+ message=response_message,
265
+ request_id=request.message.message_id,
266
+ success=True
267
+ )
268
+ console.log(f"[bold blue]Sending internal response for: {request.message.message_id}[/bold blue]")
269
+ RaiseEvent("llm_internal_response", response)
270
+ except Exception as e:
271
+ console.log(f"[bold red]Error processing LLM request: {e}[/bold red]")
272
+ traceback.print_exc()
273
+ # Create error response
274
+ error_response = LLMResponse(
275
+ message=LLMMessage(
276
+ role="system",
277
+ content=f"Error: {str(e)}",
278
+ conversation_id=request.message.conversation_id
279
+ ),
280
+ request_id=request.message.message_id,
281
+ success=False,
282
+ error=str(e)
283
+ )
284
+ RaiseEvent("llm_internal_response", error_response)
285
+
286
+ def _call_llm_sync(self, messages: List[Dict[str, str]]) -> str:
287
+ """Sync call to the LLM with retry logic"""
288
+ console.log(f"Making LLM call to {self.model_id}")
289
+ for attempt in range(self.max_retries):
290
+ try:
291
+ response = CLIENT.chat.completions.create(
292
+ model=self.model_id,
293
+ messages=messages,
294
+ temperature=self.temperature,
295
+ max_tokens=self.max_tokens
296
+ )
297
+ content = response.choices[0].message.content
298
+ console.log(f"LLM call successful, response length: {len(content)}")
299
+ return content
300
+ except Exception as e:
301
+ console.log(f"LLM call attempt {attempt + 1} failed: {e}")
302
+ if attempt == self.max_retries - 1:
303
+ raise e
304
+ time.sleep(1) # Wait before retry
305
+ def _process_queue(self):
306
+ """Main queue processing loop"""
307
+ console.log("[bold cyan]LLM Agent queue processor started[/bold cyan]")
308
+ while not self._stop_event.is_set():
309
+ try:
310
+ request = self.request_queue.get(timeout=1.0)
311
+ if request:
312
+ console.log(f"Got request from queue: {request.message.message_id}")
313
+ self._process_llm_request(request)
314
+ self.request_queue.task_done()
315
+ except Empty:
316
+ continue
317
+ except Exception as e:
318
+ console.log(f"Error in queue processing: {e}", style="bold red")
319
+ traceback.print_exc()
320
+ console.log("[bold cyan]LLM Agent queue processor stopped[/bold cyan]")
321
+
322
+ def send_message(
323
+ self,
324
+ content: str,
325
+ role: str = "user",
326
+ conversation_id: str = None,
327
+ response_event: str = None,
328
+ callback: Callable = None,
329
+ metadata: Dict = None
330
+ ) -> str:
331
+ """Send a message to the LLM and get response via events"""
332
+ if not self.is_running:
333
+ raise RuntimeError("LLM Agent is not running. Call start() first.")
334
+ # Create message
335
+ message = LLMMessage(
336
+ role=role,
337
+ content=content,
338
+ conversation_id=conversation_id,
339
+ metadata=metadata or {}
340
+ )
341
+ # Create request
342
+ request = LLMRequest(
343
+ message=message,
344
+ response_event=response_event,
345
+ callback=callback
346
+ )
347
+ # Store in pending requests BEFORE adding to queue
348
+ with self.pending_requests_lock:
349
+ self.pending_requests[message.message_id] = request
350
+ console.log(f"Added to pending requests: {message.message_id}")
351
+ # Add to queue
352
+ try:
353
+ self.request_queue.put(request, timeout=5.0)
354
+ console.log(f"[bold magenta]Message queued: {message.message_id}, Content: {content[:50]}...[/bold magenta]")
355
+ return message.message_id
356
+ except queue.Full:
357
+ console.log(f"[bold red]Queue full, cannot send message[/bold red]")
358
+ with self.pending_requests_lock:
359
+ if message.message_id in self.pending_requests:
360
+ del self.pending_requests[message.message_id]
361
+ raise RuntimeError("LLM Agent queue is full")
362
+
363
+ async def chat(self, messages: List[Dict[str, str]]) -> str:
364
+ """
365
+ Async chat method that sends message via queue and returns response string.
366
+ This is the main method you should use.
367
+ """
368
+ # Create future for the response
369
+ loop = asyncio.get_event_loop()
370
+ response_future = loop.create_future()
371
+ def chat_callback(response: LLMResponse):
372
+ """Callback when LLM responds - thread-safe"""
373
+ console.log(f"[bold yellow]βœ“ CHAT CALLBACK TRIGGERED![/bold yellow]")
374
+ if not response_future.done():
375
+ if response.success:
376
+ content = response.message.content
377
+ console.log(f"Callback received content: {content}...")
378
+ # Schedule setting the future result on the main event loop
379
+ loop.call_soon_threadsafe(response_future.set_result, content)
380
+ else:
381
+ console.log(f"Error in response: {response.error}")
382
+ error_msg = f"❌ Error: {response.error}"
383
+ loop.call_soon_threadsafe(response_future.set_result, error_msg)
384
+ else:
385
+ console.log(f"[bold red]Future already done, ignoring callback[/bold red]")
386
+ console.log(f"Sending message to LLM agent...")
387
+ # Extract the actual message content from the messages list
388
+ user_message = ""
389
+ for msg in messages:
390
+ if msg.get("role") == "user":
391
+ user_message = msg.get("content", "")
392
+ break
393
+ if not user_message.strip():
394
+ return ""
395
+ # Send message with callback using the queue system
396
+ try:
397
+ message_id = self.send_message(
398
+ content=user_message,
399
+ conversation_id="default",
400
+ callback=chat_callback
401
+ )
402
+ console.log(f"Message sent with ID: {message_id}, waiting for response...")
403
+ # Wait for the response and return it
404
+ try:
405
+ response = await asyncio.wait_for(response_future, timeout=self.timeout)
406
+ console.log(f"[bold green]βœ“ Chat complete! Response length: {len(response)}[/bold green]")
407
+ return response
408
+ except asyncio.TimeoutError:
409
+ console.log("[bold red]Response timeout[/bold red]")
410
+ # Clean up the pending request
411
+ with self.pending_requests_lock:
412
+ if message_id in self.pending_requests:
413
+ del self.pending_requests[message_id]
414
+ return "❌ Response timeout - check if LLM server is running"
415
+ except Exception as e:
416
+ console.log(f"[bold red]Error sending message: {e}[/bold red]")
417
+ traceback.print_exc()
418
+ return f"❌ Error sending message: {e}"
419
+
420
+ def start(self):
421
+ """Start the LLM agent"""
422
+ if not self.is_running:
423
+ self.is_running = True
424
+ self._stop_event.clear()
425
+ self.processing_thread = Thread(target=self._process_queue, daemon=True)
426
+ self.processing_thread.start()
427
+ console.log("[bold green]LLM Agent started[/bold green]")
428
+
429
+ def stop(self):
430
+ """Stop the LLM agent"""
431
+ console.log("Stopping LLM Agent...")
432
+ self._stop_event.set()
433
+ if self.processing_thread and self.processing_thread.is_alive():
434
+ self.processing_thread.join(timeout=10)
435
+ self.is_running = False
436
+ console.log("LLM Agent stopped")
437
+
438
+ def get_conversation_history(self, conversation_id: str = "default") -> List[LLMMessage]:
439
+ """Get conversation history"""
440
+ return self.conversations.get(conversation_id, [])[:]
441
+
442
+ def clear_conversation(self, conversation_id: str = "default"):
443
+ """Clear conversation history"""
444
+ if conversation_id in self.conversations:
445
+ del self.conversations[conversation_id]
446
+
447
+ async def _chat(self, messages: List[Dict[str, str]]) -> str:
448
+ return await self._generate(messages)
449
+
450
+ @staticmethod
451
+ async def openai_generate(messages: List[Dict[str, str]], max_tokens: int = 8096, temperature: float = 0.4, model: str = DEFAULT_MODEL_ID,tools=None) -> str:
452
+ """Static method for generating responses using OpenAI API"""
453
+ try:
454
+ resp = await BASE_CLIENT.chat.completions.create(
455
+ model=model,
456
+ messages=messages,
457
+ temperature=temperature,
458
+ max_tokens=max_tokens,
459
+ tools=tools
460
+ )
461
+ response_text = resp.choices[0].message.content or ""
462
+ return response_text
463
+ except Exception as e:
464
+ console.log(f"[bold red]Error in openai_generate: {e}[/bold red]")
465
+ return f"[LLM_Agent Error - openai_generate: {str(e)}]"
466
+
467
+ async def _call_(self, messages: List[Dict[str, str]]) -> str:
468
+ """Internal call method using instance client"""
469
+ try:
470
+ resp = await self.async_client.chat.completions.create(
471
+ model=self.model_id,
472
+ messages=messages,
473
+ temperature=self.temperature,
474
+ max_tokens=self.max_tokens
475
+ )
476
+ response_text = resp.choices[0].message.content or ""
477
+ return response_text
478
+ except Exception as e:
479
+ console.log(f"[bold red]Error in _call_: {e}[/bold red]")
480
+ return f"[LLM_Agent Error - _call_: {str(e)}]"
481
+
482
+ @staticmethod
483
+ def CreateClient(base_url: str, api_key: str) -> AsyncOpenAI:
484
+ '''Create async OpenAI Client required for multi tasking'''
485
+ return AsyncOpenAI(
486
+ base_url=base_url,
487
+ api_key=api_key
488
+ )
489
+
490
+ @staticmethod
491
+ async def fetch_available_models(base_url: str, api_key: str) -> List[str]:
492
+ """Fetches available models from the OpenAI API."""
493
+ try:
494
+ async_client = AsyncOpenAI(base_url=base_url, api_key=api_key)
495
+ models = await async_client.models.list()
496
+ model_choices = [model.id for model in models.data]
497
+ return model_choices
498
+ except Exception as e:
499
+ console.log(f"[bold red]LLM_Agent Error fetching models: {e}[/bold red]")
500
+ return ["LLM_Agent Error fetching models"]
501
+
502
+ def get_models(self) -> List[str]:
503
+ """Get available models using instance credentials"""
504
+ return asyncio.run(self.fetch_available_models(self.base_url, self.api_key))
505
+
506
+ def get_queue_size(self) -> int:
507
+ """Get current queue size"""
508
+ return self.request_queue.qsize()
509
+
510
+ def get_pending_requests_count(self) -> int:
511
+ """Get number of pending requests"""
512
+ with self.pending_requests_lock:
513
+ return len(self.pending_requests)
514
+
515
+ def get_status(self) -> Dict[str, Any]:
516
+ """Get agent status information"""
517
+ return {
518
+ "is_running": self.is_running,
519
+ "queue_size": self.get_queue_size(),
520
+ "pending_requests": self.get_pending_requests_count(),
521
+ "conversations_count": len(self.conversations),
522
+ "model": self.model_id
523
+ }
524
+
525
+
526
+
527
+
528
+ # --- Enhanced LLMAgent with Canvas Support ---
529
+ @dataclass
530
+ class CanvasArtifact:
531
+ id: str
532
+ type: str # 'code', 'diagram', 'text', 'image'
533
+ content: str
534
+ title: str
535
+ timestamp: float
536
+ metadata: Dict[str, Any]
537
+
538
+ class EnhancedLLMAgent:
539
+ def __init__(self, model_id: str = DEFAULT_MODEL_ID, system_prompt: str = None,
540
+ base_url: str = LOCAL_BASE_URL, api_key: str = LOCAL_API_KEY, use_huggingface: bool = False):
541
+ self.model_id = model_id
542
+ self.system_prompt = system_prompt or """You are an advanced AI development assistant operating in a Star Trek LCARS interface.
543
+ You specialize in code generation, analysis, and collaborative development.
544
+ Always provide practical, executable code solutions when appropriate.
545
+ Format code responses clearly with proper markdown code blocks and explain your reasoning."""
546
+ self.base_url = base_url
547
+ self.api_key = api_key
548
+ self.client = OpenAI(base_url=base_url, api_key=api_key)
549
+ self.use_huggingface = use_huggingface
550
+ if use_huggingface:
551
+ # Use HuggingFace Inference API
552
+ self.base_url = "https://api-inference.huggingface.co/models/"
553
+ self.api_key = HF_API_KEY
554
+ self.client = None # We'll use requests for HF
555
+ console.log("[green]πŸš€ Using HuggingFace Inference API[/green]")
556
+ else:
557
+ # Use local LM Studio
558
+ self.base_url = base_url
559
+ self.api_key = api_key
560
+ self.client = OpenAI(base_url=base_url, api_key=api_key)
561
+ console.log(f"[green]πŸš€ Using Local LM Studio: {base_url}[/green]")
562
+
563
+ # Enhanced conversation and canvas management
564
+ self.conversations: Dict[str, List[Dict]] = {}
565
+ self.canvas_artifacts: Dict[str, List[CanvasArtifact]] = {}
566
+ self.max_history_length = 50
567
+
568
+ # Speech synthesis
569
+ try:
570
+ self.tts_engine = pyttsx3.init()
571
+ self.setup_tts()
572
+ self.speech_enabled = True
573
+ except Exception as e:
574
+ console.log(f"[yellow]TTS not available: {e}[/yellow]")
575
+ self.speech_enabled = False
576
+
577
+ console.log("[bold green]πŸš€ Enhanced LLM Agent Initialized[/bold green]")
578
+
579
+ def speak(self, text: str):
580
+ """Convert text to speech in a non-blocking way"""
581
+ if not hasattr(self, 'speech_enabled') or not self.speech_enabled:
582
+ return
583
+
584
+ def _speak():
585
+ try:
586
+ # Clean text for speech (remove markdown, code blocks)
587
+ clean_text = re.sub(r'```.*?```', '', text, flags=re.DOTALL)
588
+ clean_text = re.sub(r'`.*?`', '', clean_text)
589
+ clean_text = clean_text.strip()
590
+ if clean_text:
591
+ self.tts_engine.say(clean_text) # Limit length
592
+ self.tts_engine.runAndWait()
593
+ else:
594
+ self.tts_engine.say(text) # Limit length
595
+ self.tts_engine.runAndWait()
596
+ except Exception as e:
597
+ console.log(f"[red]TTS Error: {e}[/red]")
598
+
599
+ thread = threading.Thread(target=_speak, daemon=True)
600
+ thread.start()
601
+
602
+ def setup_tts(self):
603
+ """Configure text-to-speech engine"""
604
+ try:
605
+ self.tts_engine = pyttsx3.init()
606
+ voices = self.tts_engine.getProperty('voices')
607
+ if voices:
608
+ # Try to find a better voice
609
+ for voice in voices:
610
+ if 'female' in voice.name.lower() or 'zira' in voice.name.lower():
611
+ self.tts_engine.setProperty('voice', voice.id)
612
+ break
613
+ else:
614
+ self.tts_engine.setProperty('voice', voices[0].id)
615
+
616
+ self.tts_engine.setProperty('rate', 180) # Slightly faster
617
+ self.tts_engine.setProperty('volume', 1.0) # Maximum volume
618
+ self.speech_enabled = True
619
+ console.log("[green]TTS engine initialized successfully[/green]")
620
+ except Exception as e:
621
+ console.log(f"[red]TTS initialization failed: {e}[/red]")
622
+ self.speech_enabled = False
623
+ async def _local_inference(self, messages: List[Dict]) -> str:
624
+ """Use local LM Studio"""
625
+ async_client = AsyncOpenAI(base_url=self.base_url, api_key=self.api_key)
626
+ response = await async_client.chat.completions.create(
627
+ model=self.model_id,
628
+ messages=messages,
629
+ temperature=0.7,
630
+ max_tokens=DEFAULT_MAX_TOKENS
631
+ )
632
+ return response.choices[0].message.content
633
+
634
+ async def _hf_inference(self, messages: List[Dict]) -> str:
635
+ """Use HuggingFace Inference API"""
636
+ import requests
637
+ import json
638
+
639
+ # Convert to HF format
640
+ prompt = self._convert_messages_to_prompt(messages)
641
+
642
+ headers = {
643
+ "Authorization": f"Bearer {self.api_key}",
644
+ "Content-Type": "application/json"
645
+ }
646
+
647
+ payload = {
648
+ "inputs": prompt,
649
+ "parameters": {
650
+ "max_new_tokens": DEFAULT_MAX_TOKENS,
651
+ "temperature": 0.7,
652
+ "do_sample": True,
653
+ "return_full_text": False
654
+ }
655
+ }
656
+
657
+ # Use the selected model
658
+ model_url = f"{self.base_url}{self.model_id}"
659
+
660
+ try:
661
+ response = requests.post(model_url, headers=headers, json=payload)
662
+ response.raise_for_status()
663
+ result = response.json()
664
+ return result[0]['generated_text']
665
+ except Exception as e:
666
+ return f"HuggingFace API Error: {str(e)}"
667
+
668
+ def add_artifact_to_canvas(self, conversation_id: str, content: str, artifact_type: str = "code", title: str = None):
669
+ """Add artifacts to the collaborative canvas"""
670
+ if conversation_id not in self.canvas_artifacts:
671
+ self.canvas_artifacts[conversation_id] = []
672
+
673
+ artifact = CanvasArtifact(
674
+ id=str(uuid.uuid4())[:8],
675
+ type=artifact_type,
676
+ content=content,
677
+ title=title or f"{artifact_type}_{len(self.canvas_artifacts[conversation_id]) + 1}",
678
+ timestamp=time.time(),
679
+ metadata={"conversation_id": conversation_id}
680
+ )
681
+
682
+ self.canvas_artifacts[conversation_id].append(artifact)
683
+ console.log(f"[green]Added artifact to canvas: {artifact.title}[/green]")
684
+ return artifact
685
+
686
+ def get_canvas_context(self, conversation_id: str) -> str:
687
+ """Get formatted canvas context for LLM prompts"""
688
+ if conversation_id not in self.canvas_artifacts or not self.canvas_artifacts[conversation_id]:
689
+ return ""
690
+
691
+ context_lines = ["\n=== COLLABORATIVE CANVAS ARTIFACTS ==="]
692
+ for artifact in self.canvas_artifacts[conversation_id][-10:]: # Last 10 artifacts
693
+ context_lines.append(f"\n--- {artifact.title} [{artifact.type.upper()}] ---")
694
+ preview = artifact.content[:500] + "..." if len(artifact.content) > 500 else artifact.content
695
+ context_lines.append(preview)
696
+
697
+ return "\n".join(context_lines) + "\n=================================\n"
698
+
699
+ async def chat_with_canvas(self, message: str, conversation_id: str = "default", include_canvas: bool = True) -> str:
700
+ """Enhanced chat that includes canvas context"""
701
+ if conversation_id not in self.conversations:
702
+ self.conversations[conversation_id] = []
703
+
704
+ # Build messages with system prompt and canvas context
705
+ messages = [{"role": "system", "content": self.system_prompt}]
706
+
707
+ # Include canvas context if requested
708
+ if include_canvas:
709
+ canvas_context = self.get_canvas_context(conversation_id)
710
+ if canvas_context:
711
+ messages.append({"role": "system", "content": f"Current collaborative canvas state:\n{canvas_context}"})
712
+
713
+ # Add conversation history
714
+ for msg in self.conversations[conversation_id][-self.max_history_length:]:
715
+ messages.append(msg)
716
+
717
+ # Add current message
718
+ messages.append({"role": "user", "content": message})
719
+
720
+ try:
721
+ # Use async client for better performance
722
+ async_client = AsyncOpenAI(base_url=self.base_url, api_key=self.api_key)
723
+ response = await async_client.chat.completions.create(
724
+ model=self.model_id,
725
+ messages=messages,
726
+ temperature=0.7,
727
+ max_tokens=DEFAULT_MAX_TOKENS
728
+ )
729
+
730
+ response_text = response.choices[0].message.content
731
+
732
+ # Update conversation history
733
+ self.conversations[conversation_id].extend([
734
+ {"role": "user", "content": message},
735
+ {"role": "assistant", "content": response_text}
736
+ ])
737
+
738
+ # Auto-extract and add code artifacts to canvas
739
+ self._extract_artifacts_to_canvas(response_text, conversation_id)
740
+
741
+ return response_text
742
+
743
+ except Exception as e:
744
+ error_msg = f"Error in chat_with_canvas: {str(e)}"
745
+ console.log(f"[red]{error_msg}[/red]")
746
+ return error_msg
747
+
748
+ def _extract_artifacts_to_canvas(self, response: str, conversation_id: str):
749
+ """Automatically extract code blocks and add to canvas"""
750
+ # Find all code blocks with optional language specification
751
+ code_blocks = re.findall(r'```(?:\w+)?\n(.*?)```', response, re.DOTALL)
752
+ for i, code_block in enumerate(code_blocks):
753
+ if len(code_block.strip()) > 10: # Only add substantial code blocks
754
+ # Try to detect language from the code block marker
755
+ lang_match = re.search(r'```(\w+)\n', response)
756
+ lang = lang_match.group(1) if lang_match else "unknown"
757
+
758
+ self.add_artifact_to_canvas(
759
+ conversation_id,
760
+ code_block.strip(),
761
+ "code",
762
+ f"code_snippet_{lang}_{len(self.canvas_artifacts.get(conversation_id, [])) + 1}"
763
+ )
764
+
765
+ def clear_conversation(self, conversation_id: str = "default"):
766
+ """Clear conversation but keep canvas artifacts"""
767
+ if conversation_id in self.conversations:
768
+ self.conversations[conversation_id] = []
769
+ console.log(f"[yellow]Cleared conversation: {conversation_id}[/yellow]")
770
+
771
+ def clear_canvas(self, conversation_id: str = "default"):
772
+ """Clear canvas artifacts"""
773
+ if conversation_id in self.canvas_artifacts:
774
+ self.canvas_artifacts[conversation_id] = []
775
+ console.log(f"[yellow]Cleared canvas: {conversation_id}[/yellow]")
776
+
777
+ def get_canvas_summary(self, conversation_id: str) -> List[Dict]:
778
+ """Get summary of canvas artifacts for display"""
779
+ if conversation_id not in self.canvas_artifacts:
780
+ return []
781
+
782
+ artifacts = []
783
+ for artifact in reversed(self.canvas_artifacts[conversation_id]): # Newest first
784
+ artifacts.append({
785
+ "id": artifact.id,
786
+ "type": artifact.type.upper(),
787
+ "title": artifact.title,
788
+ "preview": artifact.content[:100] + "..." if len(artifact.content) > 100 else artifact.content,
789
+ "timestamp": time.strftime("%H:%M:%S", time.localtime(artifact.timestamp))
790
+ })
791
+
792
+ return artifacts
793
+
794
+ def get_artifact_by_id(self, conversation_id: str, artifact_id: str) -> Optional[CanvasArtifact]:
795
+ """Get specific artifact by ID"""
796
+ if conversation_id not in self.canvas_artifacts:
797
+ return None
798
+
799
+ for artifact in self.canvas_artifacts[conversation_id]:
800
+ if artifact.id == artifact_id:
801
+ return artifact
802
+ return None
803
+
804
+ @staticmethod
805
+ async def fetch_available_models(base_url: str, api_key: str) -> List[str]:
806
+ """Fetch available models from the API"""
807
+ try:
808
+ console.log(f"[blue]Fetching models from {base_url}[/blue]")
809
+ async_client = AsyncOpenAI(base_url=base_url, api_key=api_key)
810
+ models = await async_client.models.list()
811
+ model_list = [model.id for model in models.data]
812
+ console.log(f"[green]Found {len(model_list)} models[/green]")
813
+ return model_list
814
+ except Exception as e:
815
+ console.log(f"[red]Error fetching models: {e}[/red]")
816
+ return ["default-model"]
817
+
818
+ def update_config(self, base_url: str, api_key: str, model_id: str, temperature: float, max_tokens: int):
819
+ """Update agent configuration"""
820
+ self.base_url = base_url
821
+ self.api_key = api_key
822
+ self.model_id = model_id
823
+ console.log(f"[blue]Updated config: {model_id} @ {base_url}[/blue]")
824
+ @staticmethod
825
+ async def fetch_available_models(base_url: str, api_key: str, use_huggingface: bool = False) -> List[str]:
826
+ """Fetch available models - works for both local and HF"""
827
+ if use_huggingface:
828
+ # Return popular HF models
829
+ return list(MODEL_OPTIONS.keys())[1:] # Skip "Local LM Studio"
830
+ else:
831
+ # Fetch from local LM Studio
832
+ try:
833
+ console.log(f"[blue]Fetching models from {base_url}[/blue]")
834
+ async_client = AsyncOpenAI(base_url=base_url, api_key=api_key)
835
+ models = await async_client.models.list()
836
+ model_list = [model.id for model in models.data]
837
+ console.log(f"[green]Found {len(model_list)} local models[/green]")
838
+ return model_list
839
+ except Exception as e:
840
+ console.log(f"[red]Error fetching local models: {e}[/red]")
841
+ return ["local-model"] # Fallback
842
+ async def chat_with_canvas(self, message: str, conversation_id: str = "default", include_canvas: bool = True) -> str:
843
+ """Enhanced chat that works with both local and HF"""
844
+ if conversation_id not in self.conversations:
845
+ self.conversations[conversation_id] = []
846
+
847
+ # Build messages with system prompt and canvas context
848
+ messages = [{"role": "system", "content": self.system_prompt}]
849
+
850
+ # Include canvas context if requested
851
+ if include_canvas:
852
+ canvas_context = self.get_canvas_context(conversation_id)
853
+ if canvas_context:
854
+ messages.append({"role": "system", "content": f"Current collaborative canvas state:\n{canvas_context}"})
855
+
856
+ # Add conversation history
857
+ for msg in self.conversations[conversation_id][-self.max_history_length:]:
858
+ messages.append(msg)
859
+
860
+ # Add current message
861
+ messages.append({"role": "user", "content": message})
862
+
863
+ try:
864
+ if self.use_huggingface:
865
+ response_text = await self._hf_inference(messages)
866
+ else:
867
+ response_text = await self._local_inference(messages)
868
+
869
+ # Update conversation history
870
+ self.conversations[conversation_id].extend([
871
+ {"role": "user", "content": message},
872
+ {"role": "assistant", "content": response_text}
873
+ ])
874
+
875
+ # Auto-extract and add code artifacts to canvas
876
+ self._extract_artifacts_to_canvas(response_text, conversation_id)
877
+
878
+ return response_text
879
+
880
+ except Exception as e:
881
+ error_msg = f"Error in chat_with_canvas: {str(e)}"
882
+ console.log(f"[red]{error_msg}[/red]")
883
+ return error_msg
884
+ def _convert_messages_to_prompt(self, messages: List[Dict]) -> str:
885
+ """Convert conversation messages to a single prompt for HF"""
886
+ prompt = ""
887
+ for msg in messages:
888
+ if msg["role"] == "system":
889
+ prompt += f"System: {msg['content']}\n\n"
890
+ elif msg["role"] == "user":
891
+ prompt += f"User: {msg['content']}\n\n"
892
+ elif msg["role"] == "assistant":
893
+ prompt += f"Assistant: {msg['content']}\n\n"
894
+ prompt += "Assistant:"
895
+ return prompt
896
+
897
+ # --- LCARS Styled Gradio Interface ---
898
+ class LcarsInterface:
899
+ def __init__(self, agent: EnhancedLLMAgent):
900
+ self.agent = agent
901
+ self.current_conversation = "default"
902
+
903
+ def create_interface(self):
904
+ """Create the full LCARS-styled interface"""
905
+
906
+ # Enhanced LCARS CSS with proper Star Trek styling
907
+ lcars_css = """
908
+ :root {
909
+ --lcars-orange: #FF9900;
910
+ --lcars-red: #FF0033;
911
+ --lcars-blue: #6699FF;
912
+ --lcars-purple: #CC99FF;
913
+ --lcars-pale-blue: #99CCFF;
914
+ --lcars-black: #000000;
915
+ --lcars-dark-blue: #3366CC;
916
+ --lcars-gray: #424242;
917
+ --lcars-yellow: #FFFF66;
918
+ }
919
+
920
+ body {
921
+ background: var(--lcars-black);
922
+ color: var(--lcars-orange);
923
+ font-family: 'Antonio', 'LCD', 'Courier New', monospace;
924
+ margin: 0;
925
+ padding: 0;
926
+ }
927
+
928
+ .gradio-container {
929
+ background: var(--lcars-black) !important;
930
+ min-height: 100vh;
931
+ }
932
+
933
+ .lcars-container {
934
+ background: var(--lcars-black);
935
+ border: 4px solid var(--lcars-orange);
936
+ border-radius: 0 30px 0 0;
937
+ min-height: 100vh;
938
+ padding: 20px;
939
+ }
940
+
941
+ .lcars-header {
942
+ background: linear-gradient(90deg, var(--lcars-red), var(--lcars-orange));
943
+ padding: 20px 40px;
944
+ border-radius: 0 60px 0 0;
945
+ margin: -20px -20px 20px -20px;
946
+ border-bottom: 6px solid var(--lcars-blue);
947
+ box-shadow: 0 4px 20px rgba(255, 153, 0, 0.3);
948
+ }
949
+
950
+ .lcars-title {
951
+ font-size: 3em;
952
+ font-weight: bold;
953
+ color: var(--lcars-black);
954
+ text-shadow: 3px 3px 6px rgba(255, 255, 255, 0.4);
955
+ margin: 0;
956
+ letter-spacing: 2px;
957
+ }
958
+
959
+ .lcars-subtitle {
960
+ font-size: 1.4em;
961
+ color: var(--lcars-black);
962
+ margin: 10px 0 0 0;
963
+ font-weight: bold;
964
+ }
965
+
966
+ .lcars-panel {
967
+ background: linear-gradient(135deg, rgba(66, 66, 66, 0.9), rgba(40, 40, 40, 0.9));
968
+ border: 3px solid var(--lcars-orange);
969
+ border-radius: 0 25px 0 25px;
970
+ padding: 20px;
971
+ margin-bottom: 20px;
972
+ box-shadow: 0 4px 15px rgba(255, 153, 0, 0.2);
973
+ }
974
+
975
+ .lcars-button {
976
+ background: linear-gradient(135deg, var(--lcars-orange), var(--lcars-red));
977
+ color: var(--lcars-black) !important;
978
+ border: none !important;
979
+ border-radius: 0 20px 0 20px !important;
980
+ padding: 12px 24px !important;
981
+ font-family: inherit !important;
982
+ font-weight: bold !important;
983
+ font-size: 1.1em !important;
984
+ cursor: pointer !important;
985
+ transition: all 0.3s ease !important;
986
+ margin: 8px !important;
987
+ box-shadow: 0 4px 8px rgba(255, 153, 0, 0.3) !important;
988
+ }
989
+
990
+ .lcars-button:hover {
991
+ background: linear-gradient(135deg, var(--lcars-red), var(--lcars-orange)) !important;
992
+ transform: translateY(-2px) !important;
993
+ box-shadow: 0 6px 12px rgba(255, 153, 0, 0.4) !important;
994
+ }
995
+
996
+ .lcars-input {
997
+ background: var(--lcars-black) !important;
998
+ color: var(--lcars-orange) !important;
999
+ border: 2px solid var(--lcars-blue) !important;
1000
+ border-radius: 0 15px 0 15px !important;
1001
+ padding: 12px !important;
1002
+ font-family: inherit !important;
1003
+ font-size: 1.1em !important;
1004
+ }
1005
+
1006
+ .lcars-chatbot {
1007
+ background: var(--lcars-black) !important;
1008
+ border: 3px solid var(--lcars-purple) !important;
1009
+ border-radius: 0 20px 0 20px !important;
1010
+ min-height: 400px;
1011
+ max-height: 500px;
1012
+ }
1013
+
1014
+ .lcars-code-editor {
1015
+ background: var(--lcars-black) !important;
1016
+ color: var(--lcars-pale-blue) !important;
1017
+ border: 3px solid var(--lcars-blue) !important;
1018
+ border-radius: 0 20px 0 20px !important;
1019
+ font-family: 'Fira Code', 'Courier New', monospace !important;
1020
+ font-size: 1em !important;
1021
+ }
1022
+
1023
+ .user-message {
1024
+ background: linear-gradient(135deg, rgba(102, 153, 255, 0.2), rgba(51, 102, 204, 0.2)) !important;
1025
+ border-left: 6px solid var(--lcars-blue) !important;
1026
+ padding: 12px !important;
1027
+ margin: 8px 0 !important;
1028
+ border-radius: 0 15px 0 15px !important;
1029
+ }
1030
+
1031
+ .assistant-message {
1032
+ background: linear-gradient(135deg, rgba(255, 153, 0, 0.2), rgba(255, 102, 0, 0.2)) !important;
1033
+ border-left: 6px solid var(--lcars-orange) !important;
1034
+ padding: 12px !important;
1035
+ margin: 8px 0 !important;
1036
+ border-radius: 0 15px 0 15px !important;
1037
+ }
1038
+
1039
+ .artifact-item {
1040
+ background: linear-gradient(135deg, rgba(204, 153, 255, 0.15), rgba(153, 102, 204, 0.15));
1041
+ border: 2px solid var(--lcars-purple);
1042
+ padding: 10px;
1043
+ margin: 6px 0;
1044
+ border-radius: 0 12px 0 12px;
1045
+ cursor: pointer;
1046
+ transition: all 0.3s ease;
1047
+ }
1048
+
1049
+ .artifact-item:hover {
1050
+ background: linear-gradient(135deg, rgba(204, 153, 255, 0.3), rgba(153, 102, 204, 0.3));
1051
+ transform: translateX(5px);
1052
+ }
1053
+
1054
+ .status-indicator {
1055
+ display: inline-block;
1056
+ width: 16px;
1057
+ height: 16px;
1058
+ border-radius: 50%;
1059
+ background: var(--lcars-red);
1060
+ margin-right: 12px;
1061
+ box-shadow: 0 0 10px currentColor;
1062
+ }
1063
+
1064
+ .status-online {
1065
+ background: var(--lcars-blue);
1066
+ animation: pulse 1.5s infinite;
1067
+ }
1068
+
1069
+ @keyframes pulse {
1070
+ 0% { transform: scale(1); opacity: 1; }
1071
+ 50% { transform: scale(1.1); opacity: 0.7; }
1072
+ 100% { transform: scale(1); opacity: 1; }
1073
+ }
1074
+
1075
+ .panel-title {
1076
+ color: var(--lcars-yellow) !important;
1077
+ font-size: 1.4em !important;
1078
+ font-weight: bold !important;
1079
+ margin-bottom: 15px !important;
1080
+ border-bottom: 2px solid var(--lcars-orange);
1081
+ padding-bottom: 8px;
1082
+ }
1083
+
1084
+ .gradio-accordion {
1085
+ border: 2px solid var(--lcars-orange) !important;
1086
+ border-radius: 0 20px 0 20px !important;
1087
+ margin-bottom: 20px !important;
1088
+ }
1089
+
1090
+ .gradio-accordion .label {
1091
+ background: linear-gradient(90deg, var(--lcars-orange), var(--lcars-red)) !important;
1092
+ color: var(--lcars-black) !important;
1093
+ font-size: 1.3em !important;
1094
+ font-weight: bold !important;
1095
+ padding: 15px 20px !important;
1096
+ }
1097
+ """
1098
+
1099
+ with gr.Blocks(css=lcars_css, theme=gr.themes.Default(), title="LCARS Terminal") as interface:
1100
+
1101
+ with gr.Column(elem_classes="lcars-container"):
1102
+ # Header Section
1103
+ with gr.Row(elem_classes="lcars-header"):
1104
+ gr.Markdown("""
1105
+ <div style="text-align: center; width: 100%;">
1106
+ <div class="lcars-title">πŸš€ LCARS TERMINAL v4.2</div>
1107
+ <div class="lcars-subtitle">STARFLEET AI DEVELOPMENT CONSOLE</div>
1108
+ <div style="margin-top: 10px;">
1109
+ <span class="status-indicator status-online"></span>
1110
+ <span style="color: var(--lcars-black); font-weight: bold;">SYSTEM ONLINE</span>
1111
+ </div>
1112
+ </div>
1113
+ """)
1114
+
1115
+ # Main Content Area
1116
+ with gr.Row():
1117
+ # Left Sidebar - Controls and Configuration
1118
+ with gr.Column(scale=1, min_width=400):
1119
+ # Configuration Panel
1120
+ with gr.Column(elem_classes="lcars-panel"):
1121
+ gr.Markdown("### πŸ”§ SYSTEM CONFIGURATION", elem_classes="panel-title")
1122
+
1123
+ with gr.Row():
1124
+ base_url = gr.Textbox(
1125
+ value=DEFAULT_BASE_URL,
1126
+ label="API Base URL",
1127
+ elem_classes="lcars-input"
1128
+ )
1129
+ api_key = gr.Textbox(
1130
+ value=DEFAULT_API_KEY,
1131
+ label="API Key",
1132
+ type="password",
1133
+ elem_classes="lcars-input"
1134
+ )
1135
+
1136
+ with gr.Row():
1137
+ model_dropdown = gr.Dropdown(
1138
+ choices=["Fetching models..."],
1139
+ value="default-model",
1140
+ label="AI Model",
1141
+ elem_classes="lcars-input"
1142
+ )
1143
+ fetch_models_btn = gr.Button("πŸ“‘ Fetch Models", elem_classes="lcars-button")
1144
+
1145
+ with gr.Row():
1146
+ temperature = gr.Slider(
1147
+ 0.0, 2.0,
1148
+ value=0.7,
1149
+ label="Temperature",
1150
+ elem_classes="lcars-input"
1151
+ )
1152
+ max_tokens = gr.Slider(
1153
+ 128, 8192,
1154
+ value=2000,
1155
+ step=128,
1156
+ label="Max Tokens",
1157
+ elem_classes="lcars-input"
1158
+ )
1159
+
1160
+ with gr.Row():
1161
+ update_config_btn = gr.Button("πŸ’Ύ Apply Config", elem_classes="lcars-button")
1162
+ speech_toggle = gr.Checkbox(value=True, label="πŸ”Š Speech Output")
1163
+
1164
+ # Canvas Artifacts Panel
1165
+ with gr.Column(elem_classes="lcars-panel"):
1166
+ gr.Markdown("### 🎨 CANVAS ARTIFACTS", elem_classes="panel-title")
1167
+ artifact_display = gr.JSON(
1168
+ label="",
1169
+ elem_id="artifact-display"
1170
+ )
1171
+ with gr.Row():
1172
+ refresh_artifacts_btn = gr.Button("πŸ”„ Refresh", elem_classes="lcars-button")
1173
+ clear_canvas_btn = gr.Button("πŸ—‘οΈ Clear Canvas", elem_classes="lcars-button")
1174
+
1175
+ # Main Content - Chat and Code Canvas
1176
+ with gr.Column(scale=2):
1177
+ # Collaborative Code Canvas
1178
+ with gr.Accordion("πŸ’» COLLABORATIVE CODE CANVAS", open=True):
1179
+ code_editor = gr.Code(
1180
+ value="# Welcome to LCARS Collaborative Canvas\n# Your code artifacts will appear here\n\nprint('Hello, Starfleet!')",
1181
+ language="python",
1182
+ lines=20,
1183
+ label="",
1184
+ elem_classes="lcars-code-editor"
1185
+ )
1186
+
1187
+ with gr.Row():
1188
+ load_to_chat_btn = gr.Button("πŸ’¬ Discuss This Code", elem_classes="lcars-button")
1189
+ analyze_btn = gr.Button("πŸ” Analyze Code", elem_classes="lcars-button")
1190
+ optimize_btn = gr.Button("⚑ Optimize", elem_classes="lcars-button")
1191
+ document_btn = gr.Button("πŸ“š Document", elem_classes="lcars-button")
1192
+
1193
+ # Chat Interface
1194
+ with gr.Column(elem_classes="lcars-panel"):
1195
+ gr.Markdown("### πŸ’¬ MISSION LOG", elem_classes="panel-title")
1196
+ chatbot = gr.Chatbot(
1197
+ label="",
1198
+ elem_classes="lcars-chatbot",
1199
+ show_label=False,
1200
+ height=400
1201
+ )
1202
+
1203
+ with gr.Row():
1204
+ message_input = gr.Textbox(
1205
+ placeholder="Enter your command or query...",
1206
+ show_label=False,
1207
+ lines=2,
1208
+ elem_classes="lcars-input",
1209
+ scale=4
1210
+ )
1211
+ send_btn = gr.Button("πŸš€ TRANSMIT", elem_classes="lcars-button", scale=1)
1212
+
1213
+ # Status and Controls
1214
+ with gr.Row():
1215
+ status_display = gr.Textbox(
1216
+ value="LCARS terminal operational. Awaiting commands.",
1217
+ label="Status",
1218
+ max_lines=2,
1219
+ elem_classes="lcars-input"
1220
+ )
1221
+ with gr.Column(scale=0):
1222
+ clear_chat_btn = gr.Button("πŸ—‘οΈ Clear Chat", elem_classes="lcars-button")
1223
+ new_session_btn = gr.Button("πŸ†• New Session", elem_classes="lcars-button")
1224
+
1225
+ # === EVENT HANDLERS ===
1226
+
1227
+ async def fetch_and_update_models(base_url, api_key):
1228
+ """Fetch models and update dropdown"""
1229
+ try:
1230
+ models = await EnhancedLLMAgent.fetch_available_models(base_url, api_key)
1231
+ if models:
1232
+ return gr.update(choices=models, value=models[0])
1233
+ else:
1234
+ return gr.update(choices=["No models found"], value="No models found")
1235
+ except Exception as e:
1236
+ console.log(f"[red]Error fetching models: {e}[/red]")
1237
+ return gr.update(choices=[f"Error: {str(e)}"], value=f"Error: {str(e)}")
1238
+
1239
+ def update_agent_config(base_url, api_key, model_id, temperature_val, max_tokens_val):
1240
+ """Update agent configuration"""
1241
+ try:
1242
+ self.agent.update_config(base_url, api_key, model_id, temperature_val, max_tokens_val)
1243
+ return f"βœ… Configuration updated: {model_id}"
1244
+ except Exception as e:
1245
+ return f"❌ Config error: {str(e)}"
1246
+
1247
+ def get_artifacts():
1248
+ """Get current canvas artifacts"""
1249
+ return self.agent.get_canvas_summary(self.current_conversation)
1250
+
1251
+ def clear_canvas():
1252
+ """Clear the canvas"""
1253
+ self.agent.clear_canvas(self.current_conversation)
1254
+ return [], "βœ… Canvas cleared"
1255
+
1256
+ async def process_message(message, history, speech_enabled):
1257
+ """Process a chat message"""
1258
+ if not message.strip():
1259
+ return "", history, "Please enter a message"
1260
+
1261
+ # Add user message to history
1262
+ history = history + [[message, None]]
1263
+
1264
+ try:
1265
+ # Get AI response
1266
+ response = await self.agent.chat_with_canvas(
1267
+ message,
1268
+ self.current_conversation,
1269
+ include_canvas=True
1270
+ )
1271
+
1272
+ # Update history with response
1273
+ history[-1][1] = response
1274
+
1275
+ # Speech synthesis if enabled
1276
+ if speech_enabled and self.agent.speech_enabled:
1277
+ self.agent.speak(response)
1278
+
1279
+ # Get updated artifacts
1280
+ artifacts = get_artifacts()
1281
+
1282
+ status = f"βœ… Response received. Canvas artifacts: {len(artifacts)}"
1283
+ return "", history, status, artifacts
1284
+
1285
+ except Exception as e:
1286
+ error_msg = f"❌ Error: {str(e)}"
1287
+ history[-1][1] = error_msg
1288
+ return "", history, error_msg, get_artifacts()
1289
+
1290
+ def load_code_to_chat(code):
1291
+ """Load code from canvas into chat"""
1292
+ if not code.strip():
1293
+ return ""
1294
+ return f"Please analyze this code:\n```python\n{code}\n```"
1295
+
1296
+ def analyze_code(code):
1297
+ """Quick analysis of code"""
1298
+ if not code.strip():
1299
+ return "Please provide some code to analyze"
1300
+ return f"Perform a comprehensive analysis of this code:\n```python\n{code}\n```"
1301
+
1302
+ def optimize_code(code):
1303
+ """Quick optimization request"""
1304
+ if not code.strip():
1305
+ return "Please provide some code to optimize"
1306
+ return f"Optimize this code for performance and best practices:\n```python\n{code}\n```"
1307
+
1308
+ def document_code(code):
1309
+ """Quick documentation request"""
1310
+ if not code.strip():
1311
+ return "Please provide some code to document"
1312
+ return f"Generate comprehensive documentation for this code:\n```python\n{code}\n```"
1313
+
1314
+ def clear_chat():
1315
+ """Clear chat history"""
1316
+ self.agent.clear_conversation(self.current_conversation)
1317
+ return [], "βœ… Chat cleared"
1318
+
1319
+ def new_session():
1320
+ """Start new session"""
1321
+ self.agent.clear_conversation(self.current_conversation)
1322
+ self.agent.clear_canvas(self.current_conversation)
1323
+ return [], "# New collaborative session started\n\nprint('Ready for development!')", "πŸ†• New session started", []
1324
+
1325
+ # Connect event handlers
1326
+ fetch_models_btn.click(
1327
+ fetch_and_update_models,
1328
+ inputs=[base_url, api_key],
1329
+ outputs=model_dropdown
1330
+ )
1331
+
1332
+ update_config_btn.click(
1333
+ update_agent_config,
1334
+ inputs=[base_url, api_key, model_dropdown, temperature, max_tokens],
1335
+ outputs=status_display
1336
+ )
1337
+
1338
+ send_btn.click(
1339
+ process_message,
1340
+ inputs=[message_input, chatbot, speech_toggle],
1341
+ outputs=[message_input, chatbot, status_display, artifact_display]
1342
+ )
1343
+
1344
+ message_input.submit(
1345
+ process_message,
1346
+ inputs=[message_input, chatbot, speech_toggle],
1347
+ outputs=[message_input, chatbot, status_display, artifact_display]
1348
+ )
1349
+
1350
+ load_to_chat_btn.click(
1351
+ load_code_to_chat,
1352
+ inputs=code_editor,
1353
+ outputs=message_input
1354
+ )
1355
+
1356
+ analyze_btn.click(
1357
+ analyze_code,
1358
+ inputs=code_editor,
1359
+ outputs=message_input
1360
+ )
1361
+
1362
+ optimize_btn.click(
1363
+ optimize_code,
1364
+ inputs=code_editor,
1365
+ outputs=message_input
1366
+ )
1367
+
1368
+ document_btn.click(
1369
+ document_code,
1370
+ inputs=code_editor,
1371
+ outputs=message_input
1372
+ )
1373
+
1374
+ refresh_artifacts_btn.click(
1375
+ get_artifacts,
1376
+ outputs=artifact_display
1377
+ )
1378
+
1379
+ clear_canvas_btn.click(
1380
+ clear_canvas,
1381
+ outputs=[artifact_display, status_display]
1382
+ )
1383
+
1384
+ clear_chat_btn.click(
1385
+ clear_chat,
1386
+ outputs=[chatbot, status_display]
1387
+ )
1388
+
1389
+ new_session_btn.click(
1390
+ new_session,
1391
+ outputs=[chatbot, code_editor, status_display, artifact_display]
1392
+ )
1393
+
1394
+ # Initialize artifacts on load
1395
+ interface.load(get_artifacts, outputs=artifact_display)
1396
+
1397
+ return interface
1398
+
1399
+
1400
+
1401
+
1402
+
1403
+ # Update the LcarsInterface to include connection options
1404
+ class LcarsInterface:
1405
+ def __init__(self):
1406
+ # Start with HuggingFace by default for Spaces
1407
+ self.use_huggingface = True
1408
+ self.agent = EnhancedLLMAgent(use_huggingface=self.use_huggingface)
1409
+ self.current_conversation = "default"
1410
+
1411
+
1412
+
1413
+ def create_interface(self):
1414
+ """Create the full LCARS-styled interface"""
1415
+
1416
+ # Enhanced LCARS CSS with proper Star Trek styling
1417
+ lcars_css = """
1418
+ :root {
1419
+ --lcars-orange: #FF9900;
1420
+ --lcars-red: #FF0033;
1421
+ --lcars-blue: #6699FF;
1422
+ --lcars-purple: #CC99FF;
1423
+ --lcars-pale-blue: #99CCFF;
1424
+ --lcars-black: #000000;
1425
+ --lcars-dark-blue: #3366CC;
1426
+ --lcars-gray: #424242;
1427
+ --lcars-yellow: #FFFF66;
1428
+ }
1429
+
1430
+ body {
1431
+ background: var(--lcars-black);
1432
+ color: var(--lcars-orange);
1433
+ font-family: 'Antonio', 'LCD', 'Courier New', monospace;
1434
+ margin: 0;
1435
+ padding: 0;
1436
+ }
1437
+
1438
+ .gradio-container {
1439
+ background: var(--lcars-black) !important;
1440
+ min-height: 100vh;
1441
+ }
1442
+
1443
+ .lcars-container {
1444
+ background: var(--lcars-black);
1445
+ border: 4px solid var(--lcars-orange);
1446
+ border-radius: 0 30px 0 0;
1447
+ min-height: 100vh;
1448
+ padding: 20px;
1449
+ }
1450
+
1451
+ .lcars-header {
1452
+ background: linear-gradient(90deg, var(--lcars-red), var(--lcars-orange));
1453
+ padding: 20px 40px;
1454
+ border-radius: 0 60px 0 0;
1455
+ margin: -20px -20px 20px -20px;
1456
+ border-bottom: 6px solid var(--lcars-blue);
1457
+ box-shadow: 0 4px 20px rgba(255, 153, 0, 0.3);
1458
+ }
1459
+
1460
+ .lcars-title {
1461
+ font-size: 3em;
1462
+ font-weight: bold;
1463
+ color: var(--lcars-black);
1464
+ text-shadow: 3px 3px 6px rgba(255, 255, 255, 0.4);
1465
+ margin: 0;
1466
+ letter-spacing: 2px;
1467
+ }
1468
+
1469
+ .lcars-subtitle {
1470
+ font-size: 1.4em;
1471
+ color: var(--lcars-black);
1472
+ margin: 10px 0 0 0;
1473
+ font-weight: bold;
1474
+ }
1475
+
1476
+ .lcars-panel {
1477
+ background: linear-gradient(135deg, rgba(66, 66, 66, 0.9), rgba(40, 40, 40, 0.9));
1478
+ border: 3px solid var(--lcars-orange);
1479
+ border-radius: 0 25px 0 25px;
1480
+ padding: 20px;
1481
+ margin-bottom: 20px;
1482
+ box-shadow: 0 4px 15px rgba(255, 153, 0, 0.2);
1483
+ }
1484
+
1485
+ .lcars-button {
1486
+ background: linear-gradient(135deg, var(--lcars-orange), var(--lcars-red));
1487
+ color: var(--lcars-black) !important;
1488
+ border: none !important;
1489
+ border-radius: 0 20px 0 20px !important;
1490
+ padding: 12px 24px !important;
1491
+ font-family: inherit !important;
1492
+ font-weight: bold !important;
1493
+ font-size: 1.1em !important;
1494
+ cursor: pointer !important;
1495
+ transition: all 0.3s ease !important;
1496
+ margin: 8px !important;
1497
+ box-shadow: 0 4px 8px rgba(255, 153, 0, 0.3) !important;
1498
+ }
1499
+
1500
+ .lcars-button:hover {
1501
+ background: linear-gradient(135deg, var(--lcars-red), var(--lcars-orange)) !important;
1502
+ transform: translateY(-2px) !important;
1503
+ box-shadow: 0 6px 12px rgba(255, 153, 0, 0.4) !important;
1504
+ }
1505
+
1506
+ .lcars-input {
1507
+ background: var(--lcars-black) !important;
1508
+ color: var(--lcars-orange) !important;
1509
+ border: 2px solid var(--lcars-blue) !important;
1510
+ border-radius: 0 15px 0 15px !important;
1511
+ padding: 12px !important;
1512
+ font-family: inherit !important;
1513
+ font-size: 1.1em !important;
1514
+ }
1515
+
1516
+ .lcars-chatbot {
1517
+ background: var(--lcars-black) !important;
1518
+ border: 3px solid var(--lcars-purple) !important;
1519
+ border-radius: 0 20px 0 20px !important;
1520
+ min-height: 400px;
1521
+ max-height: 500px;
1522
+ }
1523
+
1524
+ .lcars-code-editor {
1525
+ background: var(--lcars-black) !important;
1526
+ color: var(--lcars-pale-blue) !important;
1527
+ border: 3px solid var(--lcars-blue) !important;
1528
+ border-radius: 0 20px 0 20px !important;
1529
+ font-family: 'Fira Code', 'Courier New', monospace !important;
1530
+ font-size: 1em !important;
1531
+ }
1532
+
1533
+ .user-message {
1534
+ background: linear-gradient(135deg, rgba(102, 153, 255, 0.2), rgba(51, 102, 204, 0.2)) !important;
1535
+ border-left: 6px solid var(--lcars-blue) !important;
1536
+ padding: 12px !important;
1537
+ margin: 8px 0 !important;
1538
+ border-radius: 0 15px 0 15px !important;
1539
+ }
1540
+
1541
+ .assistant-message {
1542
+ background: linear-gradient(135deg, rgba(255, 153, 0, 0.2), rgba(255, 102, 0, 0.2)) !important;
1543
+ border-left: 6px solid var(--lcars-orange) !important;
1544
+ padding: 12px !important;
1545
+ margin: 8px 0 !important;
1546
+ border-radius: 0 15px 0 15px !important;
1547
+ }
1548
+
1549
+ .artifact-item {
1550
+ background: linear-gradient(135deg, rgba(204, 153, 255, 0.15), rgba(153, 102, 204, 0.15));
1551
+ border: 2px solid var(--lcars-purple);
1552
+ padding: 10px;
1553
+ margin: 6px 0;
1554
+ border-radius: 0 12px 0 12px;
1555
+ cursor: pointer;
1556
+ transition: all 0.3s ease;
1557
+ }
1558
+
1559
+ .artifact-item:hover {
1560
+ background: linear-gradient(135deg, rgba(204, 153, 255, 0.3), rgba(153, 102, 204, 0.3));
1561
+ transform: translateX(5px);
1562
+ }
1563
+
1564
+ .status-indicator {
1565
+ display: inline-block;
1566
+ width: 16px;
1567
+ height: 16px;
1568
+ border-radius: 50%;
1569
+ background: var(--lcars-red);
1570
+ margin-right: 12px;
1571
+ box-shadow: 0 0 10px currentColor;
1572
+ }
1573
+
1574
+ .status-online {
1575
+ background: var(--lcars-blue);
1576
+ animation: pulse 1.5s infinite;
1577
+ }
1578
+
1579
+ @keyframes pulse {
1580
+ 0% { transform: scale(1); opacity: 1; }
1581
+ 50% { transform: scale(1.1); opacity: 0.7; }
1582
+ 100% { transform: scale(1); opacity: 1; }
1583
+ }
1584
+
1585
+ .panel-title {
1586
+ color: var(--lcars-yellow) !important;
1587
+ font-size: 1.4em !important;
1588
+ font-weight: bold !important;
1589
+ margin-bottom: 15px !important;
1590
+ border-bottom: 2px solid var(--lcars-orange);
1591
+ padding-bottom: 8px;
1592
+ }
1593
+
1594
+ .gradio-accordion {
1595
+ border: 2px solid var(--lcars-orange) !important;
1596
+ border-radius: 0 20px 0 20px !important;
1597
+ margin-bottom: 20px !important;
1598
+ }
1599
+
1600
+ .gradio-accordion .label {
1601
+ background: linear-gradient(90deg, var(--lcars-orange), var(--lcars-red)) !important;
1602
+ color: var(--lcars-black) !important;
1603
+ font-size: 1.3em !important;
1604
+ font-weight: bold !important;
1605
+ padding: 15px 20px !important;
1606
+ }
1607
+ """
1608
+
1609
+ with gr.Blocks(css=lcars_css, theme=gr.themes.Default(), title="LCARS Terminal") as interface:
1610
+
1611
+ with gr.Column(elem_classes="lcars-container"):
1612
+ # Header Section
1613
+ with gr.Row(elem_classes="lcars-header"):
1614
+ gr.Markdown("""
1615
+ <div style="text-align: center; width: 100%;">
1616
+ <div class="lcars-title">πŸš€ LCARS TERMINAL v4.2</div>
1617
+ <div class="lcars-subtitle">STARFLEET AI DEVELOPMENT CONSOLE</div>
1618
+ <div style="margin-top: 10px;">
1619
+ <span class="status-indicator status-online"></span>
1620
+ <span style="color: var(--lcars-black); font-weight: bold;">SYSTEM ONLINE</span>
1621
+ </div>
1622
+ </div>
1623
+ """)
1624
+
1625
+ # Main Content Area
1626
+ with gr.Row():
1627
+ # Left Sidebar - Controls and Configuration
1628
+ with gr.Column(scale=1, min_width=400):
1629
+ # Configuration Panel
1630
+ with gr.Column(elem_classes="lcars-panel"):
1631
+ gr.Markdown("### πŸ”§ SYSTEM CONFIGURATION", elem_classes="panel-title")
1632
+
1633
+ with gr.Row():
1634
+ base_url = gr.Textbox(
1635
+ value=DEFAULT_BASE_URL,
1636
+ label="API Base URL",
1637
+ elem_classes="lcars-input"
1638
+ )
1639
+ api_key = gr.Textbox(
1640
+ value=DEFAULT_API_KEY,
1641
+ label="API Key",
1642
+ type="password",
1643
+ elem_classes="lcars-input"
1644
+ )
1645
+
1646
+ with gr.Row():
1647
+ model_dropdown = gr.Dropdown(
1648
+ choices=["Fetching models..."],
1649
+ value="default-model",
1650
+ label="AI Model",
1651
+ elem_classes="lcars-input"
1652
+ )
1653
+ fetch_models_btn = gr.Button("πŸ“‘ Fetch Models", elem_classes="lcars-button")
1654
+
1655
+ with gr.Row():
1656
+ temperature = gr.Slider(
1657
+ 0.0, 2.0,
1658
+ value=0.7,
1659
+ label="Temperature",
1660
+ elem_classes="lcars-input"
1661
+ )
1662
+ max_tokens = gr.Slider(
1663
+ 128, 8192,
1664
+ value=2000,
1665
+ step=128,
1666
+ label="Max Tokens",
1667
+ elem_classes="lcars-input"
1668
+ )
1669
+
1670
+ with gr.Row():
1671
+ update_config_btn = gr.Button("πŸ’Ύ Apply Config", elem_classes="lcars-button")
1672
+ speech_toggle = gr.Checkbox(value=True, label="πŸ”Š Speech Output")
1673
+
1674
+ # Canvas Artifacts Panel
1675
+ with gr.Column(elem_classes="lcars-panel"):
1676
+ gr.Markdown("### 🎨 CANVAS ARTIFACTS", elem_classes="panel-title")
1677
+ artifact_display = gr.JSON(
1678
+ label="",
1679
+ elem_id="artifact-display"
1680
+ )
1681
+ with gr.Row():
1682
+ refresh_artifacts_btn = gr.Button("πŸ”„ Refresh", elem_classes="lcars-button")
1683
+ clear_canvas_btn = gr.Button("πŸ—‘οΈ Clear Canvas", elem_classes="lcars-button")
1684
+
1685
+ # Main Content - Chat and Code Canvas
1686
+ with gr.Column(scale=2):
1687
+ # Collaborative Code Canvas
1688
+ with gr.Accordion("πŸ’» COLLABORATIVE CODE CANVAS", open=True):
1689
+ code_editor = gr.Code(
1690
+ value="# Welcome to LCARS Collaborative Canvas\n# Your code artifacts will appear here\n\nprint('Hello, Starfleet!')",
1691
+ language="python",
1692
+ lines=20,
1693
+ label="",
1694
+ elem_classes="lcars-code-editor"
1695
+ )
1696
+
1697
+ with gr.Row():
1698
+ load_to_chat_btn = gr.Button("πŸ’¬ Discuss This Code", elem_classes="lcars-button")
1699
+ analyze_btn = gr.Button("πŸ” Analyze Code", elem_classes="lcars-button")
1700
+ optimize_btn = gr.Button("⚑ Optimize", elem_classes="lcars-button")
1701
+ document_btn = gr.Button("πŸ“š Document", elem_classes="lcars-button")
1702
+
1703
+ # Chat Interface
1704
+ with gr.Column(elem_classes="lcars-panel"):
1705
+ gr.Markdown("### πŸ’¬ MISSION LOG", elem_classes="panel-title")
1706
+ chatbot = gr.Chatbot(
1707
+ label="",
1708
+ elem_classes="lcars-chatbot",
1709
+ show_label=False,
1710
+ height=400
1711
+ )
1712
+
1713
+ with gr.Row():
1714
+ message_input = gr.Textbox(
1715
+ placeholder="Enter your command or query...",
1716
+ show_label=False,
1717
+ lines=2,
1718
+ elem_classes="lcars-input",
1719
+ scale=4
1720
+ )
1721
+ send_btn = gr.Button("πŸš€ TRANSMIT", elem_classes="lcars-button", scale=1)
1722
+
1723
+ # Status and Controls
1724
+ with gr.Row():
1725
+ status_display = gr.Textbox(
1726
+ value="LCARS terminal operational. Awaiting commands.",
1727
+ label="Status",
1728
+ max_lines=2,
1729
+ elem_classes="lcars-input"
1730
+ )
1731
+ with gr.Column(scale=0):
1732
+ clear_chat_btn = gr.Button("πŸ—‘οΈ Clear Chat", elem_classes="lcars-button")
1733
+ new_session_btn = gr.Button("πŸ†• New Session", elem_classes="lcars-button")
1734
+
1735
+ # === EVENT HANDLERS ===
1736
+
1737
+ async def fetch_and_update_models(base_url, api_key):
1738
+ """Fetch models and update dropdown"""
1739
+ try:
1740
+ models = await EnhancedLLMAgent.fetch_available_models(base_url, api_key)
1741
+ if models:
1742
+ return gr.update(choices=models, value=models[0])
1743
+ else:
1744
+ return gr.update(choices=["No models found"], value="No models found")
1745
+ except Exception as e:
1746
+ console.log(f"[red]Error fetching models: {e}[/red]")
1747
+ return gr.update(choices=[f"Error: {str(e)}"], value=f"Error: {str(e)}")
1748
+
1749
+ def update_agent_config(base_url, api_key, model_id, temperature_val, max_tokens_val):
1750
+ """Update agent configuration"""
1751
+ try:
1752
+ self.agent.update_config(base_url, api_key, model_id, temperature_val, max_tokens_val)
1753
+ return f"βœ… Configuration updated: {model_id}"
1754
+ except Exception as e:
1755
+ return f"❌ Config error: {str(e)}"
1756
+
1757
+ def get_artifacts():
1758
+ """Get current canvas artifacts"""
1759
+ return self.agent.get_canvas_summary(self.current_conversation)
1760
+
1761
+ def clear_canvas():
1762
+ """Clear the canvas"""
1763
+ self.agent.clear_canvas(self.current_conversation)
1764
+ return [], "βœ… Canvas cleared"
1765
+
1766
+ async def process_message(message, history, speech_enabled):
1767
+ """Process a chat message"""
1768
+ if not message.strip():
1769
+ return "", history, "Please enter a message"
1770
+
1771
+ # Add user message to history
1772
+ history = history + [[message, None]]
1773
+
1774
+ try:
1775
+ # Get AI response
1776
+ response = await self.agent.chat_with_canvas(
1777
+ message,
1778
+ self.current_conversation,
1779
+ include_canvas=True
1780
+ )
1781
+
1782
+ # Update history with response
1783
+ history[-1][1] = response
1784
+
1785
+ # Speech synthesis if enabled
1786
+ if speech_enabled and self.agent.speech_enabled:
1787
+ self.agent.speak(response)
1788
+
1789
+ # Get updated artifacts
1790
+ artifacts = get_artifacts()
1791
+
1792
+ status = f"βœ… Response received. Canvas artifacts: {len(artifacts)}"
1793
+ return "", history, status, artifacts
1794
+
1795
+ except Exception as e:
1796
+ error_msg = f"❌ Error: {str(e)}"
1797
+ history[-1][1] = error_msg
1798
+ return "", history, error_msg, get_artifacts()
1799
+
1800
+ def load_code_to_chat(code):
1801
+ """Load code from canvas into chat"""
1802
+ if not code.strip():
1803
+ return ""
1804
+ return f"Please analyze this code:\n```python\n{code}\n```"
1805
+
1806
+ def analyze_code(code):
1807
+ """Quick analysis of code"""
1808
+ if not code.strip():
1809
+ return "Please provide some code to analyze"
1810
+ return f"Perform a comprehensive analysis of this code:\n```python\n{code}\n```"
1811
+
1812
+ def optimize_code(code):
1813
+ """Quick optimization request"""
1814
+ if not code.strip():
1815
+ return "Please provide some code to optimize"
1816
+ return f"Optimize this code for performance and best practices:\n```python\n{code}\n```"
1817
+
1818
+ def document_code(code):
1819
+ """Quick documentation request"""
1820
+ if not code.strip():
1821
+ return "Please provide some code to document"
1822
+ return f"Generate comprehensive documentation for this code:\n```python\n{code}\n```"
1823
+
1824
+ def clear_chat():
1825
+ """Clear chat history"""
1826
+ self.agent.clear_conversation(self.current_conversation)
1827
+ return [], "βœ… Chat cleared"
1828
+
1829
+ def new_session():
1830
+ """Start new session"""
1831
+ self.agent.clear_conversation(self.current_conversation)
1832
+ self.agent.clear_canvas(self.current_conversation)
1833
+ return [], "# New collaborative session started\n\nprint('Ready for development!')", "πŸ†• New session started", []
1834
+
1835
+ # Connect event handlers
1836
+ fetch_models_btn.click(
1837
+ fetch_and_update_models,
1838
+ inputs=[base_url, api_key],
1839
+ outputs=model_dropdown
1840
+ )
1841
+
1842
+ update_config_btn.click(
1843
+ update_agent_config,
1844
+ inputs=[base_url, api_key, model_dropdown, temperature, max_tokens],
1845
+ outputs=status_display
1846
+ )
1847
+
1848
+ send_btn.click(
1849
+ process_message,
1850
+ inputs=[message_input, chatbot, speech_toggle],
1851
+ outputs=[message_input, chatbot, status_display, artifact_display]
1852
+ )
1853
+
1854
+ message_input.submit(
1855
+ process_message,
1856
+ inputs=[message_input, chatbot, speech_toggle],
1857
+ outputs=[message_input, chatbot, status_display, artifact_display]
1858
+ )
1859
+
1860
+ load_to_chat_btn.click(
1861
+ load_code_to_chat,
1862
+ inputs=code_editor,
1863
+ outputs=message_input
1864
+ )
1865
+
1866
+ analyze_btn.click(
1867
+ analyze_code,
1868
+ inputs=code_editor,
1869
+ outputs=message_input
1870
+ )
1871
+
1872
+ optimize_btn.click(
1873
+ optimize_code,
1874
+ inputs=code_editor,
1875
+ outputs=message_input
1876
+ )
1877
+
1878
+ document_btn.click(
1879
+ document_code,
1880
+ inputs=code_editor,
1881
+ outputs=message_input
1882
+ )
1883
+
1884
+ refresh_artifacts_btn.click(
1885
+ get_artifacts,
1886
+ outputs=artifact_display
1887
+ )
1888
+
1889
+ clear_canvas_btn.click(
1890
+ clear_canvas,
1891
+ outputs=[artifact_display, status_display]
1892
+ )
1893
+
1894
+ clear_chat_btn.click(
1895
+ clear_chat,
1896
+ outputs=[chatbot, status_display]
1897
+ )
1898
+
1899
+ new_session_btn.click(
1900
+ new_session,
1901
+ outputs=[chatbot, code_editor, status_display, artifact_display]
1902
+ )
1903
+
1904
+ # Initialize artifacts on load
1905
+ interface.load(get_artifacts, outputs=artifact_display)
1906
+
1907
+
1908
+
1909
+ # Add connection type selector at the top
1910
+ with gr.Row(elem_classes="lcars-panel"):
1911
+ gr.Markdown("### 🌐 CONNECTION TYPE", elem_classes="panel-title")
1912
+ connection_type = gr.Radio(
1913
+ choices=["HuggingFace Inference", "Local LM Studio"],
1914
+ value="HuggingFace Inference",
1915
+ label="Select Connection Type",
1916
+ elem_classes="lcars-input"
1917
+ )
1918
+
1919
+ # Update the configuration panel
1920
+ with gr.Column(elem_classes="lcars-panel"):
1921
+ gr.Markdown("### πŸ”§ SYSTEM CONFIGURATION", elem_classes="panel-title")
1922
+ status_display = gr.Textbox()
1923
+ # Connection-specific settings
1924
+ with gr.Row(visible=False) as local_settings:
1925
+ base_url = gr.Textbox(
1926
+ value=LOCAL_BASE_URL,
1927
+ label="LM Studio URL",
1928
+ elem_classes="lcars-input"
1929
+ )
1930
+ api_key = gr.Textbox(
1931
+ value=LOCAL_API_KEY,
1932
+ label="API Key",
1933
+ type="password",
1934
+ elem_classes="lcars-input"
1935
+ )
1936
+
1937
+ with gr.Row(visible=True) as hf_settings:
1938
+ hf_api_key = gr.Textbox(
1939
+ value=HF_API_KEY,
1940
+ label="HuggingFace API Key",
1941
+ type="password",
1942
+ elem_classes="lcars-input",
1943
+ placeholder="Get from https://huggingface.co/settings/tokens"
1944
+ )
1945
+
1946
+ with gr.Row():
1947
+ model_dropdown = gr.Dropdown(
1948
+ choices=list(MODEL_OPTIONS.keys())[1:], # Start with HF models
1949
+ value=list(MODEL_OPTIONS.keys())[1],
1950
+ label="AI Model",
1951
+ elem_classes="lcars-input"
1952
+ )
1953
+ fetch_models_btn = gr.Button("πŸ“‘ Fetch Models", elem_classes="lcars-button")
1954
+
1955
+ # Update event handlers for connection switching
1956
+ def switch_connection(connection_type):
1957
+ """Switch between local and HF connection"""
1958
+ if connection_type == "Local LM Studio":
1959
+ return [
1960
+ gr.update(visible=True), # local_settings
1961
+ gr.update(visible=False), # hf_settings
1962
+ gr.update(choices=["Fetching local models..."], value="Fetching local models...")
1963
+ ]
1964
+ else:
1965
+ return [
1966
+ gr.update(visible=False), # local_settings
1967
+ gr.update(visible=True), # hf_settings
1968
+ gr.update(choices=list(MODEL_OPTIONS.keys())[1:], value=list(MODEL_OPTIONS.keys())[1])
1969
+ ]
1970
+
1971
+ connection_type.change(
1972
+ switch_connection,
1973
+ inputs=connection_type,
1974
+ outputs=[local_settings, hf_settings, model_dropdown]
1975
+ )
1976
+
1977
+ # Update model fetching for both connection types
1978
+ async def fetch_models_updated(connection_type, base_url_val, api_key_val, hf_api_key_val):
1979
+ if connection_type == "Local LM Studio":
1980
+ models = await EnhancedLLMAgent.fetch_available_models(
1981
+ base_url_val, api_key_val, use_huggingface=False
1982
+ )
1983
+ else:
1984
+ models = await EnhancedLLMAgent.fetch_available_models(
1985
+ "", hf_api_key_val, use_huggingface=True
1986
+ )
1987
+
1988
+ if models:
1989
+ return gr.update(choices=models, value=models[0])
1990
+ return gr.update(choices=["No models found"])
1991
+
1992
+ fetch_models_btn.click(
1993
+ fetch_models_updated,
1994
+ inputs=[connection_type, base_url, api_key, hf_api_key],
1995
+ outputs=model_dropdown
1996
+ )
1997
+
1998
+ # Update agent when connection changes
1999
+ def update_agent_connection(connection_type, model_id, base_url_val, api_key_val, hf_api_key_val):
2000
+ use_hf = connection_type == "HuggingFace Inference"
2001
+ self.use_huggingface = use_hf
2002
+
2003
+ if use_hf:
2004
+ self.agent = EnhancedLLMAgent(
2005
+ model_id=model_id,
2006
+ use_huggingface=True,
2007
+ api_key=hf_api_key_val
2008
+ )
2009
+ return f"βœ… Switched to HuggingFace: {model_id}"
2010
+ else:
2011
+ self.agent = EnhancedLLMAgent(
2012
+ model_id=model_id,
2013
+ base_url=base_url_val,
2014
+ api_key=api_key_val,
2015
+ use_huggingface=False
2016
+ )
2017
+ return f"βœ… Switched to Local: {base_url_val}"
2018
+
2019
+ model_dropdown.change(
2020
+ update_agent_connection,
2021
+ inputs=[connection_type, model_dropdown, base_url, api_key, hf_api_key],
2022
+ outputs=status_display
2023
+ )
2024
+ return interface
2025
+ # Update main function for Spaces compatibility
2026
+ def main():
2027
+ console.log("[bold blue]πŸš€ Starting LCARS Terminal...[/bold blue]")
2028
+
2029
+ # Auto-detect if we're in HuggingFace Spaces
2030
+ is_space = os.getenv('SPACE_ID') is not None
2031
+
2032
+ if is_space:
2033
+ console.log("[green]🌐 Detected HuggingFace Space - Using Inference API[/green]")
2034
+ else:
2035
+ console.log("[blue]πŸ’» Running locally - LM Studio available[/blue]")
2036
+
2037
+ interface = LcarsInterface()
2038
+ demo = interface.create_interface()
2039
+
2040
+ demo.launch(
2041
+ server_name="0.0.0.0" if is_space else "127.0.0.1",
2042
+ server_port=7860,
2043
+ share=is_space # Auto-share in Spaces
2044
+ )
2045
+
2046
+
2047
+ if __name__ == "__main__":
2048
+ main()