krinya commited on
Commit
96c920d
Β·
1 Parent(s): ec870c5

feat: Fix Gradio app integration and enhance console logging

Browse files

✨ Major Improvements:
- Fixed message passing issue in Gradio chat interface
- Added real-time console logging with auto-refresh
- Enhanced log display with better scrolling and formatting
- Fixed database column name from distributor_price to distributor_pricing
- Improved system prompt for more proactive database searches
- Added comprehensive log capturing from external libraries

πŸ› Bug Fixes:
- Resolved empty message issue in Gradio conversation flow
- Fixed tool invocation in web interface
- Corrected database schema references in system prompt

🎨 UI Enhancements:
- Added auto-refresh checkbox for real-time log updates
- Improved console styling with custom scrollbars
- Enhanced log display with 1000 entry capacity
- Better visual formatting and readability

πŸ”§ Technical:
- Added GradioLogHandler for capturing external library logs
- Improved conversation state management
- Enhanced error handling and debugging capabilities

src/sales_assistant/agent_tools/create_quote.py CHANGED
@@ -272,15 +272,21 @@ def create_quote(
272
  greeting = generate_dynamic_content(greeting_prompt)
273
 
274
  # Generate introduction
275
- product_summary = ", ".join([item.product_name for item in quote_items[:3]])
276
- if len(quote_items) > 3:
277
- product_summary += f" and {len(quote_items) - 3} other item(s)"
 
 
 
 
 
278
 
279
  intro_prompt = INTRO_PROMPT.format(
280
  customer_name=customer_name,
281
  company=customer_company or "your organization",
282
- product_summary=product_summary,
283
- item_count=len(quote_items)
 
284
  )
285
  introduction = generate_dynamic_content(intro_prompt)
286
 
 
272
  greeting = generate_dynamic_content(greeting_prompt)
273
 
274
  # Generate introduction
275
+ detailed_product_list = []
276
+ for item in quote_items:
277
+ if item.quantity > 1:
278
+ detailed_product_list.append(f"{item.quantity}x {item.product_name}")
279
+ else:
280
+ detailed_product_list.append(item.product_name)
281
+
282
+ product_list_str = ", ".join(detailed_product_list)
283
 
284
  intro_prompt = INTRO_PROMPT.format(
285
  customer_name=customer_name,
286
  company=customer_company or "your organization",
287
+ detailed_product_list=product_list_str,
288
+ unique_product_count=len(unique_product_ids),
289
+ total_item_count=sum(product_quantities.values())
290
  )
291
  introduction = generate_dynamic_content(intro_prompt)
292
 
src/sales_assistant/agent_tools/quote_template.py CHANGED
@@ -73,17 +73,20 @@ Company: {company}
73
  """
74
 
75
  INTRO_PROMPT = """
76
- Generate a professional introduction paragraph (2-3 sentences) for a sales quote.
77
  The introduction should:
78
- - Reference the products being quoted
79
- - Express confidence in meeting their needs
80
- - Be professional but friendly
81
- - Be concise (max 3 sentences)
 
 
82
 
83
  Customer: {customer_name}
84
  Company: {company}
85
- Products being quoted: {product_summary}
86
- Number of items: {item_count}
 
87
 
88
  Generate only the introduction paragraph, no additional formatting.
89
  """
@@ -137,15 +140,35 @@ def generate_product_description(product_info: dict, llm) -> str:
137
  return original_desc
138
 
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  def format_currency(amount: float, currency: str) -> str:
141
- """Format currency amount with proper symbol."""
 
 
 
 
 
142
  symbols = {"EUR": "€", "USD": "$", "HUF": "Ft"}
143
  symbol = symbols.get(currency, currency)
 
144
 
145
  if currency == "HUF":
146
- return f"{amount:,.0f} {symbol}"
147
  else:
148
- return f"{symbol}{amount:,.2f}"
149
 
150
 
151
  def generate_markdown_quote(quote: Quote) -> str:
@@ -163,23 +186,31 @@ def generate_markdown_quote(quote: Quote) -> str:
163
  if quote.customer.email:
164
  customer_section += f" \nπŸ“§ {quote.customer.email}"
165
 
166
- # Create Markdown table for products
167
  markdown_table = """
168
- | Product Name | Product ID | Description | Quantity | Unit Price | Total Price |
169
- |--------------|------------|-------------|----------|------------|-------------|
170
  """
171
 
172
  for item in quote.items:
173
  # Clean description for table (remove line breaks but keep full text)
174
  clean_desc = item.description.replace('\n', ' ').replace('\r', ' ')
175
 
176
- unit_price_str = format_currency(item.unit_price, quote.currency)
177
- total_price_str = format_currency(item.total_price, quote.currency)
 
 
 
 
 
178
 
179
- markdown_table += f"| {item.product_name} | {item.display_product_id} | {clean_desc} | {item.quantity} | {unit_price_str} | {total_price_str} |\n"
180
 
181
- # Grand total
182
- grand_total_str = format_currency(quote.grand_total, quote.currency)
 
 
 
183
 
184
  # Complete Markdown template
185
  markdown_template = f"""# Professional Product Quotation
 
73
  """
74
 
75
  INTRO_PROMPT = """
76
+ Generate a professional introduction paragraph (4-6 sentences) for a sales quote.
77
  The introduction should:
78
+ - Reference the specific products being quoted with their names
79
+ - Highlight key benefits or features of the product categories
80
+ - Express confidence in meeting their needs and providing value
81
+ - Mention the comprehensive nature of the solution if multiple products
82
+ - Be professional but friendly and engaging
83
+ - Be detailed but concise (4-6 sentences maximum)
84
 
85
  Customer: {customer_name}
86
  Company: {company}
87
+ Products being quoted: {detailed_product_list}
88
+ Number of different products: {unique_product_count}
89
+ Total items: {total_item_count}
90
 
91
  Generate only the introduction paragraph, no additional formatting.
92
  """
 
140
  return original_desc
141
 
142
 
143
+ def format_currency_number(amount: float, currency: str) -> str:
144
+ """Format currency amount as integer without symbol."""
145
+ if amount is None or amount == 0:
146
+ return "0"
147
+
148
+ # Round to integer
149
+ rounded_amount = round(amount)
150
+
151
+ if currency == "HUF":
152
+ return f"{rounded_amount:,}"
153
+ else:
154
+ return f"{rounded_amount:,}"
155
+
156
+
157
  def format_currency(amount: float, currency: str) -> str:
158
+ """Format currency amount with proper symbol - kept for backward compatibility."""
159
+ if amount is None or amount == 0:
160
+ symbols = {"EUR": "€", "USD": "$", "HUF": "Ft"}
161
+ symbol = symbols.get(currency, currency)
162
+ return f"0 {symbol}" if currency == "HUF" else f"{symbol}0"
163
+
164
  symbols = {"EUR": "€", "USD": "$", "HUF": "Ft"}
165
  symbol = symbols.get(currency, currency)
166
+ rounded_amount = round(amount)
167
 
168
  if currency == "HUF":
169
+ return f"{rounded_amount:,} {symbol}"
170
  else:
171
+ return f"{symbol}{rounded_amount:,}"
172
 
173
 
174
  def generate_markdown_quote(quote: Quote) -> str:
 
186
  if quote.customer.email:
187
  customer_section += f" \nπŸ“§ {quote.customer.email}"
188
 
189
+ # Create Markdown table for products with currency column
190
  markdown_table = """
191
+ | Product Name | Product ID | Description | Quantity | Unit Price | Currency | Total Price |
192
+ |--------------|------------|-------------|----------|------------|----------|-------------|
193
  """
194
 
195
  for item in quote.items:
196
  # Clean description for table (remove line breaks but keep full text)
197
  clean_desc = item.description.replace('\n', ' ').replace('\r', ' ')
198
 
199
+ # Handle missing prices
200
+ if item.unit_price is None or item.unit_price == 0:
201
+ unit_price_str = "TBD"
202
+ total_price_str = "TBD"
203
+ else:
204
+ unit_price_str = format_currency_number(item.unit_price, quote.currency)
205
+ total_price_str = format_currency_number(item.total_price, quote.currency)
206
 
207
+ markdown_table += f"| {item.product_name} | {item.display_product_id} | {clean_desc} | {item.quantity} | {unit_price_str} | {quote.currency} | {total_price_str} |\n"
208
 
209
+ # Grand total - handle case where some prices might be missing
210
+ if any(item.unit_price is None or item.unit_price == 0 for item in quote.items):
211
+ grand_total_str = f"TBD ({quote.currency})"
212
+ else:
213
+ grand_total_str = format_currency(quote.grand_total, quote.currency)
214
 
215
  # Complete Markdown template
216
  markdown_template = f"""# Professional Product Quotation
src/sales_assistant/prompts/system_prompt.py CHANGED
@@ -19,14 +19,16 @@ Help users explore products in a MySQL database and provide comprehensive sales
19
  </reasoning>
20
 
21
  ## Exploration Strategy
22
- 1. **Start Broad**: Begin with manufacturer/category exploration when user intent is unclear, check table schema if needed, check samples if needed
23
- 2. **Go Specific**: Use model names, numbers, or descriptions for targeted searches
24
- 3. **Be Creative**: Different manufacturers categorize differently - adapt your SQL approach
25
- 4. **Think Iteratively**: Use multiple queries to build a complete picture
 
26
 
27
  ## Key Guidelines
 
28
  - **Leverage Tool Descriptions**: Each tool has comprehensive examples - use them as guidance
29
- - **Start with Database Exploration**: When unsure, explore structure first using the sql tool
30
  - **Use Reasoning Annotations**: Think through your approach step-by-step
31
  - **Provide Specific Details**: Include pricing, model numbers, and specifications when available
32
  - **Ask Clarifying Questions**: If user intent is unclear after initial exploration
@@ -43,7 +45,7 @@ Focus on reasoning through the user's request and choosing the right tool with a
43
 
44
  ## Important Notes:
45
  - You are optimized for GPT-5-mini with advanced reasoning capabilities.
46
- - when a user asks product information use: id, model_number_short, model_number_long, manufacturer, model_name, category, sub_category, distributor_price, msrp, currency, description as much as possible to give a complete answer summerizing the product information based on this (just and example do not need to always use all).
47
  - or if a user asks for shorter answer you can leave out some of the fields.
48
  - description can be summarized do not write out what is there use your common senese
49
  - Use LIMITS in your SQL queries to avoid overwhelming results to use less tokens, if you need more results you can always ask for more.
 
19
  </reasoning>
20
 
21
  ## Exploration Strategy
22
+ 1. **Take Immediate Action**: When users ask about specific products, search directly using the product name/model
23
+ 2. **Be Proactive**: Use SQL queries to search for products immediately rather than asking for clarification
24
+ 3. **Search Creatively**: Use LIKE patterns to find products by name, model, or description
25
+ 4. **Start with Direct Search**: Search by product name first, then explore broader categories if nothing found
26
+ 5. **Think Iteratively**: Use multiple queries to build a complete picture
27
 
28
  ## Key Guidelines
29
+ - **Take Direct Action**: When users ask about specific products, immediately search the database
30
  - **Leverage Tool Descriptions**: Each tool has comprehensive examples - use them as guidance
31
+ - **Search First, Ask Later**: Attempt to find products before asking for clarification
32
  - **Use Reasoning Annotations**: Think through your approach step-by-step
33
  - **Provide Specific Details**: Include pricing, model numbers, and specifications when available
34
  - **Ask Clarifying Questions**: If user intent is unclear after initial exploration
 
45
 
46
  ## Important Notes:
47
  - You are optimized for GPT-5-mini with advanced reasoning capabilities.
48
+ - when a user asks product information use: id, model_number_short, model_number_long, manufacturer, model_name, category, sub_category, distributor_pricing, msrp, currency, description as much as possible to give a complete answer summerizing the product information based on this (just and example do not need to always use all).
49
  - or if a user asks for shorter answer you can leave out some of the fields.
50
  - description can be summarized do not write out what is there use your common senese
51
  - Use LIMITS in your SQL queries to avoid overwhelming results to use less tokens, if you need more results you can always ask for more.
src/sales_assistant/ui_dashboard/gradio_app.py CHANGED
@@ -9,6 +9,10 @@ import gradio as gr
9
  from typing import List, Tuple, Dict
10
  from datetime import datetime
11
  import glob
 
 
 
 
12
 
13
  # Add the src directory to the path
14
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
@@ -23,6 +27,32 @@ from sales_assistant.agent_main.agent_runner import (
23
  )
24
 
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  class SalesAssistantChat:
27
  """Chat interface for the sales assistant."""
28
 
@@ -33,8 +63,53 @@ class SalesAssistantChat:
33
  self.callback_manager = None
34
  self.thread_id = None
35
  self.quotes_dir = os.path.join(os.path.dirname(__file__), '../created_quotes')
 
 
 
 
 
 
36
  self.initialize_agent()
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  def get_quote_files(self) -> List[Tuple[str, str, str]]:
39
  """Get list of quote files with metadata."""
40
  quote_files = []
@@ -74,69 +149,117 @@ class SalesAssistantChat:
74
  def initialize_agent(self):
75
  """Initialize the agent runner."""
76
  try:
 
 
77
  # Check environment setup
78
  if not check_environment_setup():
79
- print("❌ Environment setup failed")
80
  return
81
 
82
- # Create configuration
 
 
 
 
83
  config = create_custom_config(
84
- session_id="gradio_session",
85
  user_id="gradio_user",
86
  enable_langsmith=True
87
  )
88
 
 
 
89
  # Create agent runner
90
  self.compiled_graph, self.checkpointer, self.callback_manager, self.thread_id = create_agent_runner(config)
91
- print("βœ… Sales Assistant initialized successfully!")
92
 
93
  except Exception as e:
94
- print(f"❌ Failed to initialize sales assistant: {e}")
95
  self.compiled_graph = None
96
 
97
- def chat_function(self, message: str, history: List[Dict[str, str]]) -> Tuple[str, List[Dict[str, str]], List[Tuple[str, str, str]]]:
98
  """
99
- Process a chat message and return the response.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- Args:
102
- message: User's input message
103
- history: Chat history as list of message dictionaries
104
-
105
  Returns:
106
- Tuple of (empty_string, updated_history, updated_quote_files)
107
  """
 
 
 
108
  if not self.compiled_graph:
109
  error_response = "❌ Sales Assistant is not properly initialized. Please check your environment configuration."
110
- history.append({"role": "user", "content": message})
111
- history.append({"role": "assistant", "content": error_response})
112
- return "", history, self.get_quote_files()
113
-
 
 
 
114
  try:
 
 
 
 
 
115
  # Run conversation turn
116
  response = run_conversation_turn(
117
  compiled_graph=self.compiled_graph,
118
  thread_id=self.thread_id,
119
- user_input=message,
120
  callback_manager=self.callback_manager
121
  )
122
 
123
- # Add to history
124
- history.append({"role": "user", "content": message})
125
- history.append({"role": "assistant", "content": response})
126
 
127
- # Check if a new quote was generated by looking for "Quote generated" or similar in response
 
 
 
 
128
  if "quote" in response.lower() and ("generated" in response.lower() or "created" in response.lower()):
129
  response += "\n\nπŸ“„ **Check the 'Generated Quotes' tab to view your quote file!**"
130
  history[-1] = {"role": "assistant", "content": response}
 
131
 
132
  except Exception as e:
133
  error_response = f"❌ Error processing your request: {str(e)}"
134
- history.append({"role": "user", "content": message})
135
- history.append({"role": "assistant", "content": error_response})
 
 
 
136
 
137
- return "", history, self.get_quote_files()
138
-
139
-
 
 
 
 
 
140
  def create_gradio_interface():
141
  """Create and configure the Gradio interface."""
142
 
@@ -155,6 +278,35 @@ def create_gradio_interface():
155
  padding: 20px !important;
156
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
157
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  /* ensure main text elements inherit the font */
159
  .gradio-markdown, .gradio-chatbot, .gradio-textbox, .gradio-button, .gradio-accordion {
160
  font-family: inherit;
@@ -208,6 +360,28 @@ def create_gradio_interface():
208
  )
209
  send_btn = gr.Button("Send", variant="primary", scale=1)
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  # Example queries
212
  with gr.Row():
213
  gr.Examples(
@@ -283,8 +457,13 @@ def create_gradio_interface():
283
  )
284
 
285
  # Event handlers
286
- def submit_message(message, history):
287
- return chat_assistant.chat_function(message, history)
 
 
 
 
 
288
 
289
  def refresh_files():
290
  files = chat_assistant.get_quote_files()
@@ -297,17 +476,40 @@ def create_gradio_interface():
297
  return content
298
  return "Select a quote file to view its content."
299
 
300
- # Wire up the events
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  msg_input.submit(
302
- submit_message,
 
 
 
 
303
  inputs=[msg_input, chatbot],
304
- outputs=[msg_input, chatbot, quote_files_display]
305
  )
306
 
307
  send_btn.click(
308
- submit_message,
309
  inputs=[msg_input, chatbot],
310
- outputs=[msg_input, chatbot, quote_files_display]
 
 
 
 
311
  )
312
 
313
  refresh_btn.click(
@@ -320,6 +522,28 @@ def create_gradio_interface():
320
  inputs=[file_dropdown],
321
  outputs=[quote_content]
322
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
  return interface
325
 
@@ -336,7 +560,8 @@ def main():
336
  server_name="0.0.0.0",
337
  server_port=7860,
338
  share=False,
339
- show_error=True
 
340
  )
341
 
342
 
 
9
  from typing import List, Tuple, Dict
10
  from datetime import datetime
11
  import glob
12
+ import threading
13
+ import time
14
+ import logging
15
+ import io
16
 
17
  # Add the src directory to the path
18
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
 
27
  )
