milwright commited on
Commit
8e528af
·
1 Parent(s): 352fa12

update export to text format with timestamps and message length metadata

Browse files

- change export format from markdown (.md) to text (.txt)
- add timestamp storage to each message in chat history
- add message length metadata for both user and assistant messages
- update export function to display timestamps and character counts
- sync app.py preview panel with space_template.py implementation
- ensure preview behavior matches deployed space functionality

Files changed (2) hide show
  1. app.py +143 -23
  2. space_template.py +31 -12
app.py CHANGED
@@ -26,10 +26,58 @@ from utils import (
26
  # Load environment variables
27
  load_dotenv()
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  # Load templates
30
  try:
31
- from space_template import get_template, validate_template
32
- print("Loaded space template")
 
33
  except Exception as e:
34
  print(f"Could not load space_template.py: {e}")
35
  # Fallback template will be defined if needed
@@ -105,6 +153,40 @@ class SpaceGenerator:
105
  with gr.Tab("Documentation"):
106
  self._create_documentation_tab()
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  return demo
109
 
110
  def _create_configuration_tab(self):
@@ -456,7 +538,7 @@ class SpaceGenerator:
456
 
457
  # Message input
458
  msg = gr.Textbox(
459
- label="Message",
460
  placeholder="Type your message here...",
461
  lines=2
462
  )
@@ -468,11 +550,17 @@ class SpaceGenerator:
468
 
469
  # Export functionality
470
  with gr.Row():
471
- export_btn = gr.DownloadButton(
 
472
  "📥 Export Conversation",
473
  variant="secondary",
474
  size="sm"
475
  )
 
 
 
 
 
476
 
477
  # Examples section
478
  examples = config.get('examples_list', [])
@@ -635,10 +723,13 @@ class SpaceGenerator:
635
  except Exception as e:
636
  response = f"❌ Error: {str(e)}"
637
 
638
- # Update chat history
 
 
 
639
  chat_history = chat_history + [
640
- {"role": "user", "content": message},
641
- {"role": "assistant", "content": response}
642
  ]
643
 
644
  return chat_history, ""
@@ -651,26 +742,32 @@ class SpaceGenerator:
651
  # Export handler
652
  def prepare_export(chat_history):
653
  if not chat_history:
 
654
  return None
655
 
656
- # Export conversation
657
- content = export_conversation_to_markdown(chat_history)
658
-
659
- # Create filename
660
- space_name_safe = re.sub(r'[^a-zA-Z0-9]+', '_', config.get('name', 'AI_Assistant')).lower()
661
- timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
662
- filename = f"{space_name_safe}_conversation_{timestamp}.md"
663
-
664
- # Save to temp file
665
- temp_path = Path(tempfile.gettempdir()) / filename
666
- temp_path.write_text(content, encoding='utf-8')
667
-
668
- return str(temp_path)
 
 
 
 
 
669
 
670
- export_btn.click(
671
  prepare_export,
672
  inputs=[preview_chatbot],
673
- outputs=[export_btn]
674
  )
675
 
676
  def _create_documentation_tab(self):
@@ -939,8 +1036,31 @@ class SpaceGenerator:
939
  'language': repr(language)
940
  }
941
 
942
- # Generate files
943
  template = get_template()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
944
  app_content = template.format(**config)
945
 
