krinya commited on
Commit
cb6cec5
Β·
1 Parent(s): b5d15eb

Update quote template and creation tools, improve UI dashboard

Browse files
src/sales_assistant/agent_tools/create_quote.py CHANGED
@@ -16,7 +16,9 @@ from .quote_template import (
16
  Quote, QuoteItem, CustomerInfo,
17
  generate_markdown_quote,
18
  GREETING_PROMPT, INTRO_PROMPT,
19
- generate_product_description
 
 
20
  )
21
  from .get_exchange_rates import convert_amount
22
  from .agent_tools_utils import get_data_for_agent
@@ -224,26 +226,37 @@ def create_quote(
224
  temperature=0.3
225
  )
226
 
227
- # Create quote items with quantities and LLM-generated descriptions
228
  quote_items = []
229
  for product_data in products_data:
230
  product_id = product_data['id']
231
  quantity = product_quantities[product_id]
232
 
233
- # Generate concise description using LLM
234
  product_info = {
 
235
  'manufacturer': product_data['manufacturer'],
236
  'model_name': product_data['product_name'],
 
 
237
  'category': product_data['category'],
238
  'sub_category': product_data['sub_category'],
239
  'description': product_data['original_description']
240
  }
 
 
 
 
 
 
 
 
241
  llm_description = generate_product_description(product_info, llm)
242
 
243
  quote_item = QuoteItem(
244
- product_id=product_id,
245
- product_name=product_data['product_name'],
246
- model_number_short=product_data['model_number_short'],
247
  model_number_long=product_data['model_number_long'],
248
  description=llm_description,
249
  quantity=quantity,
@@ -275,9 +288,9 @@ def create_quote(
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
 
 
16
  Quote, QuoteItem, CustomerInfo,
17
  generate_markdown_quote,
18
  GREETING_PROMPT, INTRO_PROMPT,
19
+ generate_product_description,
20
+ generate_product_name,
21
+ generate_product_id
22
  )
23
  from .get_exchange_rates import convert_amount
24
  from .agent_tools_utils import get_data_for_agent
 
226
  temperature=0.3
227
  )
228
 
229
+ # Create quote items with quantities and LLM-generated descriptions, names, and IDs
230
  quote_items = []
231
  for product_data in products_data:
232
  product_id = product_data['id']
233
  quantity = product_quantities[product_id]
234
 
235
+ # Prepare product info for AI generation
236
  product_info = {
237
+ 'id': product_data['id'],
238
  'manufacturer': product_data['manufacturer'],
239
  'model_name': product_data['product_name'],
240
+ 'model_number_short': product_data['model_number_short'],
241
+ 'model_number_long': product_data['model_number_long'],
242
  'category': product_data['category'],
243
  'sub_category': product_data['sub_category'],
244
  'description': product_data['original_description']
245
  }
246
+
247
+ # Generate AI-enhanced product name
248
+ ai_product_name = generate_product_name(product_info, llm)
249
+
250
+ # Generate AI-selected product ID
251
+ ai_product_id = generate_product_id(product_info, llm)
252
+
253
+ # Generate concise description using LLM
254
  llm_description = generate_product_description(product_info, llm)
255
 
256
  quote_item = QuoteItem(
257
+ product_id=product_id, # Keep database ID for internal reference
258
+ product_name=ai_product_name, # AI-generated name
259
+ model_number_short=ai_product_id, # AI-selected ID for display
260
  model_number_long=product_data['model_number_long'],
261
  description=llm_description,
262
  quantity=quantity,
 
288
  detailed_product_list = []
289
  for item in quote_items:
290
  if item.quantity > 1:
291
+ detailed_product_list.append(f"{item.quantity}x {item.product_name}") # Using AI-generated name
292
  else:
293
+ detailed_product_list.append(item.product_name) # Using AI-generated name
294
 
295
  product_list_str = ", ".join(detailed_product_list)
296
 
src/sales_assistant/agent_tools/quote_template.py CHANGED
@@ -74,10 +74,13 @@ Company: {company}
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)
@@ -101,10 +104,69 @@ Given the following product information:
101
  - Current Description: {current_description}
