| """ |
| Analysis View Component - Main analysis interface with input and results |
| """ |
|
|
| import gradio as gr |
| import re |
| from typing import Optional |
|
|
|
|
| def parse_markdown(text: str) -> str: |
| """Convert basic markdown to HTML""" |
| text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text) |
| text = re.sub(r'__(.+?)__', r'<strong>\1</strong>', text) |
| text = re.sub(r'\*(.+?)\*', r'<em>\1</em>', text) |
|
|
| |
| lines = text.split('\n') |
| in_list = False |
| result = [] |
| for line in lines: |
| stripped = line.strip() |
| if re.match(r'^[\*\-] ', stripped): |
| if not in_list: |
| result.append('<ul>') |
| in_list = True |
| item = re.sub(r'^[\*\-] ', '', stripped) |
| result.append(f'<li>{item}</li>') |
| else: |
| if in_list: |
| result.append('</ul>') |
| in_list = False |
| result.append(line) |
| if in_list: |
| result.append('</ul>') |
|
|
| return '\n'.join(result) |
|
|
|
|
| |
| _STAGE_RE = re.compile(r'\[STAGE:(\w+)\](.*?)\[/STAGE\]') |
| _THINKING_RE = re.compile(r'\[THINKING\](.*?)\[/THINKING\]') |
| _OBSERVATION_RE = re.compile(r'\[OBSERVATION\](.*?)\[/OBSERVATION\]') |
| _TOOL_OUTPUT_RE = re.compile(r'\[TOOL_OUTPUT:(.*?)\]\n(.*?)\[/TOOL_OUTPUT\]', re.DOTALL) |
| _RESULT_RE = re.compile(r'\[RESULT\](.*?)\[/RESULT\]') |
| _ERROR_RE = re.compile(r'\[ERROR\](.*?)\[/ERROR\]') |
| _GRADCAM_RE = re.compile(r'\[GRADCAM_IMAGE:[^\]]+\]\n?') |
| _RESPONSE_RE = re.compile(r'\[RESPONSE\]\n(.*?)\n\[/RESPONSE\]', re.DOTALL) |
| _COMPLETE_RE = re.compile(r'\[COMPLETE\](.*?)\[/COMPLETE\]') |
| _CONFIRM_RE = re.compile(r'\[CONFIRM:(\w+)\](.*?)\[/CONFIRM\]') |
| _REFERENCES_RE = re.compile(r'\[REFERENCES\](.*?)\[/REFERENCES\]', re.DOTALL) |
| _REF_RE = re.compile(r'\[REF:([^:]+):([^:]+):([^:]+):([^:]+):([^\]]+)\]') |
|
|
|
|
| def format_output(raw_text: str, gradcam_base64: Optional[str] = None) -> str: |
| """Convert tagged output to styled HTML""" |
| html = raw_text |
|
|
| |
| html = _STAGE_RE.sub( |
| r'<div class="stage"><span class="stage-indicator"></span><span class="stage-text">\2</span></div>', |
| html |
| ) |
|
|
| |
| html = _THINKING_RE.sub(r'<div class="thinking">\1</div>', html) |
|
|
| |
| html = _OBSERVATION_RE.sub(r'<div class="observation">\1</div>', html) |
|
|
| |
| html = _TOOL_OUTPUT_RE.sub( |
| r'<div class="tool-output"><div class="tool-header">\1</div><pre class="tool-content">\2</pre></div>', |
| html |
| ) |
|
|
| |
| html = _RESULT_RE.sub(r'<div class="result">\1</div>', html) |
|
|
| |
| html = _ERROR_RE.sub(r'<div class="error">\1</div>', html) |
|
|
| |
| if gradcam_base64: |
| img_html = f'<div class="gradcam-inline"><div class="gradcam-header">Attention Map</div><img src="data:image/png;base64,{gradcam_base64}" alt="Grad-CAM"></div>' |
| html = _GRADCAM_RE.sub(img_html, html) |
| else: |
| html = _GRADCAM_RE.sub('', html) |
|
|
| |
| def format_response(match): |
| content = match.group(1) |
| parsed = parse_markdown(content) |
| parsed = re.sub(r'\n\n+', '</p><p>', parsed) |
| parsed = parsed.replace('\n', '<br>') |
| return f'<div class="response"><p>{parsed}</p></div>' |
|
|
| html = _RESPONSE_RE.sub(format_response, html) |
|
|
| |
| html = _COMPLETE_RE.sub(r'<div class="complete">\1</div>', html) |
|
|
| |
| html = _CONFIRM_RE.sub( |
| r'<div class="confirm-box"><div class="confirm-text">\2</div></div>', |
| html |
| ) |
|
|
| |
| def format_references(match): |
| ref_content = match.group(1) |
| refs_html = ['<div class="references"><div class="references-header">References</div><ul>'] |
| for ref_match in _REF_RE.finditer(ref_content): |
| _, source, page, filename, superscript = ref_match.groups() |
| refs_html.append( |
| f'<li><a href="guidelines/{filename}#page={page}" target="_blank" class="ref-link">' |
| f'<sup>{superscript}</sup> {source}, p.{page}</a></li>' |
| ) |
| refs_html.append('</ul></div>') |
| return '\n'.join(refs_html) |
|
|
| html = _REFERENCES_RE.sub(format_references, html) |
|
|
| |
| html = html.replace('\n', '<br>') |
|
|
| return f'<div class="analysis-output">{html}</div>' |
|
|
|
|
| def create_analysis_view(): |
| """ |
| Create the analysis view component. |
| |
| Returns: |
| Tuple of (container, components dict) |
| """ |
| with gr.Group(visible=False, elem_classes=["analysis-container"]) as container: |
|
|
| with gr.Row(): |
| |
| with gr.Column(elem_classes=["main-content"]): |
|
|
| |
| with gr.Group(visible=True, elem_classes=["input-greeting"]) as input_greeting: |
| gr.Markdown("What would you like to analyze?", elem_classes=["greeting-title"]) |
| gr.Markdown("Upload an image and describe what you'd like to know", elem_classes=["greeting-subtitle"]) |
|
|
| with gr.Column(elem_classes=["input-box-container"]): |
| message_input = gr.Textbox( |
| placeholder="Describe the lesion or ask a question...", |
| show_label=False, |
| lines=3, |
| elem_classes=["message-input"] |
| ) |
|
|
| |
| image_input = gr.Image( |
| label="", |
| type="pil", |
| height=180, |
| elem_classes=["image-preview"], |
| show_label=False |
| ) |
|
|
| with gr.Row(elem_classes=["input-actions"]): |
| upload_hint = gr.Markdown("*Upload a skin lesion image above*", visible=True) |
| send_btn = gr.Button("Analyze", elem_classes=["send-btn"], interactive=False) |
|
|
| |
| with gr.Group(visible=False, elem_classes=["chat-view"]) as chat_view: |
| results_output = gr.HTML( |
| value='<div class="analysis-output">Starting analysis...</div>', |
| elem_classes=["results-area"] |
| ) |
|
|
| |
| with gr.Group(visible=False, elem_classes=["confirm-buttons"]) as confirm_group: |
| gr.Markdown("**Do you agree with this diagnosis?**") |
| with gr.Row(): |
| confirm_yes_btn = gr.Button("Yes, continue", variant="primary", size="sm") |
| confirm_no_btn = gr.Button("No, I disagree", variant="secondary", size="sm") |
| feedback_input = gr.Textbox( |
| label="Your assessment", |
| placeholder="Enter your diagnosis...", |
| visible=False |
| ) |
| submit_feedback_btn = gr.Button("Submit", visible=False, size="sm") |
|
|
| |
| with gr.Row(elem_classes=["chat-input-area"]): |
| followup_input = gr.Textbox( |
| placeholder="Ask a follow-up question...", |
| show_label=False, |
| lines=1 |
| ) |
| followup_btn = gr.Button("Send", size="sm", elem_classes=["send-btn"]) |
|
|
| components = { |
| "input_greeting": input_greeting, |
| "chat_view": chat_view, |
| "message_input": message_input, |
| "image_input": image_input, |
| "send_btn": send_btn, |
| "results_output": results_output, |
| "confirm_group": confirm_group, |
| "confirm_yes_btn": confirm_yes_btn, |
| "confirm_no_btn": confirm_no_btn, |
| "feedback_input": feedback_input, |
| "submit_feedback_btn": submit_feedback_btn, |
| "followup_input": followup_input, |
| "followup_btn": followup_btn, |
| "upload_hint": upload_hint |
| } |
|
|
| return container, components |
|
|