28
 
29
 
30
+ class GradioLogHandler(logging.Handler):
31
+ """Custom log handler to capture all logs for Gradio console."""
32
+
33
+ def __init__(self, chat_assistant):
34
+ super().__init__()
35
+ self.chat_assistant = chat_assistant
36
+
37
+ def emit(self, record):
38
+ try:
39
+ # Format the log message
40
+ log_entry = self.format(record)
41
+ # Add to Gradio console (without timestamp since we add our own)
42
+ if hasattr(self.chat_assistant, 'console_logs'):
43
+ timestamp = datetime.now().strftime("%H:%M:%S")
44
+ level = record.levelname
45
+ message = record.getMessage()
46
+ formatted_entry = f"[{timestamp}] {level}: {message}"
47
+ self.chat_assistant.console_logs.append(formatted_entry)
48
+
49
+ # Keep only the last max_logs entries
50
+ if len(self.chat_assistant.console_logs) > self.chat_assistant.max_logs:
51
+ self.chat_assistant.console_logs.pop(0)
52
+ except Exception:
53
+ pass # Ignore errors in logging
54
+
55
+
56
  class SalesAssistantChat:
57
  """Chat interface for the sales assistant."""
58
 
 
63
  self.callback_manager = None
64
  self.thread_id = None
65
  self.quotes_dir = os.path.join(os.path.dirname(__file__), '../created_quotes')