102
 
103
  Write a professional, concise product description in less than 30 words.
104
- Focus on key features, capabilities, and benefits. Make it suitable for a business quote.
 
 
 
 
105
 
106
  Write only the description of the product, no additional text."""
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
  def generate_product_description(product_info: dict, llm) -> str:
110
  """
@@ -140,6 +202,88 @@ def generate_product_description(product_info: dict, llm) -> str:
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:
@@ -188,8 +332,8 @@ def generate_markdown_quote(quote: Quote) -> str:
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:
@@ -204,7 +348,7 @@ def generate_markdown_quote(quote: Quote) -> str:
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):
 
74
 
75
  INTRO_PROMPT = """
76
  Generate a professional introduction paragraph (4-6 sentences) for a sales quote.
77
+
78
+ Background:
79
+ - We are a company called: StreamNet and we provide video conferencing and networking solutions.
80
+
81
  The introduction should:
82
+ - Reference the specific products being quoted
83
+ - Highlight key benefits separately and together if multiple products
 
84
  - Mention the comprehensive nature of the solution if multiple products
85
  - Be professional but friendly and engaging
86
  - Be detailed but concise (4-6 sentences maximum)
 
104
  - Current Description: {current_description}
105
 
106
  Write a professional, concise product description in less than 30 words.
107
+ Focus on key features, capabilities, and benefits. Make it suitable for part of a business quote.
108
+
109
+ The description should be:
110
+ - Concise and to the point (max 30 words)
111
+ - Do not need to mention product name and product id as this is already written in other parts of the quote
112
 
113
  Write only the description of the product, no additional text."""
114
 
115
+ PRODUCT_NAME_GENERATION_PROMPT = """You are a product naming specialist for technology equipment quotes.
116
+
117
+ Given the following product information:
118
+ - Manufacturer: {manufacturer}
119
+ - Model Name: {model_name}
120
+ - Model Number Short: {model_number_short}
121
+ - Model Number Long: {model_number_long}
122
+ - Category: {category}
123
+ - Sub-category: {sub_category}
124
+ - Description: {description}
125
+
126
+ Create a professional, clear product name
127
+ The name should be:
128
+ - Concise but informativerefering to the brand and model name
129
+ - Include manufacturer/brand name
130
+ - Do not include id or SKU unless it's part of the model name as another field will do that
131
+ - Professional and suitable for business quotes
132
+ - Easy to understand for customers
133
+ - capitalize first letter if it is not like that
134
+
135
+ Good Examples:
136
+ - "Cisco Catalyst Switch"
137
+ - "Dell PowerEdge Server"
138
+ - "HP EliteBook Laptop"
139
+
140
+ Bad Examples:
141
+ - "Cisco Catalyst 9300-24T-A Switch"
142
+ - "Dell PowerEdge R740xd Server"
143
+ - "HP EliteBook 850 G7 Laptop"
144
+
145
+ Write only the product name, no additional text."""
146
+
147
+ PRODUCT_ID_GENERATION_PROMPT = """You are a product identifier specialist for technology equipment quotes.
148
+
149
+ Given the following product information:
150
+ - Manufacturer: {manufacturer}
151
+ - Model Name: {model_name}
152
+ - Model Number Short: {model_number_short}
153
+ - Model Number Long: {model_number_long}
154
+ - Category: {category}
155
+ - Sub-category: {sub_category}
156
+ - Description: {description}
157
+
158
+ Generate the most appropriate product ID/SKU for display in a quote.
159
+ Priority order:
160
+ 1. combine model_number_short if it looks like a proper product ID/SKU together
161
+ 2. If does not exist or is not suitable generate an empty string
162
+
163
+ The product ID should be:
164
+ - Professional and recognizable
165
+ - Useful for customers to reference
166
+ - Concise but informative
167
+
168
+ Write only the product ID, no additional text."""
169
+
170
 
