class ConversationTimelineGenerator
A class that generates comprehensive PDF reports documenting conversation timelines, including detailed exchanges, problem-solving analysis, references, and visual summaries.
/tf/active/vicechatdev/e-ink-llm/conversation_timeline.py
25 - 422
complex
Purpose
ConversationTimelineGenerator creates professional PDF documentation of conversations using the ReportLab library. It produces multi-page timeline reports with title pages, executive summaries, detailed exchange histories, problem-solving analysis, and reference mappings. The class supports both comprehensive multi-page reports and quick one-page summaries, making it suitable for conversation analysis, documentation, and archival purposes.
Source Code
class ConversationTimelineGenerator:
"""Generate comprehensive conversation timeline PDFs"""
def __init__(self):
"""Initialize timeline generator"""
self.logger = logging.getLogger(__name__)
# PDF styling
self.styles = getSampleStyleSheet()
self.setup_custom_styles()
# Colors for different elements
self.colors = {
'header': colors.HexColor('#2E86AB'),
'exchange': colors.HexColor('#A23B72'),
'topic': colors.HexColor('#F18F01'),
'reference': colors.HexColor('#C73E1D'),
'insight': colors.HexColor('#6A994E'),
'light_gray': colors.HexColor('#F5F5F5'),
'medium_gray': colors.HexColor('#E0E0E0')
}
def setup_custom_styles(self):
"""Set up custom paragraph styles"""
self.styles.add(ParagraphStyle(
name='ConversationTitle',
parent=self.styles['Title'],
fontSize=18,
spaceAfter=20,
textColor=self.colors['header'] if hasattr(self, 'colors') else colors.blue,
alignment=TA_CENTER
))
self.styles.add(ParagraphStyle(
name='ExchangeHeader',
parent=self.styles['Heading2'],
fontSize=14,
spaceBefore=15,
spaceAfter=10,
textColor=self.colors['exchange'] if hasattr(self, 'colors') else colors.darkred,
leftIndent=20
))
self.styles.add(ParagraphStyle(
name='ExchangeContent',
parent=self.styles['Normal'],
fontSize=11,
spaceBefore=5,
spaceAfter=5,
leftIndent=30,
rightIndent=20,
alignment=TA_JUSTIFY
))
self.styles.add(ParagraphStyle(
name='TopicStyle',
parent=self.styles['Normal'],
fontSize=10,
textColor=self.colors['topic'] if hasattr(self, 'colors') else colors.orange,
leftIndent=30
))
self.styles.add(ParagraphStyle(
name='ReferenceStyle',
parent=self.styles['Normal'],
fontSize=10,
textColor=self.colors['reference'] if hasattr(self, 'colors') else colors.red,
leftIndent=30,
fontName='Helvetica-Oblique'
))
async def generate_conversation_timeline(self,
context: ConversationContext,
output_path: str) -> bool:
"""
Generate comprehensive conversation timeline PDF
Args:
context: ConversationContext with full conversation data
output_path: Path for output PDF
Returns:
True if successful, False otherwise
"""
self.logger.info(f"Generating conversation timeline for {context.conversation_id}")
try:
# Create PDF document
doc = SimpleDocTemplate(
output_path,
pagesize=A4,
rightMargin=72,
leftMargin=72,
topMargin=72,
bottomMargin=72
)
# Build story elements
story = []
# Title page
story.extend(self._build_title_page(context))
story.append(PageBreak())
# Executive summary
story.extend(self._build_executive_summary(context))
story.append(PageBreak())
# Timeline visualization
story.extend(self._build_timeline_section(context))
story.append(PageBreak())
# Detailed exchanges
story.extend(self._build_detailed_exchanges(context))
# Problem-solving analysis
if context.problem_solving_chain:
story.append(PageBreak())
story.extend(self._build_problem_solving_analysis(context))
# References and connections
if context.reference_map:
story.append(PageBreak())
story.extend(self._build_references_section(context))
# Build PDF
doc.build(story)
self.logger.info(f"Timeline PDF generated: {output_path}")
return True
except Exception as e:
self.logger.error(f"Error generating timeline PDF: {e}")
return False
def _build_title_page(self, context: ConversationContext) -> List[Any]:
"""Build title page elements"""
elements = []
# Title
title = f"Conversation Timeline"
elements.append(Paragraph(title, self.styles['ConversationTitle']))
elements.append(Spacer(1, 20))
# Conversation details
details = [
f"<b>Conversation ID:</b> {context.conversation_id}",
f"<b>Total Exchanges:</b> {context.total_exchanges}",
f"<b>Generated:</b> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
"",
f"<b>Summary:</b> {context.conversation_summary}"
]
for detail in details:
elements.append(Paragraph(detail, self.styles['Normal']))
elements.append(Spacer(1, 6))
elements.append(Spacer(1, 30))
# Active topics
if context.active_topics:
elements.append(Paragraph("<b>Active Topics:</b>", self.styles['Heading3']))
for topic in context.active_topics:
elements.append(Paragraph(f"• {topic.replace('_', ' ').title()}", self.styles['TopicStyle']))
elements.append(Spacer(1, 20))
# Key insights
if context.key_insights:
elements.append(Paragraph("<b>Key Insights:</b>", self.styles['Heading3']))
for insight in context.key_insights:
elements.append(Paragraph(f"• {insight}", self.styles['Normal']))
elements.append(Spacer(1, 10))
return elements
def _build_executive_summary(self, context: ConversationContext) -> List[Any]:
"""Build executive summary section"""
elements = []
elements.append(Paragraph("Executive Summary", self.styles['Heading1']))
elements.append(Spacer(1, 12))
# Conversation overview
overview = f"""
This conversation timeline documents {context.total_exchanges} exchanges in conversation {context.conversation_id}.
The conversation covers {len(context.active_topics)} main topic areas and demonstrates a clear progression
through {len(context.problem_solving_chain)} problem-solving steps.
"""
elements.append(Paragraph(overview, self.styles['Normal']))
elements.append(Spacer(1, 15))
# Statistics table
stats_data = [
['Metric', 'Value'],
['Total Exchanges', str(context.total_exchanges)],
['Active Topics', str(len(context.active_topics))],
['Key Insights', str(len(context.key_insights))],
['Problem-Solving Steps', str(len(context.problem_solving_chain))],
['Cross-References', str(sum(len(refs) for refs in context.reference_map.values()))]
]
stats_table = Table(stats_data, colWidths=[2*inch, 1*inch])
stats_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), self.colors['header']),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 11),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), self.colors['light_gray']),
('GRID', (0, 0), (-1, -1), 1, colors.black)
]))
elements.append(stats_table)
elements.append(Spacer(1, 20))
# Problem-solving progression
if context.problem_solving_chain:
elements.append(Paragraph("Problem-Solving Progression", self.styles['Heading3']))
for i, step in enumerate(context.problem_solving_chain, 1):
step_text = f"{i}. <b>{step['step_type'].title()}</b> (Exchange {step['exchange_number']}): {step['description']}"
elements.append(Paragraph(step_text, self.styles['Normal']))
elements.append(Spacer(1, 15))
return elements
def _build_timeline_section(self, context: ConversationContext) -> List[Any]:
"""Build visual timeline section"""
elements = []
elements.append(Paragraph("Conversation Timeline", self.styles['Heading1']))
elements.append(Spacer(1, 12))
# Timeline table
timeline_data = [['Exchange', 'Timestamp', 'Input', 'Key Topics', 'Processing Time']]
for turn in context.conversation_turns:
timeline_data.append([
str(turn.exchange_number),
turn.timestamp.strftime('%H:%M:%S'),
turn.input_summary[:30] + "..." if len(turn.input_summary) > 30 else turn.input_summary,
", ".join(turn.topics[:2]),
f"{turn.processing_time:.1f}s"
])
timeline_table = Table(timeline_data, colWidths=[0.8*inch, 1*inch, 2.5*inch, 1.5*inch, 1*inch])
timeline_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), self.colors['header']),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 10),
('FONTSIZE', (0, 1), (-1, -1), 9),
('BOTTOMPADDING', (0, 0), (-1, 0), 8),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, self.colors['light_gray']]),
('GRID', (0, 0), (-1, -1), 0.5, colors.gray),
('VALIGN', (0, 0), (-1, -1), 'TOP')
]))
elements.append(timeline_table)
elements.append(Spacer(1, 20))
return elements
def _build_detailed_exchanges(self, context: ConversationContext) -> List[Any]:
"""Build detailed exchange documentation"""
elements = []
elements.append(Paragraph("Detailed Exchange History", self.styles['Heading1']))
elements.append(Spacer(1, 12))
for turn in context.conversation_turns:
# Exchange header
header_text = f"Exchange {turn.exchange_number} - {turn.timestamp.strftime('%Y-%m-%d %H:%M:%S')}"
elements.append(Paragraph(header_text, self.styles['ExchangeHeader']))
# Input summary
elements.append(Paragraph(f"<b>Input:</b> {turn.input_summary}", self.styles['ExchangeContent']))
# Response summary
elements.append(Paragraph(f"<b>Response:</b> {turn.response_summary}", self.styles['ExchangeContent']))
# Topics
if turn.topics:
topics_text = f"<b>Topics:</b> {', '.join(turn.topics)}"
elements.append(Paragraph(topics_text, self.styles['TopicStyle']))
# Key points
if turn.key_points:
elements.append(Paragraph("<b>Key Points:</b>", self.styles['ExchangeContent']))
for point in turn.key_points:
elements.append(Paragraph(f"• {point}", self.styles['ExchangeContent']))
# Processing stats
stats_text = f"<b>Processing:</b> {turn.processing_time:.1f}s, {turn.tokens_used} tokens"
elements.append(Paragraph(stats_text, self.styles['Normal']))
elements.append(Spacer(1, 15))
return elements
def _build_problem_solving_analysis(self, context: ConversationContext) -> List[Any]:
"""Build problem-solving analysis section"""
elements = []
elements.append(Paragraph("Problem-Solving Analysis", self.styles['Heading1']))
elements.append(Spacer(1, 12))
# Problem-solving flow
elements.append(Paragraph("The conversation demonstrates the following problem-solving progression:", self.styles['Normal']))
elements.append(Spacer(1, 10))
for i, step in enumerate(context.problem_solving_chain, 1):
step_header = f"Step {i}: {step['step_type'].title()} (Exchange {step['exchange_number']})"
elements.append(Paragraph(step_header, self.styles['ExchangeHeader']))
elements.append(Paragraph(step['description'], self.styles['ExchangeContent']))
if step['topics']:
topics_text = f"Related topics: {', '.join(step['topics'])}"
elements.append(Paragraph(topics_text, self.styles['TopicStyle']))
elements.append(Spacer(1, 10))
return elements
def _build_references_section(self, context: ConversationContext) -> List[Any]:
"""Build references and connections section"""
elements = []
elements.append(Paragraph("References and Connections", self.styles['Heading1']))
elements.append(Spacer(1, 12))
elements.append(Paragraph("This section shows how exchanges reference and build upon previous discussions:", self.styles['Normal']))
elements.append(Spacer(1, 10))
for exchange_num, references in context.reference_map.items():
if references:
header = f"Exchange {exchange_num} References:"
elements.append(Paragraph(header, self.styles['ExchangeHeader']))
for ref in references:
ref_text = f"→ References Exchange {ref.exchange_number} ({ref.reference_type}): {ref.referenced_content}"
elements.append(Paragraph(ref_text, self.styles['ReferenceStyle']))
if ref.context_snippet:
context_text = f" Context: \"{ref.context_snippet}\""
elements.append(Paragraph(context_text, self.styles['Normal']))
elements.append(Spacer(1, 10))
return elements
def generate_quick_summary_pdf(self,
context: ConversationContext,
output_path: str) -> bool:
"""Generate a quick 1-page summary PDF"""
try:
c = canvas.Canvas(output_path, pagesize=letter)
width, height = letter
# Title
c.setFont("Helvetica-Bold", 16)
c.drawString(50, height - 50, f"Conversation Summary: {context.conversation_id}")
# Basic stats
y_pos = height - 100
c.setFont("Helvetica", 12)
stats = [
f"Total Exchanges: {context.total_exchanges}",
f"Active Topics: {', '.join(context.active_topics[:5])}",
f"Key Insights: {len(context.key_insights)}",
f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
]
for stat in stats:
c.drawString(50, y_pos, stat)
y_pos -= 20
# Recent exchanges
y_pos -= 20
c.setFont("Helvetica-Bold", 14)
c.drawString(50, y_pos, "Recent Exchanges:")
y_pos -= 20
c.setFont("Helvetica", 10)
for turn in context.conversation_turns[-3:]: # Last 3 exchanges
exchange_text = f"Ex {turn.exchange_number}: {turn.input_summary} → {turn.response_summary[:60]}..."
c.drawString(50, y_pos, exchange_text)
y_pos -= 15
c.save()
return True
except Exception as e:
self.logger.error(f"Error generating quick summary: {e}")
return False
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
- | - |
Parameter Details
No constructor parameters: The __init__ method takes no parameters. It initializes the logger, sets up PDF styling with custom paragraph styles, and defines a color scheme for different document elements.
Return Value
Instantiation returns a ConversationTimelineGenerator object. The main methods (generate_conversation_timeline and generate_quick_summary_pdf) return boolean values: True if PDF generation succeeds, False if an error occurs. The private helper methods return List[Any] containing ReportLab flowable elements for PDF construction.
Class Interface
Methods
__init__(self) -> None
Purpose: Initialize the timeline generator with logger, PDF styles, and color scheme
Returns: None - initializes instance attributes
setup_custom_styles(self) -> None
Purpose: Configure custom paragraph styles for different PDF elements (titles, headers, content, topics, references)
Returns: None - modifies self.styles by adding custom ParagraphStyle objects
async generate_conversation_timeline(self, context: ConversationContext, output_path: str) -> bool
Purpose: Generate a comprehensive multi-page PDF timeline report with title page, executive summary, timeline visualization, detailed exchanges, problem-solving analysis, and references
Parameters:
context: ConversationContext object containing full conversation data including turns, topics, insights, problem-solving chain, and reference mapoutput_path: String path where the output PDF file should be saved
Returns: Boolean - True if PDF generation succeeds, False if an error occurs
_build_title_page(self, context: ConversationContext) -> List[Any]
Purpose: Build ReportLab flowable elements for the title page including conversation details, active topics, and key insights
Parameters:
context: ConversationContext object with conversation metadata
Returns: List of ReportLab flowable elements (Paragraph, Spacer objects) for the title page
_build_executive_summary(self, context: ConversationContext) -> List[Any]
Purpose: Build executive summary section with conversation overview, statistics table, and problem-solving progression
Parameters:
context: ConversationContext object with conversation statistics and problem-solving chain
Returns: List of ReportLab flowable elements for the executive summary section
_build_timeline_section(self, context: ConversationContext) -> List[Any]
Purpose: Build visual timeline section with a table showing exchange numbers, timestamps, inputs, topics, and processing times
Parameters:
context: ConversationContext object with conversation_turns data
Returns: List of ReportLab flowable elements including a formatted timeline table
_build_detailed_exchanges(self, context: ConversationContext) -> List[Any]
Purpose: Build detailed documentation for each conversation exchange including input/response summaries, topics, key points, and processing statistics
Parameters:
context: ConversationContext object with conversation_turns containing detailed exchange information
Returns: List of ReportLab flowable elements documenting each exchange in detail
_build_problem_solving_analysis(self, context: ConversationContext) -> List[Any]
Purpose: Build problem-solving analysis section showing the progression of problem-solving steps throughout the conversation
Parameters:
context: ConversationContext object with problem_solving_chain data
Returns: List of ReportLab flowable elements documenting problem-solving progression
_build_references_section(self, context: ConversationContext) -> List[Any]
Purpose: Build references and connections section showing how exchanges reference and build upon previous discussions
Parameters:
context: ConversationContext object with reference_map data
Returns: List of ReportLab flowable elements documenting cross-references between exchanges
generate_quick_summary_pdf(self, context: ConversationContext, output_path: str) -> bool
Purpose: Generate a quick one-page summary PDF with basic statistics and recent exchanges using ReportLab canvas
Parameters:
context: ConversationContext object with conversation dataoutput_path: String path where the output PDF file should be saved
Returns: Boolean - True if PDF generation succeeds, False if an error occurs
Attributes
| Name | Type | Description | Scope |
|---|---|---|---|
logger |
logging.Logger | Logger instance for tracking PDF generation operations and errors | instance |
styles |
reportlab.lib.styles.StyleSheet1 | ReportLab stylesheet containing both default and custom paragraph styles for PDF formatting | instance |
colors |
Dict[str, colors.HexColor] | Dictionary mapping element types (header, exchange, topic, reference, insight, light_gray, medium_gray) to HexColor objects for consistent PDF styling | instance |
Dependencies
asynciologgingpathlibtypingdatetimejsonreportlab
Required Imports
import asyncio
import logging
from pathlib import Path
from typing import List, Dict, Any, Optional
from datetime import datetime
import json
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib import colors
from reportlab.lib.units import inch
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from conversation_context import ConversationContext, ConversationTurn, ConversationReference
Usage Example
import asyncio
from conversation_timeline_generator import ConversationTimelineGenerator
from conversation_context import ConversationContext
# Create generator instance
generator = ConversationTimelineGenerator()
# Assume we have a ConversationContext object with conversation data
context = ConversationContext(conversation_id="conv_123")
# ... populate context with conversation turns, topics, insights, etc.
# Generate comprehensive timeline PDF
async def generate_report():
success = await generator.generate_conversation_timeline(
context=context,
output_path="/path/to/timeline_report.pdf"
)
if success:
print("Timeline PDF generated successfully")
else:
print("Failed to generate timeline PDF")
# Generate quick summary PDF
success = generator.generate_quick_summary_pdf(
context=context,
output_path="/path/to/quick_summary.pdf"
)
# Run async generation
asyncio.run(generate_report())
Best Practices
- Always instantiate the class before calling any methods - the constructor sets up essential styling and color schemes
- Ensure the ConversationContext object is fully populated with all required data (conversation_turns, active_topics, key_insights, etc.) before generating PDFs
- Use await when calling generate_conversation_timeline() as it is an async method
- Check the boolean return value to verify successful PDF generation before assuming the file exists
- Provide absolute paths for output_path to avoid file location ambiguity
- The class maintains state through instance attributes (logger, styles, colors), so reuse the same instance for multiple PDF generations
- Handle exceptions in calling code as the methods catch and log errors but return False rather than raising exceptions
- For large conversations, be aware that comprehensive timeline generation may take significant time and memory
- The quick summary PDF is synchronous and faster, suitable for real-time previews
- Custom styles are set up during initialization and cannot be modified after instantiation without directly accessing self.styles
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class AuditPageGenerator 60.0% similar
-
class PDFGenerator 58.9% similar
-
function export_to_pdf_v1 58.5% similar
-
class ConversationContext 57.4% similar
-
class SessionDocTemplate 55.9% similar