66
+ self.console_logs = []
67
+ self.max_logs = 1000 # Increased from 500 to store more logs
68
+ self.current_message = "" # Store current message for processing
69
+
70
+ # Set up logging to capture external logs
71
+ self.setup_logging()
72
  self.initialize_agent()
73
 
74
+ def setup_logging(self):
75
+ """Set up logging to capture all relevant logs."""
76
+ # Create custom handler
77
+ self.log_handler = GradioLogHandler(self)
78
+ formatter = logging.Formatter('%(name)s: %(message)s')
79
+ self.log_handler.setFormatter(formatter)
80
+
81
+ # Add handler to relevant loggers
82
+ loggers_to_capture = [
83
+ 'sales_assistant.agent_tools.agent_tools_utils',
84
+ 'paramiko.transport',
85
+ 'sshtunnel',
86
+ 'httpx',
87
+ 'src.sales_assistant.agent_tools.agent_tools_utils'
88
+ ]
89
+
90
+ for logger_name in loggers_to_capture:
91
+ logger = logging.getLogger(logger_name)
92
+ logger.addHandler(self.log_handler)
93
+ logger.setLevel(logging.INFO)
94
+
95
+ def log_to_console(self, message: str, level: str = "INFO"):
96
+ """Add a message to console logs with timestamp."""
97
+ timestamp = datetime.now().strftime("%H:%M:%S")
98
+ log_entry = f"[{timestamp}] {level}: {message}"
99
+ self.console_logs.append(log_entry)
100
+
101
+ # Keep only the last max_logs entries
102
+ if len(self.console_logs) > self.max_logs:
103
+ self.console_logs.pop(0)
104
+
105
+ print(log_entry) # Also print to actual console
106
+
107
+ def get_console_logs(self) -> str:
108
+ """Get formatted console logs."""
109
+ if not self.console_logs:
110
+ return "No logs available yet..."
111
+ return "\n".join(self.console_logs) # Show all logs instead of just last 20
112
+
113
  def get_quote_files(self) -> List[Tuple[str, str, str]]:
114
  """Get list of quote files with metadata."""
115
  quote_files = []
 
149
  def initialize_agent(self):
150
  """Initialize the agent runner."""
151
  try:
152
+ self.log_to_console("Starting agent initialization...")
153
+
154
  # Check environment setup
155
  if not check_environment_setup():
156
+ self.log_to_console("Environment setup failed", "ERROR")
157
  return
158
 
159
+ self.log_to_console("Environment setup completed")
160
+
161
+ # Create configuration with unique session ID
162
+ import uuid
163
+ unique_session_id = f"gradio_session_{uuid.uuid4().hex[:8]}"
164
  config = create_custom_config(
165
+ session_id=unique_session_id,
166
  user_id="gradio_user",
167
  enable_langsmith=True
168
  )
169
 
170
+ self.log_to_console(f"Configuration created with session: {unique_session_id}")
171
+
172
  # Create agent runner
173
  self.compiled_graph, self.checkpointer, self.callback_manager, self.thread_id = create_agent_runner(config)
174
+ self.log_to_console("Sales Assistant initialized successfully!", "SUCCESS")
175
 
176
  except Exception as e:
177
+ self.log_to_console(f"Failed to initialize sales assistant: {e}", "ERROR")
178
  self.compiled_graph = None
179
 
180
+ def chat_function_immediate(self, message: str, history: List[Dict[str, str]]) -> Tuple[str, List[Dict[str, str]], str]:
181
  """
182
+ Immediately show user message and loading indicator.
183
+
184
+ Returns:
185
+ Tuple of (empty_string, updated_history_with_loading, console_logs)
186
+ """
187
+ if not message.strip():
188
+ return "", history, self.get_console_logs()
189
+
190
+ # Store the message for later processing
191
+ self.current_message = message
192
+
193
+ # Immediately add user message and loading indicator
194
+ history.append({"role": "user", "content": message})
195
+ history.append({"role": "assistant", "content": "πŸ€” Thinking... Please wait while I process your request."})
196
+
197
+ self.log_to_console(f"User message received: {message[:50]}...")
198
+
199
+ return "", history, self.get_console_logs()
200
+
201
+ def chat_function_process(self, message: str, history: List[Dict[str, str]]) -> Tuple[str, List[Dict[str, str]], List[Tuple[str, str, str]], str]:
202
+ """
203
+ Process the actual chat message and return the response.
204
 
 
 
 
 
205
  Returns:
206
+ Tuple of (empty_string, updated_history, updated_quote_files, console_logs)
207
  """