171
  def generate_product_description(product_info: dict, llm) -> str:
172
  """
 
202
  return original_desc
203
 
204
 
205
+ def generate_product_name(product_info: dict, llm) -> str:
206
+ """
207
+ Generate a professional product name using LLM.
208
+
209
+ Args:
210
+ product_info: Dictionary containing product details
211
+ llm: Language model instance
212
+
213
+ Returns:
214
+ Generated product name (max 8 words)
215
+ """
216
+ try:
217
+ prompt = PRODUCT_NAME_GENERATION_PROMPT.format(
218
+ manufacturer=product_info.get('manufacturer', 'N/A'),
219
+ model_name=product_info.get('model_name', 'N/A'),
220
+ model_number_short=product_info.get('model_number_short', 'N/A'),
221
+ model_number_long=product_info.get('model_number_long', 'N/A'),
222
+ category=product_info.get('category', 'N/A'),
223
+ sub_category=product_info.get('sub_category', 'N/A')
224
+ )
225
+
226
+ response = llm.invoke(prompt)
227
+ product_name = response.content.strip()
228
+
229
+ return product_name
230
+
231
+ except Exception as e:
232
+ # Fallback to constructing name from available data
233
+ manufacturer = product_info.get('manufacturer', '')
234
+ model_name = product_info.get('model_name', '')
235
+ model_short = product_info.get('model_number_short', '')
236
+
237
+ if manufacturer and model_name:
238
+ return f"{manufacturer} {model_name}"
239
+ elif manufacturer and model_short:
240
+ return f"{manufacturer} {model_short}"
241
+ elif model_name:
242
+ return model_name
243
+ else:
244
+ return f"Product {product_info.get('id', 'Unknown')}"
245
+
246
+
247
+ def generate_product_id(product_info: dict, llm) -> str:
248
+ """
249
+ Generate an appropriate product ID using LLM.
250
+
251
+ Args:
252
+ product_info: Dictionary containing product details
253
+ llm: Language model instance
254
+
255
+ Returns:
256
+ Generated or selected product ID
257
+ """
258
+ try:
259
+ prompt = PRODUCT_ID_GENERATION_PROMPT.format(
260
+ manufacturer=product_info.get('manufacturer', 'N/A'),
261
+ model_name=product_info.get('model_name', 'N/A'),
262
+ model_number_short=product_info.get('model_number_short', 'N/A'),
263
+ model_number_long=product_info.get('model_number_long', 'N/A'),
264
+ category=product_info.get('category', 'N/A'),
265
+ sub_category=product_info.get('sub_category', 'N/A')
266
+ )
267
+
268
+ response = llm.invoke(prompt)
269
+ product_id = response.content.strip()
270
+
271
+ return product_id
272
+
273
+ except Exception as e:
274
+ # Fallback logic: prefer model_number_short > model_number_long > database_id
275
+ model_short = product_info.get('model_number_short')
276
+ model_long = product_info.get('model_number_long')
277
+ database_id = product_info.get('id')
278
+
279
+ if model_short:
280
+ return str(model_short)
281
+ elif model_long:
282
+ return str(model_long)
283
+ else:
284
+ return str(database_id)
285
+
286
+
287
  def format_currency_number(amount: float, currency: str) -> str:
288
  """Format currency amount as integer without symbol."""
289
  if amount is None or amount == 0:
 
332
 
333
  # Create Markdown table for products with currency column
334
  markdown_table = """
