Spaces:
Sleeping
Sleeping
import gradio as gr | |
import json | |
import os | |
import logging | |
import requests | |
import re | |
from datetime import datetime | |
# Configure logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
# Anthropic API key - can be set as HuggingFace secret or environment variable | |
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "") | |
# Check if API key is available | |
if ANTHROPIC_API_KEY: | |
logger.info("Claude API key found") | |
else: | |
logger.warning("Claude API key not found - using demo mode") | |
def call_claude_api(prompt): | |
"""Call Claude API directly""" | |
if not ANTHROPIC_API_KEY: | |
return "β Claude API key not configured. Please set ANTHROPIC_API_KEY environment variable." | |
try: | |
headers = { | |
"Content-Type": "application/json", | |
"x-api-key": ANTHROPIC_API_KEY, | |
"anthropic-version": "2023-06-01" | |
} | |
data = { | |
"model": "claude-3-5-sonnet-20241022", | |
"max_tokens": 4096, | |
"messages": [ | |
{ | |
"role": "user", | |
"content": prompt | |
} | |
] | |
} | |
response = requests.post( | |
"https://api.anthropic.com/v1/messages", | |
headers=headers, | |
json=data, | |
timeout=60 | |
) | |
if response.status_code == 200: | |
response_json = response.json() | |
return response_json['content'][0]['text'] | |
else: | |
logger.error(f"Claude API error: {response.status_code} - {response.text}") | |
return f"β Claude API Error: {response.status_code}" | |
except Exception as e: | |
logger.error(f"Error calling Claude API: {str(e)}") | |
return f"β Error: {str(e)}" | |
def process_file(file): | |
"""Process uploaded file""" | |
if file is None: | |
return "Please upload a file first." | |
try: | |
# Read file content | |
with open(file.name, 'r', encoding='utf-8', errors='ignore') as f: | |
content = f.read() | |
if not content.strip(): | |
return "File appears to be empty." | |
return content | |
except Exception as e: | |
return f"Error reading file: {str(e)}" | |
def read_cha_file(file_path): | |
"""Read and parse a .cha transcript file""" | |
try: | |
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | |
content = f.read() | |
# Extract participant lines (starting with *PAR:) | |
par_lines = [] | |
for line in content.splitlines(): | |
if line.startswith('*PAR:'): | |
par_lines.append(line) | |
# If no PAR lines found, just return the whole content | |
if not par_lines: | |
return content | |
return '\n'.join(par_lines) | |
except Exception as e: | |
logger.error(f"Error reading CHA file: {str(e)}") | |
return "" | |
def process_upload(file): | |
"""Process an uploaded file (text or CHA)""" | |
if file is None: | |
return "" | |
file_path = file.name | |
if file_path.endswith('.cha'): | |
return read_cha_file(file_path) | |
else: | |
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | |
return f.read() | |
def generate_demo_response(prompt): | |
"""Generate a demo response when API is not available""" | |
return """## Speech Factors Analysis | |
**Difficulty producing fluent speech**: 8 instances, moderate severity | |
- Examples: "today I would &-um like to talk about &-um a fun trip" | |
- "we went to the &-um &-um beach [//] no to the mountains [//] I mean the beach actually" | |
**Word retrieval issues**: 6 instances, mild-moderate severity | |
- Examples: "what do you call those &-um &-um sprinkles! that's the word" | |
- "sometimes I forget [//] forgetted [: forgot] [*] what they call those things we built" | |
**Grammatical errors**: 4 instances, moderate severity | |
- Examples: "after swimming we [//] I eat [: ate] [*] &-um ice cream" | |
- "we saw [/] saw fishies [: fish] [*] swimming in the water" | |
**Repetitions and revisions**: 5 instances, mild severity | |
- Examples: "we [/] we stayed for &-um three no [//] four days" | |
- "I want to go back to the beach [/] beach next year" | |
## Language Skills Assessment | |
**Lexical/Semantic Skills**: | |
- Vocabulary diversity appears age-appropriate with some word-finding difficulties | |
- Examples: "what do you call those &-um &-um sprinkles! that's the word" | |
- Shows good semantic understanding but retrieval challenges | |
**Syntactic Skills**: | |
- Basic sentence structure is intact with some grammatical inconsistencies | |
- Examples: "my brother he [//] he helped me dig a big hole" | |
- Verb tense errors noted: "forgetted" for "forgot", "eat" for "ate" | |
**Supralinguistic Skills**: | |
- Narrative organization is good with logical sequence | |
- Examples: "sometimes I wonder [/] wonder where fishies [: fish] [*] go when it's cold" | |
- Shows creative thinking and topic maintenance | |
## Treatment Recommendations | |
1. **Word-finding strategies**: Implement semantic cuing techniques using the patient's experiences (beach, ice cream) as context | |
2. **Grammar practice**: Focus on verb tense consistency with structured exercises | |
3. **Fluency techniques**: Work on reducing fillers and improving speech flow | |
4. **Self-monitoring**: Help patient identify and correct grammatical errors | |
5. **Vocabulary expansion**: Build on existing semantic networks | |
## Clinical Summary | |
This child demonstrates a mild-to-moderate expressive language disorder with primary concerns in word retrieval and grammatical accuracy. Strengths include good narrative organization and topic maintenance. The pattern suggests intervention should focus on word-finding strategies and grammatical form practice while building on existing semantic knowledge.""" | |
def analyze_transcript(transcript, age, gender, slp_notes=""): | |
"""Analyze a speech transcript using Claude""" | |
if not transcript or len(transcript.strip()) < 50: | |
return "Error: Please provide a longer transcript for analysis." | |
# Add SLP notes to the prompt if provided | |
notes_section = "" | |
if slp_notes and slp_notes.strip(): | |
notes_section = f""" | |
SLP CLINICAL NOTES: | |
{slp_notes.strip()} | |
""" | |
# Simplified analysis prompt | |
prompt = f""" | |
You are a speech-language pathologist analyzing a transcript for CASL assessment. | |
Patient: {age}-year-old {gender} | |
TRANSCRIPT: | |
{transcript}{notes_section} | |
Please provide a comprehensive CASL analysis including: | |
1. SPEECH FACTORS (with counts and severity): | |
- Difficulty producing fluent speech | |
- Word retrieval issues | |
- Grammatical errors | |
- Repetitions and revisions | |
2. LANGUAGE SKILLS ASSESSMENT: | |
- Lexical/Semantic Skills (qualitative assessment) | |
- Syntactic Skills (qualitative assessment) | |
- Supralinguistic Skills (qualitative assessment) | |
3. TREATMENT RECOMMENDATIONS: | |
- List 3-5 specific intervention strategies | |
4. CLINICAL SUMMARY: | |
- Brief explanation of findings and prognosis | |
Use exact quotes from the transcript as evidence. | |
Focus on qualitative observations rather than standardized scores. | |
Be specific and provide concrete examples from the transcript. | |
{f"Consider the SLP clinical notes in your analysis." if slp_notes and slp_notes.strip() else ""} | |
""" | |
# Get analysis from Claude API or demo | |
if ANTHROPIC_API_KEY: | |
result = call_claude_api(prompt) | |
else: | |
result = generate_demo_response(prompt) | |
return result | |
def create_interface(): | |
"""Create the Gradio interface""" | |
with gr.Blocks(title="Enhanced CASL Analysis Tool", theme=gr.themes.Soft()) as app: | |
gr.Markdown("# π£οΈ Enhanced CASL Analysis Tool") | |
gr.Markdown("Upload a speech transcript and get comprehensive CASL assessment results.") | |
with gr.Tabs(): | |
# Analysis Tab | |
with gr.Tab("π Analysis"): | |
with gr.Row(): | |
with gr.Column(): | |
gr.Markdown("### Patient Information") | |
with gr.Row(): | |
age = gr.Number(label="Age", value=8, minimum=1, maximum=120) | |
gender = gr.Radio(["male", "female", "other"], label="Gender", value="male") | |
slp_notes = gr.Textbox( | |
label="SLP Clinical Notes (Optional)", | |
placeholder="Enter any additional clinical observations, context, or notes...", | |
lines=3 | |
) | |
gr.Markdown("### Transcript Input") | |
file_upload = gr.File( | |
label="Upload Transcript File", | |
file_types=[".txt", ".cha"] | |
) | |
transcript = gr.Textbox( | |
label="Or Paste Transcript Here", | |
placeholder="Enter transcript text or upload a file...", | |
lines=10 | |
) | |
analyze_btn = gr.Button("π Analyze Transcript", variant="primary") | |
with gr.Column(): | |
gr.Markdown("### Analysis Results") | |
analysis_output = gr.Textbox( | |
label="CASL Analysis Report", | |
placeholder="Analysis results will appear here...", | |
lines=25, | |
max_lines=30 | |
) | |
# Sample Transcripts Tab | |
with gr.Tab("π Sample Transcripts"): | |
with gr.Row(): | |
with gr.Column(): | |
gr.Markdown("### Sample Transcripts") | |
sample_choice = gr.Dropdown( | |
choices=[ | |
"Beach Trip (Child)", | |
"School Day (Adolescent)", | |
"Adult Recovery" | |
], | |
label="Select a sample transcript:", | |
value="Beach Trip (Child)" | |
) | |
load_sample_btn = gr.Button("Load Sample", variant="secondary") | |
sample_transcript = gr.Textbox( | |
label="Sample Transcript", | |
lines=15, | |
interactive=False | |
) | |
use_sample_btn = gr.Button("Use This Sample for Analysis", variant="primary") | |
with gr.Column(): | |
gr.Markdown("### Sample Descriptions") | |
gr.Markdown(""" | |
**Beach Trip (Child)**: 8-year-old child describing a family beach vacation | |
- Shows typical child language patterns | |
- Contains word-finding difficulties and grammatical errors | |
- Good narrative structure despite language challenges | |
**School Day (Adolescent)**: Teenager describing a school day | |
- More complex language but still some disfluencies | |
- Shows adolescent speech patterns | |
- Academic vocabulary and social language | |
**Adult Recovery**: Adult describing stroke recovery | |
- Post-stroke language patterns | |
- Word-finding difficulties | |
- Shows recovery progress | |
""") | |
# Sample transcripts | |
SAMPLE_TRANSCRIPTS = { | |
"Beach Trip (Child)": """*PAR: today I would &-um like to talk about &-um a fun trip I took last &-um summer with my family. | |
*PAR: we went to the &-um &-um beach [//] no to the mountains [//] I mean the beach actually. | |
*PAR: there was lots of &-um &-um swimming and &-um sun. | |
*PAR: we [/] we stayed for &-um three no [//] four days in a &-um hotel near the water [: ocean] [*]. | |
*PAR: my favorite part was &-um building &-um castles with sand. | |
*PAR: sometimes I forget [//] forgetted [: forgot] [*] what they call those things we built. | |
*PAR: my brother he [//] he helped me dig a big hole. | |
*PAR: we saw [/] saw fishies [: fish] [*] swimming in the water. | |
*PAR: sometimes I wonder [/] wonder where fishies [: fish] [*] go when it's cold. | |
*PAR: maybe they have [/] have houses under the water. | |
*PAR: after swimming we [//] I eat [: ate] [*] &-um ice cream with &-um chocolate things on top. | |
*PAR: what do you call those &-um &-um sprinkles! that's the word. | |
*PAR: my mom said to &-um that I could have &-um two scoops next time. | |
*PAR: I want to go back to the beach [/] beach next year.""", | |
"School Day (Adolescent)": """*PAR: yesterday was &-um kind of a weird day at school. | |
*PAR: I had this big test in math and I was like really nervous about it. | |
*PAR: when I got there [//] when I got to class the teacher said we could use calculators. | |
*PAR: I was like &-oh &-um that's good because I always mess up the &-um the calculations. | |
*PAR: there was this one problem about &-um what do you call it &-um geometry I think. | |
*PAR: I couldn't remember the formula for [//] I mean I knew it but I just couldn't think of it. | |
*PAR: so I raised my hand and asked the teacher and she was really nice about it. | |
*PAR: after the test me and my friends went to lunch and we talked about how we did. | |
*PAR: everyone was saying it was hard but I think I did okay. | |
*PAR: oh and then in English class we had to read our essays out loud. | |
*PAR: I hate doing that because I get really nervous and I start talking fast. | |
*PAR: but the teacher said mine was good which made me feel better.""", | |
"Adult Recovery": """*PAR: I &-um I want to talk about &-uh my &-um recovery. | |
*PAR: it's been &-um [//] it's hard to &-um to find the words sometimes. | |
*PAR: before the &-um the stroke I was &-um working at the &-uh at the bank. | |
*PAR: now I have to &-um practice speaking every day with my therapist. | |
*PAR: my wife she [//] she helps me a lot at home. | |
*PAR: we do &-um exercises together like &-uh reading and &-um talking about pictures. | |
*PAR: sometimes I get frustrated because I know what I want to say but &-um the words don't come out right. | |
*PAR: but I'm getting better little by little. | |
*PAR: the doctor says I'm making good progress. | |
*PAR: I hope to go back to work someday but right now I'm focusing on &-um getting better.""" | |
} | |
# Event handlers | |
def load_sample_transcript(sample_name): | |
"""Load a sample transcript""" | |
return SAMPLE_TRANSCRIPTS.get(sample_name, "") | |
def use_sample_for_analysis(sample_text, age_val, gender_val, notes): | |
"""Use sample transcript for analysis""" | |
if not sample_text: | |
return "Please load a sample transcript first." | |
return analyze_transcript(sample_text, age_val, gender_val, notes) | |
def on_analyze(transcript_text, age_val, gender_val, notes): | |
"""Handle analysis""" | |
if not transcript_text or len(transcript_text.strip()) < 50: | |
return "Error: Please provide a longer transcript for analysis." | |
return analyze_transcript(transcript_text, age_val, gender_val, notes) | |
# Connect event handlers | |
load_sample_btn.click( | |
load_sample_transcript, | |
inputs=[sample_choice], | |
outputs=[sample_transcript] | |
) | |
use_sample_btn.click( | |
use_sample_for_analysis, | |
inputs=[sample_transcript, age, gender, slp_notes], | |
outputs=[analysis_output] | |
) | |
analyze_btn.click( | |
on_analyze, | |
inputs=[transcript, age, gender, slp_notes], | |
outputs=[analysis_output] | |
) | |
# File upload handler | |
file_upload.upload(process_upload, file_upload, transcript) | |
return app | |
if __name__ == "__main__": | |
print("π Starting Enhanced CASL Analysis Tool...") | |
if not ANTHROPIC_API_KEY: | |
print("β οΈ ANTHROPIC_API_KEY not configured - analysis will show demo response") | |
print(" For HuggingFace Spaces: Add ANTHROPIC_API_KEY as a secret in your space settings") | |
print(" For local use: export ANTHROPIC_API_KEY='your-key-here'") | |
else: | |
print("β Claude API configured") | |
app = create_interface() | |
app.launch(show_api=False) |