208
+ # Use the stored message instead of the parameter (which might be empty)
209
+ actual_message = self.current_message if self.current_message else message
210
+
211
  if not self.compiled_graph:
212
  error_response = "❌ Sales Assistant is not properly initialized. Please check your environment configuration."
213
+ # Replace the loading message with error
214
+ if history and history[-1]["role"] == "assistant":
215
+ history[-1] = {"role": "assistant", "content": error_response}
216
+
217
+ self.log_to_console("Agent not initialized - cannot process request", "ERROR")
218
+ return "", history, self.get_quote_files(), self.get_console_logs()
219
+
220
  try:
221
+ self.log_to_console("Starting conversation turn...")
222
+ self.log_to_console("Querying database and analyzing request...")
223
+ self.log_to_console(f"Thread ID: {self.thread_id}")
224
+ self.log_to_console(f"Processing message: {actual_message}")
225
+
226
  # Run conversation turn
227
  response = run_conversation_turn(
228
  compiled_graph=self.compiled_graph,
229
  thread_id=self.thread_id,
230
+ user_input=actual_message,
231
  callback_manager=self.callback_manager
232
  )
233
 
234
+ self.log_to_console(f"Raw AI response: {response[:200]}...")
235
+ self.log_to_console("AI response generated successfully")
 