335
+ | Product Name | Product ID | Description | Quantity | Currency | Unit Price | Total Price |
336
+ |--------------|------------|-------------|----------|----------|------------|-------------|
337
  """
338
 
339
  for item in quote.items:
 
348
  unit_price_str = format_currency_number(item.unit_price, quote.currency)
349
  total_price_str = format_currency_number(item.total_price, quote.currency)
350
 
351
+ markdown_table += f"| {item.product_name} | {item.display_product_id} | {clean_desc} | {item.quantity} | {quote.currency} | {unit_price_str} | {total_price_str} |\n"
352
 
353
  # Grand total - handle case where some prices might be missing
354
  if any(item.unit_price is None or item.unit_price == 0 for item in quote.items):
src/sales_assistant/created_quotes/quote_QT-B4849588_Kristof_Proba_20250825_114359.md DELETED
@@ -1,63 +0,0 @@
1
- # Professional Product Quotation
2
-
3
- ## STREAMNET SOLUTIONS
4
-
5
- ---
6
-
7
- **Quote ID:** QT-B4849588
8
- **Date:** August 25, 2025
9
- **Valid Until:** September 24, 2025
10
-
11
- ---
12
-
13
- ## Quote To:
14
-
15
- **Kristof Proba**
16
- πŸ“§ kristofkristof@gmail.com
17
-
18
- ---
19
-
20
- Option 1: Dear Kristof Proba, thank you for your interestβ€”please find your tailored quotation below.
21
-
22
- Option 2: Hello Kristof, we’re pleased to provide the following quote and look forward to assisting you.
23
-
24
- Kristof Proba, please find a quote for two items: the Saber U20 (WHITE) and the Saber 5X (Saber Light). We’re confident these products will meet your needs and deliver the reliable performance you expect.
25
-
26
- ---
27
-
28
- ## Products
29
-
30
-
31
- | Product Name | Product ID | Description | Quantity | Unit Price | Total Price |
32
- |--------------|------------|-------------|----------|------------|-------------|
33
- | Saber U20 (WHITE) | ANG2-20FHD-01W | Angekis Saber U20 (White) β€” compact professional camera delivering high-quality imaging, reliable performance, versatile mounting and connectivity, and a discreet white finish for seamless commercial integration. | 2 | 312,460 Ft | 624,920 Ft |
34
- | Saber 5X (Saber Light) | U3-5FHD6 | angekis Saber 5X (Saber Light) PTZ camera β€” 5Γ— zoom, 90Β° HFOV, simultaneous USB 3.0 60 fps and RS232 connectivity for high-frame-rate capture and seamless integration. | 3 | 321,300 Ft | 963,900 Ft |
35
-
36
-
37
- ---
38
-
39
- ## **Grand Total: 1,588,820 Ft**
40
-
41
- ---
42
-
43
- ## Terms and Conditions
44
-
45
- - This quotation is valid for 30 days from the date of issue
46
- - Prices are subject to change without notice after expiration
47
- - Payment terms: Net 30 days from invoice date
48
- - All prices exclude shipping and handling unless otherwise specified
49
- - Products are subject to availability
50
- - Technical specifications may vary, please confirm before ordering
51
- - Returns accepted within 14 days in original condition
52
- - Warranty terms as per manufacturer specifications
53
-
54
- ---
55
-
56
- ### Contact Information
57
-
58
- πŸ“§ **Email:** sales@streamnet.com
59
- πŸ“ž **Phone:** +1 (555) 123-4567
60
-
61
- ---
62
-
63
- *Generated on August 25, 2025*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/sales_assistant/ui_dashboard/gradio_app.py CHANGED
@@ -172,6 +172,7 @@ class SalesAssistantChat:
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")
@@ -260,12 +261,14 @@ class SalesAssistantChat:
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
 
266
- # Initialize the chat assistant
267
- chat_assistant = SalesAssistantChat()
268
-
269
  # Create the interface
270
  with gr.Blocks(
271
  title="Streamnet Sales Assistant with Quote Generation",
@@ -316,11 +319,39 @@ def create_gradio_interface():
316
  max-width: none !important;
317
  width: 100% !important;
318
  }
 
 
 
 
 
 
 
 
 
319
  """
320
  ) as interface:
321
  # load Google Font (Inter)
322
  gr.HTML('<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap" rel="stylesheet">')