946
  requirements_content = """gradio>=5.42.0
 
26
  # Load environment variables
27
  load_dotenv()
28
 
29
+ def export_conversation_to_text(history, config=None):
30
+ """Export conversation history to text with timestamps"""
31
+ if not history:
32
+ return "No conversation to export."
33
+
34
+ space_name = config.get('name', 'AI Assistant') if config else 'AI Assistant'
35
+ model_name = config.get('model', 'Unknown') if config else 'Unknown'
36
+
37
+ text_content = f"""Conversation Export
38
+ ==================
39
+ Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
40
+ Space: {space_name}
41
+ Model: {model_name}
42
+
43
+ ==================
44
+
45
+ """
46
+
47
+ message_count = 0
48
+ for message in history:
49
+ if isinstance(message, dict):
50
+ role = message.get('role', 'unknown')
51
+ content = message.get('content', '')
52
+
53
+ # Get timestamp from message or use current time as fallback
54
+ timestamp_str = message.get('timestamp', '')
55
+ if timestamp_str:
56
+ try:
57
+ # Parse ISO format timestamp and format it nicely
58
+ timestamp = datetime.fromisoformat(timestamp_str)
59
+ formatted_timestamp = timestamp.strftime('%Y-%m-%d %H:%M:%S')
60
+ except:
61
+ formatted_timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
62
+ else:
63
+ formatted_timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
64
+
65
+ # Get message length
66
+ msg_length = message.get('length', len(content))
67
+
68
+ if role == 'user':
69
+ message_count += 1
70
+ text_content += f"[{formatted_timestamp}] User Message {message_count} ({msg_length} chars):\n{content}\n\n"
71
+ elif role == 'assistant':
72
+ text_content += f"[{formatted_timestamp}] Assistant Response {message_count} ({msg_length} chars):\n{content}\n\n------------------\n\n"
73
+
74
+ return text_content
75
+
76
  # Load templates
77
  try:
78
+ from space_template import get_template
79
+ from template_validator import validate_silently
80
+ print("Loaded space template and validator")
81
  except Exception as e:
82
  print(f"Could not load space_template.py: {e}")
83
  # Fallback template will be defined if needed
 
153
  with gr.Tab("Documentation"):
154
  self._create_documentation_tab()
155
 
156
+ # Add keyboard shortcuts
157
+ demo.load(
158
+ None,
159
+ None,
160
+ None,
161
+ js="""
162
+ () => {
163
+ // Focus on message input when page loads
164
+ setTimeout(() => {
165
+ const msgInput = document.querySelector('textarea');
166
+ if (msgInput) msgInput.focus();
167
+ }, 100);
168
+
169
+ // Keyboard shortcuts
170
+ document.addEventListener('keydown', function(e) {
171
+ // Ctrl+L to clear chat
172
+ if (e.ctrlKey && e.key === 'l') {
173
+ e.preventDefault();
174
+ const buttons = Array.from(document.querySelectorAll('button'));
175
+ const clearBtn = buttons.find(btn => btn.textContent.includes('Clear'));
176
+ if (clearBtn) clearBtn.click();
177
+ }
178
+ // Ctrl+E to export
179
+ else if (e.ctrlKey && e.key === 'e') {
180
+ e.preventDefault();
181
+ const buttons = Array.from(document.querySelectorAll('button'));
182
+ const exportBtn = buttons.find(btn => btn.textContent.includes('Export'));
183
+ if (exportBtn) exportBtn.click();
184
+ }
185
+ });
186
+ }
187
+ """
188
+ )
189
+
190
  return demo
191
 
192
  def _create_configuration_tab(self):
 
538
 
539
  # Message input
540
  msg = gr.Textbox(
541
+ label="Message (Shift+Enter to send)",
542
  placeholder="Type your message here...",
543
  lines=2
544
  )
 
550
 
551
  # Export functionality
552
  with gr.Row():
553
+ # Use a regular Button for triggering export
554
+ export_trigger_btn = gr.Button(
555
  "📥 Export Conversation",
556
  variant="secondary",
557
  size="sm"
558
  )
559
+ # Hidden file component for actual download
560
+ export_file = gr.File(
561
+ visible=False,
562
+ label="Download Export"
563
+ )
564
 
565
  # Examples section
566
  examples = config.get('examples_list', [])
 
723
  except Exception as e:
724
  response = f"❌ Error: {str(e)}"
725
 
726
+ # Get current timestamp
727
+ current_time = datetime.now()
728
+
729
+ # Update chat history with timestamps and message lengths
730
  chat_history = chat_history + [
731
+ {"role": "user", "content": message, "timestamp": current_time.isoformat(), "length": len(message)},
732
+ {"role": "assistant", "content": response, "timestamp": current_time.isoformat(), "length": len(response)}
733
  ]
734
 
735
  return chat_history, ""
 
742
  # Export handler
743
  def prepare_export(chat_history):
744
  if not chat_history:
745
+ gr.Warning("No conversation history to export.")
746
  return None
747
 
748
+ try:
749
+ # Export conversation using the text format with timestamps
750
+ content = export_conversation_to_text(chat_history, config)
751
+
752
+ # Create filename
753
+ space_name_safe = re.sub(r'[^a-zA-Z0-9]+', '_', config.get('name', 'AI_Assistant')).lower()
754
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
755
+ filename = f"{space_name_safe}_conversation_{timestamp}.txt"
756
+
757
+ # Save to temp file
758
+ temp_path = Path(tempfile.gettempdir()) / filename
759
+ temp_path.write_text(content, encoding='utf-8')
760
+
761
+ # Return the file component with visibility and value
762
+ return gr.File(visible=True, value=str(temp_path))
763
+ except Exception as e:
764
+ gr.Error(f"Failed to export conversation: {str(e)}")
765
+ return None
766
 
767
+ export_trigger_btn.click(
768
  prepare_export,
769
  inputs=[preview_chatbot],
770
+ outputs=[export_file]
771
  )
772
 
773
  def _create_documentation_tab(self):
 
1036
  'language': repr(language)
1037
  }
1038
 
1039
+ # Validate template before generating
1040
  template = get_template()
1041
+ validation_success, validation_results = validate_silently(template)
1042
+
1043
+ if not validation_success:
1044
+ # Build error message from validation results
1045
+ error_msg = "❌ **Template validation failed!**\n\n"
1046
+ for check_name, result in validation_results.items():
1047
+ if not result['passed']:
1048
+ error_msg += f"**{check_name.replace('_', ' ').title()}:**\n"
1049
+ for error in result['errors']:
1050
+ error_msg += f" - {error}\n"
1051
+ if result.get('missing'):
1052
+ error_msg += f" - Missing: {', '.join(result['missing'])}\n"
1053
+
1054
+ gr.Error("Template validation failed. Please check the template structure.")
1055
+ return (
1056
+ gr.update(visible=True),
1057
+ gr.update(value=error_msg),
1058
+ gr.update(visible=False),
1059
+ gr.update(visible=False),
1060
+ {}
1061
+ )
1062
+
1063
+ # Generate files
1064
  app_content = template.format(**config)
1065
 
1066
  requirements_content = """gradio>=5.42.0