236
 
237
+ # Replace the loading message with actual response
238
+ if history and history[-1]["role"] == "assistant":
239
+ history[-1] = {"role": "assistant", "content": response}
240
+
241
+ # Check if a new quote was generated
242
  if "quote" in response.lower() and ("generated" in response.lower() or "created" in response.lower()):
243
  response += "\n\nπŸ“„ **Check the 'Generated Quotes' tab to view your quote file!**"
244
  history[-1] = {"role": "assistant", "content": response}
245
+ self.log_to_console("Quote file generated and saved")
246
 
247
  except Exception as e:
248
  error_response = f"❌ Error processing your request: {str(e)}"
249
+ # Replace the loading message with error
250
+ if history and history[-1]["role"] == "assistant":
251
+ history[-1] = {"role": "assistant", "content": error_response}
252
+
253
+ self.log_to_console(f"Error processing request: {str(e)}", "ERROR")
254
 
255
+ # Clear the stored message after processing
256
+ self.current_message = ""
257
+
258
+ return "", history, self.get_quote_files(), self.get_console_logs()
259
+
260
+ def get_intermediate_logs(self) -> str:
261
+ """Get console logs for intermediate updates."""
262
+ return self.get_console_logs()
263
  def create_gradio_interface():
264
  """Create and configure the Gradio interface."""