323
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  gr.Markdown(
325
  """
326
  # πŸ€– Streamnet Sales Assistant with Quote Generation
@@ -363,7 +394,7 @@ def create_gradio_interface():
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,
@@ -407,7 +438,7 @@ def create_gradio_interface():
407
  quote_files_display = gr.Dataframe(
408
  headers=["File Name", "Modified", "Size"],
409
  datatype=["str", "str", "str"],
410
- value=chat_assistant.get_quote_files(),
411
  label="Quote Files",
412
  interactive=False
413
  )
@@ -416,7 +447,7 @@ def create_gradio_interface():
416
 
417
  # File selection
418
  file_dropdown = gr.Dropdown(
419
- choices=[f[0] for f in chat_assistant.get_quote_files()],
420
  label="Select Quote to View",
421
  value=None
422
  )
@@ -438,6 +469,21 @@ def create_gradio_interface():
438
  """
439
  )
440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  # Additional information
442
  with gr.Accordion("ℹ️ About This Assistant", open=False):
443
  gr.Markdown(
@@ -456,81 +502,121 @@ def create_gradio_interface():
456
  """
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()
470
  choices = [f[0] for f in files]
471
- return files, gr.Dropdown(choices=choices, value=None)
472
 
473
- def display_quote_content(filename):
 
 
474
  if filename:
475
  content = chat_assistant.read_quote_file(filename)
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(
516
  refresh_files,
517
- outputs=[quote_files_display, file_dropdown]
 
518
  )
519
 
520
  file_dropdown.change(
521
  display_quote_content,
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
@@ -542,7 +628,14 @@ def create_gradio_interface():
542
 
543
  timer.tick(
544
  auto_refresh_logs,
545
- outputs=[console_display]
 
 
 
 
 
 
 
546
  )
547
 
548
  return interface
 
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
+ self.log_to_console(f"Session ready - Thread ID: {self.thread_id}")
176
 
177
  except Exception as e:
178
  self.log_to_console(f"Failed to initialize sales assistant: {e}", "ERROR")
 
261
  def get_intermediate_logs(self) -> str:
262
  """Get console logs for intermediate updates."""
263
  return self.get_console_logs()
264
+ def create_chat_assistant():
265
+ """Create a new chat assistant instance for a session."""
266
+ return SalesAssistantChat()
267
+
268
+
269
  def create_gradio_interface():
270
  """Create and configure the Gradio interface."""
271
 
 
 
 
272
  # Create the interface
273
  with gr.Blocks(
274
  title="Streamnet Sales Assistant with Quote Generation",
 
319
  max-width: none !important;
320
  width: 100% !important;
321
  }
322
+ .session-info {
323
+ background-color: #f0f8ff;
324
+ border: 1px solid #b0d4f0;
325
+ border-radius: 6px;
326
+ padding: 10px;
327
+ margin: 10px 0;
328
+ font-size: 12px;
329
+ color: #2c5aa0;
330
+ }
331
  """
332
  ) as interface:
333
  # load Google Font (Inter)
334
  gr.HTML('<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap" rel="stylesheet">')
335
 
336
+ # Session state for chat assistant
337
+ chat_assistant_state = gr.State()
338
+ session_id_state = gr.State()
339
+
340
+ def initialize_session():
341
+ """Initialize a new session with a fresh chat assistant."""
342
+ import uuid
343
+ session_id = f"gradio_session_{uuid.uuid4().hex[:8]}"
344
+ chat_assistant = SalesAssistantChat()
345
+ return chat_assistant, session_id, f"πŸ”„ Session ID: {session_id}"
346
+
347
+ def get_or_create_session(chat_assistant, session_id):
348
+ """Get existing session or create new one if None."""
349
+ if chat_assistant is None:
350
+ return initialize_session()
351
+ return chat_assistant, session_id, f"πŸ”„ Session ID: {session_id}"
352
+ # load Google Font (Inter)
353
+ gr.HTML('<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap" rel="stylesheet">')
354
+
355
  gr.Markdown(
356
  """
357
  # πŸ€– Streamnet Sales Assistant with Quote Generation
 
394
  # Console logs section
395
  with gr.Accordion("πŸ” Console Logs & Processing Info", open=False):
396
  console_display = gr.Textbox(
397
+ value="Console logs will appear here...",
398
  label="Real-time Processing Logs",
399
  lines=20,
400
  max_lines=30,
 
438
  quote_files_display = gr.Dataframe(
439
  headers=["File Name", "Modified", "Size"],
440
  datatype=["str", "str", "str"],
441
+ value=[],
442
  label="Quote Files",
443
  interactive=False
444
  )
 
447
 
448
  # File selection
449
  file_dropdown = gr.Dropdown(
450
+ choices=[],
451
  label="Select Quote to View",
452
  value=None
453
  )
 
469
  """
470
  )
