Class: Assistant::ResponseFormatter
- Inherits:
-
Object
- Object
- Assistant::ResponseFormatter
- Includes:
- ActionView::Helpers::DateHelper, ActionView::Helpers::NumberHelper, ActionView::Helpers::UrlHelper
- Defined in:
- app/services/assistant/response_formatter.rb
Overview
Formats AI chat responses for display
Handles:
- Markdown to HTML conversion using Kramdown (GFM-compatible)
- SQL code blocks in collapsible accordions
- Table formatting with proper number/date/currency formatting
- Entity linking (customers, employees, products, orders)
- Copy to clipboard buttons
Usage:
formatter = Assistant::ResponseFormatter.new(content, view_context: view_context)
html = formatter.format
Constant Summary collapse
- CURRENCY_COLUMNS =
Fallback heuristics for AI-generated column headers that have no manifest metadata.
These are only used when the column wasn't found in @column_metadata (i.e. the AI
aliased or computed the column, or the table isn't from a SQL tool result).
For SQL tool results, manifest-backed column_hints take precedence (see format_cell). /revenue|amount|value|price|cost|msrp/i- DATE_COLUMNS =
/date|created_at|updated_at|shipped|ordered/i- PERCENTAGE_COLUMNS =
/rate|percent|pct|ratio/i- COUNT_COLUMNS =
/count|orders|units|quantity|calls|activities|stock|inventory|qty/i- PRODUCT_LINE_COLUMNS =
/product_line|product line|productline/i- LANGUAGE_DISPLAY_NAMES =
Human-friendly names for code block language identifiers
{ 'html' => 'HTML', 'css' => 'CSS', 'scss' => 'SCSS', 'sass' => 'Sass', 'javascript' => 'JavaScript', 'js' => 'JavaScript', 'typescript' => 'TypeScript', 'ts' => 'TypeScript', 'ruby' => 'Ruby', 'rb' => 'Ruby', 'erb' => 'ERB', 'sql' => 'SQL', 'json' => 'JSON', 'yaml' => 'YAML', 'yml' => 'YAML', 'python' => 'Python', 'py' => 'Python', 'bash' => 'Bash', 'sh' => 'Shell', 'shell' => 'Shell', 'zsh' => 'Shell', 'xml' => 'XML', 'svg' => 'SVG', 'markdown' => 'Markdown', 'md' => 'Markdown', 'text' => 'Plain Text', 'plaintext' => 'Plain Text', 'csv' => 'CSV', 'diff' => 'Diff', 'go' => 'Go', 'rust' => 'Rust', 'java' => 'Java', 'swift' => 'Swift', 'kotlin' => 'Kotlin', 'php' => 'PHP', 'c' => 'C', 'cpp' => 'C++', 'csharp' => 'C#' }.freeze
- SANITIZE_CONFIG =
Sanitize LLM HTML to strip XSS vectors while keeping safe formatting.
Runs BEFORE our post-processing (tables, SQL blocks) which adds safe data-* attributes.
Based on Sanitize::Config::RELAXED minus tags and inline style attributes. Sanitize::Config.merge( Sanitize::Config::RELAXED, elements: Sanitize::Config::RELAXED[:elements] - ['style'], attributes: Sanitize::Config.merge( Sanitize::Config::RELAXED[:attributes], all: (Sanitize::Config::RELAXED[:attributes][:all] || Set.new) - Set['style'] ) ).freeze
Instance Method Summary collapse
-
#format ⇒ Object
Format the content with all transformations.
-
#initialize(content, view_context: nil, column_metadata: {}) ⇒ ResponseFormatter
constructor
A new instance of ResponseFormatter.
Constructor Details
#initialize(content, view_context: nil, column_metadata: {}) ⇒ ResponseFormatter
Returns a new instance of ResponseFormatter.
69 70 71 72 73 74 75 |
# File 'app/services/assistant/response_formatter.rb', line 69 def initialize(content, view_context: nil, column_metadata: {}) @content = content.to_s @view_context = view_context @column_metadata = || {} @sql_counter = 0 @product_line_cache = nil # Lazy-loaded lookup hash end |
Instance Method Details
#format ⇒ Object
Format the content with all transformations
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'app/services/assistant/response_formatter.rb', line 78 def format # Pre-load product lines to avoid N+1 queries preload_product_lines # Extract ALL fenced code blocks before markdown processing. # Each block records its language and raw source for copy/preview buttons. content_with_placeholders, code_blocks = extract_code_blocks(@content) # Neutralize bracket-style source citations before Kramdown processes them. # Kramdown interprets `- [Text]: URL` as a link reference definition and hides the line, # leaving empty <li> bullets. Replace `[X]` at the start of list items with `X ยท`. content_with_placeholders = neutralize_bracket_sources(content_with_placeholders) # Convert markdown to HTML using Kramdown with GFM + Rouge syntax highlighting html = render_markdown(content_with_placeholders) # Sanitize HTML to prevent XSS from LLM-generated content. # LLMs can hallucinate <script> tags or malicious attributes. html = sanitize_html(html) # Link plain URLs in general response content before code panels are restored. # Code blocks are still placeholders at this stage, so URLs in code stay untouched. html = auto_link_urls(html) # Restore code blocks with rich panels (language header, copy, preview for HTML) html = restore_code_blocks(html, code_blocks) # Format tables with proper cell formatting html = format_tables(html) # Link entities to CRM pages html = link_entities(html) # Style the Sources section with a distinctive panel html = format_sources_section(html) html.html_safe end |