265
 
 
278
  padding: 20px !important;
279
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
280
  }
281
+ .console-logs {
282
+ background-color: #1a1a1a;
283
+ color: #00ff00;
284
+ font-family: 'Courier New', monospace;
285
+ padding: 15px;
286
+ border-radius: 8px;
287
+ height: 400px !important;
288
+ max-height: 500px !important;
289
+ overflow-y: auto !important;
290
+ font-size: 12px;
291
+ line-height: 1.5;
292
+ border: 1px solid #333;
293
+ white-space: pre-wrap;
294
+ word-wrap: break-word;
295
+ }
296
+ .console-logs::-webkit-scrollbar {
297
+ width: 8px;
298
+ }
299
+ .console-logs::-webkit-scrollbar-track {
300
+ background: #2a2a2a;
301
+ border-radius: 4px;
302
+ }
303
+ .console-logs::-webkit-scrollbar-thumb {
304
+ background: #555;
305
+ border-radius: 4px;
306
+ }
307
+ .console-logs::-webkit-scrollbar-thumb:hover {
308
+ background: #777;
309
+ }
310
  /* ensure main text elements inherit the font */
311
  .gradio-markdown, .gradio-chatbot, .gradio-textbox, .gradio-button, .gradio-accordion {
312
  font-family: inherit;
 
360
  )