471
 
472
+ # Session Management Section
473
+ with gr.Accordion("πŸ”„ Session Management", open=False):
474
+ gr.Markdown(
475
+ """
476
+ **Session Control:** Each browser session gets a unique conversation thread.
477
+ Use the button below to start completely fresh or check your current session ID.
478
+ """
479
+ )
480
+ with gr.Row():
481
+ session_info_display = gr.Markdown(
482
+ value="πŸ”„ Session ID: Initializing...",
483
+ elem_classes=["session-info"]
484
+ )
485
+ new_session_btn = gr.Button("πŸ†• Start New Session", variant="secondary", scale=1)
486
+
487
  # Additional information
488
  with gr.Accordion("ℹ️ About This Assistant", open=False):
489
  gr.Markdown(
 
502
  """
503
  )
504
 
505
+ # Event handlers with session state
506
+ def submit_message_immediate(message, history, chat_assistant, session_id):
507
  """Immediately show user message and loading state."""
508
+ chat_assistant, session_id, session_info = get_or_create_session(chat_assistant, session_id)
509
+ return chat_assistant.chat_function_immediate(message, history) + (chat_assistant, session_id, session_info)
510
 
511
+ def process_message_full(message, history, chat_assistant, session_id):
512
  """Process the full message after showing immediate feedback."""
513
+ chat_assistant, session_id, session_info = get_or_create_session(chat_assistant, session_id)
514
+ result = chat_assistant.chat_function_process(message, history)
515
+ return result + (chat_assistant, session_id, session_info)
516
 
517
+ def refresh_files(chat_assistant, session_id):
518
+ """Refresh quote files."""
519
+ chat_assistant, session_id, session_info = get_or_create_session(chat_assistant, session_id)
520
  files = chat_assistant.get_quote_files()
521
  choices = [f[0] for f in files]
522
+ return files, gr.Dropdown(choices=choices, value=None), chat_assistant, session_id, session_info
523
 
524
+ def display_quote_content(filename, chat_assistant, session_id):
525
+ """Display quote content."""
526
+ chat_assistant, session_id, session_info = get_or_create_session(chat_assistant, session_id)
527
  if filename:
528
  content = chat_assistant.read_quote_file(filename)
529
+ return content, chat_assistant, session_id, session_info
530
+ return "Select a quote file to view its content.", chat_assistant, session_id, session_info
531
 
532
+ def refresh_console_logs(chat_assistant, session_id):
533
+ """Refresh console logs."""
534
+ chat_assistant, session_id, session_info = get_or_create_session(chat_assistant, session_id)
535
+ return chat_assistant.get_console_logs(), chat_assistant, session_id, session_info
536
 
537
+ def clear_console_logs(chat_assistant, session_id):
538
+ """Clear console logs."""
539
+ chat_assistant, session_id, session_info = get_or_create_session(chat_assistant, session_id)
540
  chat_assistant.console_logs = []
541
  chat_assistant.log_to_console("Console logs cleared")
542
+ return chat_assistant.get_console_logs(), chat_assistant, session_id, session_info
543
 
544
+ def auto_refresh_logs(chat_assistant, session_id):
545
  """Auto-refresh function for logs - returns updated logs"""
546
+ chat_assistant, session_id, session_info = get_or_create_session(chat_assistant, session_id)
547
+ return chat_assistant.get_console_logs(), chat_assistant, session_id, session_info
548
+
549
+ def start_new_session():
550
+ """Start a completely new session."""
551
+ chat_assistant, session_id, session_info = initialize_session()
552
+ # Reset UI components
553
+ empty_history = []
554
+ console_logs = chat_assistant.get_console_logs()
555
+ quote_files = chat_assistant.get_quote_files()
556
+ file_choices = [f[0] for f in quote_files]
557
+
558
+ return (
559
+ empty_history, # Reset chat history
560
+ console_logs, # Fresh console logs
561
+ quote_files, # Refresh quote files
562
+ gr.Dropdown(choices=file_choices, value=None), # Reset file dropdown
563
+ "Select a quote file to view its content.", # Reset quote content
564
+ chat_assistant, # New chat assistant
565
+ session_id, # New session ID
566
+ session_info # New session info display
567
+ )
568
 
569
  # Set up auto-refresh timer
570
  timer = gr.Timer(2) # Refresh every 2 seconds
571
 
572
+ # Wire up the events with session state
573
  msg_input.submit(
574
  submit_message_immediate,
575
+ inputs=[msg_input, chatbot, chat_assistant_state, session_id_state],
576
+ outputs=[msg_input, chatbot, console_display, chat_assistant_state, session_id_state, session_info_display]
577
  ).then(
578
  process_message_full,
579
+ inputs=[msg_input, chatbot, chat_assistant_state, session_id_state],
580
+ outputs=[msg_input, chatbot, quote_files_display, console_display, chat_assistant_state, session_id_state, session_info_display]
581
  )
582
 
583
  send_btn.click(
584
  submit_message_immediate,
585
+ inputs=[msg_input, chatbot, chat_assistant_state, session_id_state],
586
+ outputs=[msg_input, chatbot, console_display, chat_assistant_state, session_id_state, session_info_display]
587
  ).then(
588
  process_message_full,
589
+ inputs=[msg_input, chatbot, chat_assistant_state, session_id_state],
590
+ outputs=[msg_input, chatbot, quote_files_display, console_display, chat_assistant_state, session_id_state, session_info_display]
591
+ )
592
+
593
+ new_session_btn.click(
594
+ start_new_session,
595
+ outputs=[chatbot, console_display, quote_files_display, file_dropdown, quote_content, chat_assistant_state, session_id_state, session_info_display]
596
  )
597
 
598
  refresh_btn.click(
599
  refresh_files,
600
+ inputs=[chat_assistant_state, session_id_state],
601
+ outputs=[quote_files_display, file_dropdown, chat_assistant_state, session_id_state, session_info_display]
602
  )
603
 
604
  file_dropdown.change(
605
  display_quote_content,
606
+ inputs=[file_dropdown, chat_assistant_state, session_id_state],
607
+ outputs=[quote_content, chat_assistant_state, session_id_state, session_info_display]
608
  )
609
 
610
  refresh_logs_btn.click(
611
  refresh_console_logs,
612
+ inputs=[chat_assistant_state, session_id_state],
613
+ outputs=[console_display, chat_assistant_state, session_id_state, session_info_display]
614
  )
615
 
616
  clear_logs_btn.click(
617
  clear_console_logs,
618
+ inputs=[chat_assistant_state, session_id_state],
619
+ outputs=[console_display, chat_assistant_state, session_id_state, session_info_display]
620
  )
621
 
622
  # Auto-refresh functionality
 
628
 
629
  timer.tick(
630
  auto_refresh_logs,
631
+ inputs=[chat_assistant_state, session_id_state],
632
+ outputs=[console_display, chat_assistant_state, session_id_state, session_info_display]
633
+ )
634
+
635
+ # Initialize session on load
636
+ interface.load(
637
+ initialize_session,
638
+ outputs=[chat_assistant_state, session_id_state, session_info_display]
639
  )
640
 
641
  return interface