space_template.py CHANGED
@@ -334,17 +334,18 @@ def get_grounding_context() -> str:
334
  return ""
335
 
336
 
337
- def export_conversation_to_markdown(history: List[Dict[str, str]]) -> str:
338
- """Export conversation history to markdown"""
339
  if not history:
340
  return "No conversation to export."
341
 
342
- markdown_content = f"""# Conversation Export
 
343
  Generated on: {{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}}
344
  Space: {{SPACE_NAME}}
345
  Model: {{MODEL}}
346
 
347
- ---
348
 
349
  """
350
 
@@ -354,13 +355,28 @@ Model: {{MODEL}}
354
  role = message.get('role', 'unknown')
355
  content = message.get('content', '')
356
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  if role == 'user':
358
  message_count += 1
359
- markdown_content += f"## User Message {{message_count}}\\n\\n{{content}}\\n\\n"
360
  elif role == 'assistant':
361
- markdown_content += f"## Assistant Response {{message_count}}\\n\\n{{content}}\\n\\n---\\n\\n"
362
 
363
- return markdown_content
364
 
365
 
366
  def generate_response(message: str, history: List[Dict[str, str]], files: Optional[List] = None) -> str:
@@ -612,12 +628,12 @@ def create_interface():
612
  return None
613
 
614
  try:
615
- content = export_conversation_to_markdown(chat_history)
616
 
617
  # Create filename
618
  space_name_safe = re.sub(r'[^a-zA-Z0-9]+', '_', SPACE_NAME).lower()
619
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
620
- filename = f"{{space_name_safe}}_conversation_{{timestamp}}.md"
621
 
622
  # Save to temp file
623
  temp_path = Path(tempfile.gettempdir()) / filename
@@ -656,10 +672,13 @@ def create_interface():
656
  # Get response
657
  response = generate_response(message, formatted_history, files_state)
658
 
659
- # Update chat history
 
 
 
660
  chat_history = chat_history + [
661
- {{"role": "user", "content": message}},
662
- {{"role": "assistant", "content": response}}
663
  ]
664
 
665
  # Update stored history for export
 
334
  return ""
335
 
336
 
337
+ def export_conversation_to_text(history: List[Dict[str, str]]) -> str:
338
+ """Export conversation history to text with timestamps"""
339
  if not history:
340
  return "No conversation to export."
341
 
342
+ text_content = f"""Conversation Export
343
+ ==================
344
  Generated on: {{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}}
345
  Space: {{SPACE_NAME}}
346
  Model: {{MODEL}}
347
 
348
+ ==================
349
 
350
  """
351
 
 
355
  role = message.get('role', 'unknown')
356
  content = message.get('content', '')
357
 
358
+ # Get timestamp from message or use current time as fallback
359
+ timestamp_str = message.get('timestamp', '')
360
+ if timestamp_str:
361
+ try:
362
+ # Parse ISO format timestamp and format it nicely
363
+ timestamp = datetime.fromisoformat(timestamp_str)
364
+ formatted_timestamp = timestamp.strftime('%Y-%m-%d %H:%M:%S')
365
+ except:
366
+ formatted_timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
367
+ else:
368
+ formatted_timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
369
+
370
+ # Get message length
371
+ msg_length = message.get('length', len(content))
372
+
373
  if role == 'user':
374
  message_count += 1
375
+ text_content += f"[{{formatted_timestamp}}] User Message {{message_count}} ({{msg_length}} chars):\\n{{content}}\\n\\n"
376
  elif role == 'assistant':
377
+ text_content += f"[{{formatted_timestamp}}] Assistant Response {{message_count}} ({{msg_length}} chars):\\n{{content}}\\n\\n------------------\\n\\n"
378
 
379
+ return text_content
380
 
381
 
382
  def generate_response(message: str, history: List[Dict[str, str]], files: Optional[List] = None) -> str:
 
628
  return None
629
 
630
  try:
631
+ content = export_conversation_to_text(chat_history)
632
 
633
  # Create filename
634
  space_name_safe = re.sub(r'[^a-zA-Z0-9]+', '_', SPACE_NAME).lower()
635
  timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
636
+ filename = f"{{space_name_safe}}_conversation_{{timestamp}}.txt"
637
 
638
  # Save to temp file
639
  temp_path = Path(tempfile.gettempdir()) / filename
 
672
  # Get response
673
  response = generate_response(message, formatted_history, files_state)
674
 
675
+ # Get current timestamp
676
+ current_time = datetime.now()
677
+
678
+ # Update chat history with timestamps and message lengths
679
  chat_history = chat_history + [
680
+ {{"role": "user", "content": message, "timestamp": current_time.isoformat(), "length": len(message)}},
681
+ {{"role": "assistant", "content": response, "timestamp": current_time.isoformat(), "length": len(response)}}
682
  ]
683
 
684
  # Update stored history for export