Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import tempfile | |
| import os | |
| import datetime | |
| import re | |
| import time | |
| import concurrent.futures | |
| from therapist import ( | |
| run_therapy_individual, | |
| run_therapy_group, | |
| run_therapy_relationship | |
| ) | |
| def select_group_row(evt: gr.SelectData, df): | |
| row_idx = evt.index[0] | |
| if hasattr(df, "iloc"): | |
| circle_name = df.iloc[row_idx, 0] | |
| bugs = df.iloc[row_idx, 1] | |
| else: | |
| circle_name = df[row_idx][0] | |
| bugs = df[row_idx][1] | |
| return circle_name, bugs | |
| def add_group_to_history(circle_name, bugs, df): | |
| if not bugs or not bugs.strip(): | |
| return df | |
| name = circle_name.strip() if circle_name and circle_name.strip() else "Custom Circle" | |
| new_row = [name, bugs.strip()] | |
| if hasattr(df, "values") and hasattr(df, "tolist"): | |
| df_list = df.values.tolist() | |
| else: | |
| df_list = list(df) | |
| exists = False | |
| for r in df_list: | |
| if r[0] == name and r[1] == bugs.strip(): | |
| exists = True | |
| break | |
| if not exists: | |
| df_list.append(new_row) | |
| if hasattr(df, "iloc"): | |
| try: | |
| import pandas as pd | |
| return pd.DataFrame(df_list, columns=df.columns) | |
| except ImportError: | |
| pass | |
| return df_list | |
| def select_couples_row(evt: gr.SelectData, df): | |
| row_idx = evt.index[0] | |
| if hasattr(df, "iloc"): | |
| a = df.iloc[row_idx, 0] | |
| b = df.iloc[row_idx, 1] | |
| conflict = df.iloc[row_idx, 2] | |
| else: | |
| a = df[row_idx][0] | |
| b = df[row_idx][1] | |
| conflict = df[row_idx][2] | |
| return a, b, conflict | |
| def add_couples_to_history(a, b, conflict, df): | |
| if not a or not b or not conflict: | |
| return df | |
| new_row = [a.strip(), b.strip(), conflict.strip()] | |
| if hasattr(df, "values") and hasattr(df, "tolist"): | |
| df_list = df.values.tolist() | |
| else: | |
| df_list = list(df) | |
| exists = False | |
| for r in df_list: | |
| if r[0] == a.strip() and r[1] == b.strip() and r[2] == conflict.strip(): | |
| exists = True | |
| break | |
| if not exists: | |
| df_list.append(new_row) | |
| if hasattr(df, "iloc"): | |
| try: | |
| import pandas as pd | |
| return pd.DataFrame(df_list, columns=df.columns) | |
| except ImportError: | |
| pass | |
| return df_list | |
| # Monochromatic case-file design system (typewriter-inspired typography) | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap'); | |
| body, .gradio-container { | |
| background-color: #0A0A0A !important; | |
| font-family: 'Geist', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; | |
| color: #A3A3A3 !important; | |
| letter-spacing: -0.01em; | |
| } | |
| .gradio-container { | |
| max-width: 960px !important; | |
| margin: 0 auto !important; | |
| padding: 60px 20px !important; | |
| } | |
| /* Header */ | |
| .clinical-title { | |
| font-family: 'Geist', sans-serif !important; | |
| font-weight: 500 !important; | |
| color: #F5F5F5 !important; | |
| font-size: 2.2rem !important; | |
| letter-spacing: -0.03em; | |
| margin-bottom: 6px !important; | |
| text-align: center; | |
| } | |
| .clinical-subtitle { | |
| color: #737373 !important; | |
| font-size: 1rem !important; | |
| margin-bottom: 48px !important; | |
| font-weight: 400 !important; | |
| text-align: center; | |
| } | |
| /* Tabs */ | |
| .tabs { | |
| background: transparent !important; | |
| border-bottom: 1px solid #262626 !important; | |
| margin-bottom: 32px !important; | |
| } | |
| .tab-nav { | |
| border: none !important; | |
| display: flex !important; | |
| gap: 32px !important; | |
| } | |
| .tab-nav button { | |
| font-family: 'Geist', sans-serif !important; | |
| font-size: 0.95rem !important; | |
| font-weight: 500 !important; | |
| color: #737373 !important; | |
| background: transparent !important; | |
| border: none !important; | |
| border-bottom: 2px solid transparent !important; | |
| padding: 8px 0 !important; | |
| border-radius: 0 !important; | |
| transition: all 0.15s ease !important; | |
| } | |
| .tab-nav button:hover { | |
| color: #E5E5E5 !important; | |
| } | |
| .tab-nav button.selected { | |
| color: #F5F5F5 !important; | |
| border-bottom: 2px solid #F5F5F5 !important; | |
| } | |
| /* Patient Case File (Typewriter look) */ | |
| .patient-file-container { | |
| background-color: #111111 !important; | |
| border: 1px solid #262626 !important; | |
| border-radius: 8px !important; | |
| padding: 28px !important; | |
| color: #CCCCCC !important; | |
| font-size: 0.95rem !important; | |
| line-height: 1.8 !important; | |
| font-family: 'Courier New', Courier, monospace !important; | |
| } | |
| .patient-file-container h1, .patient-file-container h2 { | |
| color: #F5F5F5 !important; | |
| font-family: 'Courier New', Courier, monospace !important; | |
| font-weight: bold !important; | |
| border-bottom: 1px dashed #262626 !important; | |
| padding-bottom: 6px !important; | |
| margin-top: 28px !important; | |
| margin-bottom: 14px !important; | |
| font-size: 1.25rem !important; | |
| letter-spacing: -0.01em; | |
| } | |
| .patient-file-container h1:first-of-type { | |
| margin-top: 0 !important; | |
| } | |
| .patient-file-container p, .patient-file-container li { | |
| font-family: 'Courier New', Courier, monospace !important; | |
| color: #AAAAAA !important; | |
| font-size: 0.95rem !important; | |
| } | |
| .patient-file-container blockquote { | |
| border-left: 3px solid #525252 !important; | |
| padding: 4px 16px !important; | |
| margin: 16px 0 !important; | |
| color: #737373 !important; | |
| font-style: italic; | |
| } | |
| .patient-file-container code { | |
| font-family: 'JetBrains Mono', monospace !important; | |
| font-size: 0.85rem !important; | |
| background-color: #222222 !important; | |
| padding: 2px 6px !important; | |
| border-radius: 4px !important; | |
| color: #E5E5E5 !important; | |
| } | |
| /* Form inputs styling */ | |
| .gr-box { | |
| background-color: #171717 !important; | |
| border: 1px solid #262626 !important; | |
| border-radius: 8px !important; | |
| } | |
| textarea, input[type="text"] { | |
| background-color: #0A0A0A !important; | |
| border: 1px solid #262626 !important; | |
| color: #E5E5E5 !important; | |
| border-radius: 6px !important; | |
| font-size: 0.9rem !important; | |
| padding: 12px !important; | |
| } | |
| textarea:focus, input[type="text"]:focus { | |
| border-color: #525252 !important; | |
| box-shadow: none !important; | |
| outline: none !important; | |
| } | |
| /* Primary CTA Button */ | |
| .diagnose-btn { | |
| background-color: #F5F5F5 !important; | |
| border: 1px solid #F5F5F5 !important; | |
| color: #0A0A0A !important; | |
| font-weight: 500 !important; | |
| font-size: 0.95rem !important; | |
| font-family: 'Geist', sans-serif !important; | |
| border-radius: 6px !important; | |
| padding: 12px 24px !important; | |
| transition: all 0.15s ease !important; | |
| cursor: pointer !important; | |
| text-align: center !important; | |
| } | |
| .diagnose-btn:hover { | |
| background-color: #E5E5E5 !important; | |
| border-color: #E5E5E5 !important; | |
| } | |
| /* Secondary Action Buttons (Outlined) */ | |
| .export-btn { | |
| background-color: transparent !important; | |
| border: 1px solid #262626 !important; | |
| color: #737373 !important; | |
| font-weight: 500 !important; | |
| font-family: 'Geist', sans-serif !important; | |
| border-radius: 6px !important; | |
| padding: 10px 20px !important; | |
| transition: all 0.15s ease !important; | |
| margin-top: 16px !important; | |
| text-align: center !important; | |
| font-size: 0.85rem !important; | |
| cursor: pointer !important; | |
| width: 100% !important; | |
| } | |
| .export-btn:hover { | |
| border-color: #525252 !important; | |
| color: #F5F5F5 !important; | |
| } | |
| /* Small inline pill button */ | |
| .pill-btn { | |
| background-color: #171717 !important; | |
| border: 1px solid #262626 !important; | |
| color: #A3A3A3 !important; | |
| font-size: 0.8rem !important; | |
| font-weight: 400 !important; | |
| padding: 4px 10px !important; | |
| border-radius: 4px !important; | |
| cursor: pointer !important; | |
| transition: all 0.1s ease !important; | |
| } | |
| .pill-btn:hover { | |
| background-color: #262626 !important; | |
| color: #F5F5F5 !important; | |
| border-color: #525252 !important; | |
| } | |
| /* Examples box */ | |
| .gr-example { | |
| background-color: #171717 !important; | |
| border: 1px solid #262626 !important; | |
| border-radius: 6px !important; | |
| color: #A3A3A3 !important; | |
| font-size: 0.85rem !important; | |
| transition: all 0.15s !important; | |
| } | |
| .gr-example:hover { | |
| background-color: #262626 !important; | |
| border-color: #525252 !important; | |
| } | |
| /* Hidden download buttons selector (hides them from users but keeps in DOM for JS selectors) */ | |
| #ind_download_hidden, #group_download_hidden, #rel_download_hidden { | |
| display: none !important; | |
| } | |
| /* Remove confusing elapsed/ETA texts from Gradio's loading indicator, leaving only spinner/loader elements */ | |
| .progress-level-inner, .progress-bar-wrap, .eta-bar, .meta-text, .meta-text-center, .meta-text-container { | |
| display: none !important; | |
| } | |
| """ | |
| def clean_txt_for_pdf(text): | |
| """ | |
| Translites curly/unicode punctuation that FPDF standard 8-bit fonts can't encode. | |
| """ | |
| replacements = { | |
| '\u2018': "'", # Smart left single quote | |
| '\u2019': "'", # Smart right single quote | |
| '\u201c': '"', # Smart left double quote | |
| '\u201d': '"', # Smart right double quote | |
| '\u2013': '-', # En dash | |
| '\u2014': '-', # Em dash | |
| '\u2026': '...', # Ellipsis | |
| } | |
| for orig, rep in replacements.items(): | |
| text = text.replace(orig, rep) | |
| # Filter non-latin-1 characters safely | |
| return text.encode('latin-1', 'replace').decode('latin-1') | |
| def generate_pdf_report(report_text, mode, bug_name_or_comp_a="", comp_b=""): | |
| """ | |
| Saves the therapy report text into a professionally styled case-file PDF. | |
| """ | |
| if not report_text: | |
| return None | |
| default_texts = [ | |
| "Medical file will appear here after analysis", | |
| "Group therapy session transcript will appear here", | |
| "Relationship agreement notes will appear here", | |
| "Error" | |
| ] | |
| if any(dt in report_text for dt in default_texts): | |
| return None | |
| from fpdf import FPDF | |
| import tempfile | |
| import os | |
| # Dynamic safe filename matching target formatting exactly | |
| if mode == "individual": | |
| clean_name = re.sub(r'[^a-zA-Z0-9_\-]', '', bug_name_or_comp_a.strip().replace(' ', '_')) | |
| filename = f"{clean_name if clean_name else 'Bug'}_CaseFile.pdf" | |
| elif mode == "group": | |
| filename = "GroupTherapy_Report.pdf" | |
| elif mode == "relationship": | |
| filename = "RelationshipCounseling_Report.pdf" | |
| else: | |
| filename = "Therapy_Report.pdf" | |
| # Write to unique subdirectory to completely prevent permission locks | |
| temp_sub = tempfile.mkdtemp(prefix="bug_pdf_") | |
| path = os.path.join(temp_sub, filename) | |
| # PDF Configuration | |
| pdf = FPDF() | |
| pdf.add_page() | |
| pdf.set_margins(20, 20, 20) | |
| pdf.set_auto_page_break(auto=True, margin=20) | |
| # Clinic letterhead in clinical typewriter Courier | |
| pdf.set_font("Courier", "B", 16) | |
| pdf.cell(0, 10, "BUG THERAPY CLINIC", new_x="LMARGIN", new_y="NEXT", align="C") | |
| pdf.set_font("Courier", "", 9) | |
| pdf.cell(0, 5, "LICENSED SOFTWARE PSYCHOLOGISTS | CASE FILE CONFIDENTIAL", new_x="LMARGIN", new_y="NEXT", align="C") | |
| pdf.ln(4) | |
| pdf.line(20, pdf.get_y(), 190, pdf.get_y()) | |
| pdf.ln(8) | |
| # Metadata Block | |
| date_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| pdf.set_font("Courier", "", 9) | |
| pdf.cell(0, 5, f"DATE OF SESSION: {date_str}", new_x="LMARGIN", new_y="NEXT") | |
| pdf.cell(0, 5, f"THERAPY CLASSIFICATION: {mode.upper()} INTERVENTION", new_x="LMARGIN", new_y="NEXT") | |
| pdf.cell(0, 5, "STATUS: ARCHIVED NOTES", new_x="LMARGIN", new_y="NEXT") | |
| pdf.ln(6) | |
| pdf.line(20, pdf.get_y(), 190, pdf.get_y()) | |
| pdf.ln(8) | |
| # Parsing report | |
| lines = report_text.split("\n") | |
| for line in lines: | |
| line = clean_txt_for_pdf(line.strip()) | |
| if not line: | |
| continue | |
| # Draw clean solid partition lines for markdown horizontal dividers | |
| if line.startswith("---") or line.startswith("___") or (line.startswith("-") and len(line) > 5): | |
| pdf.ln(3) | |
| pdf.line(20, pdf.get_y(), 190, pdf.get_y()) | |
| pdf.ln(5) | |
| continue | |
| # Heading 1 | |
| if line.startswith("# "): | |
| pdf.ln(4) | |
| pdf.set_font("Courier", "B", 13) | |
| content = line.replace("# ", "").strip() | |
| pdf.cell(0, 8, content, new_x="LMARGIN", new_y="NEXT") | |
| pdf.set_font("Courier", "", 10) | |
| pdf.ln(2) | |
| # Bold labels | |
| elif line.startswith("**"): | |
| pdf.ln(1) | |
| parts = line.split("**") | |
| if len(parts) >= 3: | |
| pdf.set_font("Courier", "B", 10) | |
| pdf.write(6, parts[1] + ": ") | |
| pdf.set_font("Courier", "", 10) | |
| pdf.write(6, "".join(parts[2:]).strip() + "\n") | |
| else: | |
| pdf.set_font("Courier", "", 10) | |
| pdf.multi_cell(0, 6, line.replace("**", "")) | |
| pdf.ln(2) | |
| # Monospace bullet points | |
| elif line.startswith("* ") or line.startswith("- "): | |
| pdf.set_font("Courier", "", 10) | |
| content = line[2:].strip() | |
| pdf.multi_cell(0, 6, f" * {content}") | |
| pdf.ln(1) | |
| else: | |
| pdf.set_font("Courier", "", 10) | |
| pdf.multi_cell(0, 6, line) | |
| pdf.ln(1.5) | |
| pdf.output(path) | |
| return path | |
| # Simulated section-by-section progress stream generators | |
| def run_therapy_individual_stream(bug_text, personality="None"): | |
| with concurrent.futures.ThreadPoolExecutor() as executor: | |
| future = executor.submit(run_therapy_individual, bug_text, personality) | |
| stages = [ | |
| ("⏳ Case File", "⏳ Trauma History", "⏳ Therapy Session", "⏳ Treatment Plan"), | |
| ("✓ Case File", "⏳ Trauma History", "⏳ Therapy Session", "⏳ Treatment Plan"), | |
| ("✓ Case File", "✓ Trauma History", "⏳ Therapy Session", "⏳ Treatment Plan"), | |
| ("✓ Case File", "✓ Trauma History", "✓ Therapy Session", "⏳ Treatment Plan"), | |
| ("✓ Case File", "✓ Trauma History", "✓ Therapy Session", "✓ Treatment Plan") | |
| ] | |
| stage_idx = 0 | |
| while not future.done(): | |
| progress_md = f"""# Consulting Patient... | |
| -------------------------------- | |
| {stages[stage_idx][0]} | |
| {stages[stage_idx][1]} | |
| {stages[stage_idx][2]} | |
| {stages[stage_idx][3]} | |
| """ | |
| yield progress_md | |
| time.sleep(1.5) | |
| if stage_idx < len(stages) - 1: | |
| stage_idx += 1 | |
| try: | |
| yield future.result() | |
| except Exception as e: | |
| yield f"# Case File\n\nPatient:\n{bug_text}\n\n--------------------------------\n\n# Error\n\nTherapy interrupted: {str(e)}" | |
| def run_therapy_group_stream(bugs_text): | |
| with concurrent.futures.ThreadPoolExecutor() as executor: | |
| future = executor.submit(run_therapy_group, bugs_text) | |
| stages = [ | |
| ("⏳ Case File: Group Circle", "⏳ Circle Dynamics", "⏳ Therapy Session", "⏳ Group Treatment Plan"), | |
| ("✓ Case File: Group Circle", "⏳ Circle Dynamics", "⏳ Therapy Session", "⏳ Group Treatment Plan"), | |
| ("✓ Case File: Group Circle", "✓ Circle Dynamics", "⏳ Therapy Session", "⏳ Group Treatment Plan"), | |
| ("✓ Case File: Group Circle", "✓ Circle Dynamics", "✓ Therapy Session", "⏳ Group Treatment Plan"), | |
| ("✓ Case File: Group Circle", "✓ Circle Dynamics", "✓ Therapy Session", "✓ Group Treatment Plan") | |
| ] | |
| stage_idx = 0 | |
| while not future.done(): | |
| progress_md = f"""# Group Session In Progress... | |
| -------------------------------- | |
| {stages[stage_idx][0]} | |
| {stages[stage_idx][1]} | |
| {stages[stage_idx][2]} | |
| {stages[stage_idx][3]} | |
| """ | |
| yield progress_md | |
| time.sleep(1.5) | |
| if stage_idx < len(stages) - 1: | |
| stage_idx += 1 | |
| try: | |
| yield future.result() | |
| except Exception as e: | |
| yield f"# Case File: Group Circle\n\n--------------------------------\n\n# Error\n\nGroup therapy interrupted: {str(e)}" | |
| def run_therapy_relationship_stream(component_a, component_b, conflict): | |
| with concurrent.futures.ThreadPoolExecutor() as executor: | |
| future = executor.submit(run_therapy_relationship, component_a, component_b, conflict) | |
| stages = [ | |
| ("⏳ Case File: Couples Counseling", "⏳ Relationship Status", "⏳ Therapy Session", "⏳ Relationship Agreement"), | |
| ("✓ Case File: Couples Counseling", "⏳ Relationship Status", "⏳ Therapy Session", "⏳ Relationship Agreement"), | |
| ("✓ Case File: Couples Counseling", "✓ Relationship Status", "⏳ Therapy Session", "⏳ Relationship Agreement"), | |
| ("✓ Case File: Couples Counseling", "✓ Relationship Status", "✓ Therapy Session", "⏳ Relationship Agreement"), | |
| ("✓ Case File: Couples Counseling", "✓ Relationship Status", "✓ Therapy Session", "✓ Relationship Agreement") | |
| ] | |
| stage_idx = 0 | |
| while not future.done(): | |
| progress_md = f"""# Relationship Mediation In Progress... | |
| -------------------------------- | |
| {stages[stage_idx][0]} | |
| {stages[stage_idx][1]} | |
| {stages[stage_idx][2]} | |
| {stages[stage_idx][3]} | |
| """ | |
| yield progress_md | |
| time.sleep(1.5) | |
| if stage_idx < len(stages) - 1: | |
| stage_idx += 1 | |
| try: | |
| yield future.result() | |
| except Exception as e: | |
| yield f"# Case File: Couples Counseling\n\n--------------------------------\n\n# Error\n\nCouples therapy interrupted: {str(e)}" | |
| # Build Gradio UI | |
| with gr.Blocks(title="bug therapist") as demo: | |
| # Header Section | |
| gr.HTML( | |
| """ | |
| <div style="text-align: center; padding: 24px 0 12px 0;"> | |
| <h1 class="clinical-title">bug therapist</h1> | |
| <p class="clinical-subtitle">clinical psychoanalysis for software defects.</p> | |
| </div> | |
| """ | |
| ) | |
| with gr.Tabs(elem_classes="tabs") as tabs: | |
| # TAB 1: Individual Session | |
| with gr.Tab("Individual Therapy", id=1): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| ind_bug_input = gr.Textbox( | |
| label="admit patient (paste stack trace, code snippet, or error log)", | |
| lines=8, | |
| placeholder="e.g. java.lang.NullPointerException: Attempt to invoke virtual method..." | |
| ) | |
| ind_personality = gr.Dropdown( | |
| choices=[ | |
| "None", | |
| "Abandonment issues (feeling ignored by developer)", | |
| "Control freak (compulsion to govern all processing states)", | |
| "Commitment issues (refusal to resolve callback/promise states)", | |
| "Identity crisis (type instability, constant casting errors)", | |
| "Attention seeker (verbose logs, printing internal operations)", | |
| "Existential dread (cycles of execution without clear intent)" | |
| ], | |
| value="None", | |
| label="Choose a Trauma (Optional)", | |
| info="Leave blank and the therapist will diagnose automatically." | |
| ) | |
| with gr.Column(): | |
| ind_btn = gr.Button("diagnose bug", elem_classes=["diagnose-btn"]) | |
| with gr.Column(scale=1): | |
| ind_output = gr.Markdown( | |
| value="*Medical file will appear here after analysis.*", | |
| elem_classes=["patient-file-container"] | |
| ) | |
| # Normal button that triggers hidden DownloadButton click | |
| ind_export_btn = gr.Button("download case report pdf", elem_classes=["export-btn"]) | |
| gr.Examples( | |
| examples=[ | |
| ["NullPointerException", "Abandonment issues (feeling ignored by developer)"], | |
| ["Memory Leak in loop", "Control freak (compulsion to govern all processing states)"], | |
| ["Segmentation Fault", "Existential dread (cycles of execution without clear intent)"], | |
| ["RecursionError: maximum recursion depth exceeded", "Identity crisis (type instability, constant casting errors)"] | |
| ], | |
| inputs=[ind_bug_input, ind_personality], | |
| label="common admissions", | |
| cache_examples=False | |
| ) | |
| # TAB 2: Group Therapy | |
| with gr.Tab("Group Therapy", id=2): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| # Preset triggers right above textbox | |
| gr.Markdown("Quick admission presets:") | |
| with gr.Row(): | |
| preset_classic = gr.Button("classic trio", elem_classes=["pill-btn"]) | |
| preset_concurrency = gr.Button("concurrency crisis", elem_classes=["pill-btn"]) | |
| preset_boundary = gr.Button("boundary conflict", elem_classes=["pill-btn"]) | |
| gr.HTML("<div style='height: 10px;'></div>") | |
| group_circle_name = gr.Textbox( | |
| label="selected circle preset", | |
| interactive=False, | |
| placeholder="Select a preset circle or enter custom bugs below" | |
| ) | |
| group_bugs_input = gr.Textbox( | |
| label="admit patient group (separated by commas)", | |
| lines=6, | |
| placeholder="e.g. NullPointerException, Memory Leak, Segmentation Fault" | |
| ) | |
| with gr.Column(): | |
| group_btn = gr.Button("counsel group", elem_classes=["diagnose-btn"]) | |
| with gr.Column(scale=1): | |
| group_output = gr.Markdown( | |
| value="*Group therapy session transcript will appear here.*", | |
| elem_classes=["patient-file-container"] | |
| ) | |
| # Normal button that triggers hidden DownloadButton click | |
| group_export_btn = gr.Button("download group report pdf", elem_classes=["export-btn"]) | |
| # Map quick preset buttons | |
| preset_classic.click( | |
| fn=lambda: ("Classic Trio", "NullPointerException, Memory Leak, Segmentation Fault"), | |
| inputs=None, | |
| outputs=[group_circle_name, group_bugs_input] | |
| ) | |
| preset_concurrency.click( | |
| fn=lambda: ("Concurrency Crisis", "Deadlock, Race Condition, Thread Leak"), | |
| inputs=None, | |
| outputs=[group_circle_name, group_bugs_input] | |
| ) | |
| preset_boundary.click( | |
| fn=lambda: ("Boundary Conflict", "Segmentation Fault, IndexOutOfBoundsException, OffByOneError"), | |
| inputs=None, | |
| outputs=[group_circle_name, group_bugs_input] | |
| ) | |
| # Standard Circles dynamic dataframe | |
| group_history_df = gr.Dataframe( | |
| headers=["Circle Name", "Bugs"], | |
| datatype=["str", "str"], | |
| value=[ | |
| ["Classic Trio", "NullPointerException, Memory Leak, Segmentation Fault"], | |
| ["Concurrency Crisis", "Deadlock, Race Condition, Thread Leak"], | |
| ["Boundary Conflict", "Segmentation Fault, IndexOutOfBoundsException, OffByOneError"] | |
| ], | |
| interactive=False, | |
| label="standard circles" | |
| ) | |
| group_history_df.select( | |
| fn=select_group_row, | |
| inputs=[group_history_df], | |
| outputs=[group_circle_name, group_bugs_input] | |
| ) | |
| # TAB 3: Relationship Counseling | |
| with gr.Tab("Relationship Counseling", id=3): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("Quick relationship presets:") | |
| with gr.Row(): | |
| preset_api = gr.Button("api friction", elem_classes=["pill-btn"]) | |
| preset_nesting = gr.Button("nested hell", elem_classes=["pill-btn"]) | |
| preset_history = gr.Button("force pushing", elem_classes=["pill-btn"]) | |
| gr.HTML("<div style='height: 10px;'></div>") | |
| rel_comp_a = gr.Textbox( | |
| label="component a", | |
| placeholder="e.g. React Frontend" | |
| ) | |
| rel_comp_b = gr.Textbox( | |
| label="component b", | |
| placeholder="e.g. Django Backend" | |
| ) | |
| rel_conflict = gr.Textbox( | |
| label="architectural conflict description", | |
| lines=4, | |
| placeholder="e.g. component a queries component b relentlessly; component b returns silent timeouts." | |
| ) | |
| with gr.Column(): | |
| rel_btn = gr.Button("counsel couple", elem_classes=["diagnose-btn"]) | |
| with gr.Column(scale=1): | |
| rel_output = gr.Markdown( | |
| value="*Relationship agreement notes will appear here.*", | |
| elem_classes=["patient-file-container"] | |
| ) | |
| # Normal button that triggers hidden DownloadButton click | |
| rel_export_btn = gr.Button("download couples report pdf", elem_classes=["export-btn"]) | |
| # Map quick relationship presets | |
| preset_api.click( | |
| fn=lambda: ("React Frontend", "Django Backend", "Frontend floods backend with requests"), | |
| inputs=None, | |
| outputs=[rel_comp_a, rel_comp_b, rel_conflict] | |
| ) | |
| preset_nesting.click( | |
| fn=lambda: ("Callback", "Promise", "Communication delays and trust issues"), | |
| inputs=None, | |
| outputs=[rel_comp_a, rel_comp_b, rel_conflict] | |
| ) | |
| preset_history.click( | |
| fn=lambda: ("Git CLI", "Developer", "Force pushes without communication"), | |
| inputs=None, | |
| outputs=[rel_comp_a, rel_comp_b, rel_conflict] | |
| ) | |
| # Troubled Couplings dynamic dataframe | |
| couples_history_df = gr.Dataframe( | |
| headers=["Component A", "Component B", "Conflict"], | |
| datatype=["str", "str", "str"], | |
| value=[ | |
| ["React Frontend", "Django Backend", "Frontend floods backend with requests"], | |
| ["Git CLI", "Developer", "Force pushes without communication"], | |
| ["Microservice A", "Microservice B", "Circular dependency blame game"], | |
| ["Callback", "Promise", "Communication delays and trust issues"] | |
| ], | |
| interactive=False, | |
| label="troubled couplings" | |
| ) | |
| couples_history_df.select( | |
| fn=select_couples_row, | |
| inputs=[couples_history_df], | |
| outputs=[rel_comp_a, rel_comp_b, rel_conflict] | |
| ) | |
| # Hidden download buttons to execute reliable one-click download stream via event trigger | |
| ind_download_hidden = gr.DownloadButton("Download Hidden 1", elem_id="ind_download_hidden") | |
| group_download_hidden = gr.DownloadButton("Download Hidden 2", elem_id="group_download_hidden") | |
| rel_download_hidden = gr.DownloadButton("Download Hidden 3", elem_id="rel_download_hidden") | |
| # Event handlers | |
| # 1. Individual | |
| ind_btn.click( | |
| run_therapy_individual_stream, | |
| inputs=[ind_bug_input, ind_personality], | |
| outputs=ind_output, | |
| show_progress="minimal" | |
| ) | |
| ind_export_btn.click( | |
| fn=lambda report, bug: generate_pdf_report(report, "individual", bug), | |
| inputs=[ind_output, ind_bug_input], | |
| outputs=ind_download_hidden, | |
| show_progress="minimal" | |
| ).then( | |
| fn=None, | |
| inputs=None, | |
| outputs=None, | |
| js="() => { const link = document.querySelector('#ind_download_hidden a') || document.querySelector('#ind_download_hidden'); if (link) link.click(); }" | |
| ) | |
| # 2. Group | |
| group_btn.click( | |
| run_therapy_group_stream, | |
| inputs=[group_bugs_input], | |
| outputs=group_output, | |
| show_progress="minimal" | |
| ).then( | |
| fn=add_group_to_history, | |
| inputs=[group_circle_name, group_bugs_input, group_history_df], | |
| outputs=group_history_df | |
| ) | |
| group_export_btn.click( | |
| fn=lambda report: generate_pdf_report(report, "group"), | |
| inputs=group_output, | |
| outputs=group_download_hidden, | |
| show_progress="minimal" | |
| ).then( | |
| fn=None, | |
| inputs=None, | |
| outputs=None, | |
| js="() => { setTimeout(() => { const link = document.querySelector('#group_download_hidden a') || document.querySelector('#group_download_hidden'); if (link) link.click(); }, 150); }" | |
| ) | |
| # 3. Relationship | |
| rel_btn.click( | |
| run_therapy_relationship_stream, | |
| inputs=[rel_comp_a, rel_comp_b, rel_conflict], | |
| outputs=rel_output, | |
| show_progress="minimal" | |
| ).then( | |
| fn=add_couples_to_history, | |
| inputs=[rel_comp_a, rel_comp_b, rel_conflict, couples_history_df], | |
| outputs=couples_history_df | |
| ) | |
| rel_export_btn.click( | |
| fn=lambda report, a, b: generate_pdf_report(report, "relationship", a, b), | |
| inputs=[rel_output, rel_comp_a, rel_comp_b], | |
| outputs=rel_download_hidden, | |
| show_progress="minimal" | |
| ).then( | |
| fn=None, | |
| inputs=None, | |
| outputs=None, | |
| js="() => { const link = document.querySelector('#rel_download_hidden a') || document.querySelector('#rel_download_hidden'); if (link) link.click(); }" | |
| ) | |
| # Launch app locally | |
| if __name__ == "__main__": | |
| demo.launch(css=custom_css) |