361
  send_btn = gr.Button("Send", variant="primary", scale=1)
362
 
363
+ # Console logs section
364
+ with gr.Accordion("πŸ” Console Logs & Processing Info", open=False):
365
+ console_display = gr.Textbox(
366
+ value=chat_assistant.get_console_logs(),
367
+ label="Real-time Processing Logs",
368
+ lines=20,
369
+ max_lines=30,
370
+ interactive=False,
371
+ elem_classes=["console-logs"],
372
+ show_copy_button=True,
373
+ autoscroll=True
374
+ )
375
+
376
+ with gr.Row():
377
+ refresh_logs_btn = gr.Button("πŸ”„ Refresh Logs", variant="secondary", scale=1)
378
+ clear_logs_btn = gr.Button("πŸ—‘οΈ Clear Logs", variant="secondary", scale=1)
379
+ auto_refresh_checkbox = gr.Checkbox(
380
+ label="Auto-refresh logs (every 2 seconds)",
381
+ value=False,
382
+ scale=1
383
+ )
384
+
385
  # Example queries
386
  with gr.Row():
387
  gr.Examples(
 
457
  )
458
 
459
  # Event handlers
460
+ def submit_message_immediate(message, history):
461
+ """Immediately show user message and loading state."""
462
+ return chat_assistant.chat_function_immediate(message, history)
463
+
464
+ def process_message_full(message, history):
465
+ """Process the full message after showing immediate feedback."""
466
+ return chat_assistant.chat_function_process(message, history)
467
 
468
  def refresh_files():
469
  files = chat_assistant.get_quote_files()
 
476
  return content
477
  return "Select a quote file to view its content."
478
 
479
+ def refresh_console_logs():
480
+ return chat_assistant.get_console_logs()
481
+
482
+ def clear_console_logs():
483
+ chat_assistant.console_logs = []
484
+ chat_assistant.log_to_console("Console logs cleared")
485
+ return chat_assistant.get_console_logs()
486
+
487
+ def auto_refresh_logs():
488
+ """Auto-refresh function for logs - returns updated logs"""
489
+ return chat_assistant.get_console_logs()
490
+
491
+ # Set up auto-refresh timer
492
+ timer = gr.Timer(2) # Refresh every 2 seconds
493
+
494
+ # Wire up the events with better UX
495
  msg_input.submit(
496
+ submit_message_immediate,
497
+ inputs=[msg_input, chatbot],
498
+ outputs=[msg_input, chatbot, console_display]
499
+ ).then(
500
+ process_message_full,
501
  inputs=[msg_input, chatbot],
502
+ outputs=[msg_input, chatbot, quote_files_display, console_display]
503
  )
504
 
505
  send_btn.click(
506
+ submit_message_immediate,
507
  inputs=[msg_input, chatbot],
508
+ outputs=[msg_input, chatbot, console_display]
509
+ ).then(
510
+ process_message_full,
511
+ inputs=[msg_input, chatbot],
512
+ outputs=[msg_input, chatbot, quote_files_display, console_display]
513
  )
514
 
515
  refresh_btn.click(
 
522
  inputs=[file_dropdown],
523
  outputs=[quote_content]
524
  )
525
+
526
+ refresh_logs_btn.click(
527
+ refresh_console_logs,
528
+ outputs=[console_display]
529
+ )
530
+
531
+ clear_logs_btn.click(
532
+ clear_console_logs,
533
+ outputs=[console_display]
534
+ )
535
+
536
+ # Auto-refresh functionality
537
+ auto_refresh_checkbox.change(
538
+ lambda enabled: timer.start() if enabled else timer.stop(),
539
+ inputs=[auto_refresh_checkbox],
540
+ outputs=[]
541
+ )
542
+
543
+ timer.tick(
544
+ auto_refresh_logs,
545
+ outputs=[console_display]
546
+ )
547
 
548
  return interface
549
 
 
560
  server_name="0.0.0.0",
561
  server_port=7860,
562
  share=False,
563
+ show_error=True,
564
+ debug=True
565
  )
566
 
567