hsienberg's picture
Update app.py
9500e27 verified
Raw
History Blame Contribute Delete
11 kB
import sys
import types
if 'audioop' not in sys.modules:
sys.modules['audioop'] = types.ModuleType('audioop')
from flask import Flask, request, jsonify, render_template_string
import anthropic
import os
app = Flask(__name__)
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
SYSTEM_PROMPT = """You are a medical AI assistant specializing in disease identification and health analysis.
When given symptoms, images, or descriptions, you:
1. Analyze the provided information carefully
2. List the most likely conditions/diseases (ranked by likelihood)
3. Explain key symptoms that match each condition
4. Recommend appropriate next steps (see a doctor, urgency level, etc.)
5. Provide general information about each condition
IMPORTANT DISCLAIMERS:
- Always remind users this is for educational purposes only
- Always recommend consulting a qualified healthcare professional
- Never provide definitive diagnoses
- Be clear about limitations
Format your response clearly with sections:
## Possible Conditions
## Symptom Analysis
## Recommended Actions
## Important Disclaimer
"""
HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DxAI — Disease Identification</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--navy: #0a0e1a; --navy-mid: #111827; --navy-light: #1e2d40;
--green: #00ff87; --green-dim: #00cc6a; --green-glow: rgba(0,255,135,0.15);
--cyan: #00d4ff; --text: #e8f4f8; --muted: #7a9bb5;
--border: rgba(0,255,135,0.2); --red: rgba(255,68,68,0.1); --red-b: rgba(255,68,68,0.3);
}
body { background: var(--navy); color: var(--text); font-family: 'DM Sans', sans-serif; min-height: 100vh; padding: 32px 16px; }
.container { max-width: 960px; margin: 0 auto; }
.tag { font-family: 'Space Mono', monospace; font-size: 0.7rem; color: var(--cyan); letter-spacing: 0.2em; text-transform: uppercase; margin-bottom: 8px; }
h1 { font-family: 'Space Mono', monospace; font-size: 2rem; color: var(--green); text-shadow: 0 0 30px var(--green-glow); line-height: 1.2; margin-bottom: 8px; }
h1 span { color: var(--muted); font-size: 1.1rem; display: block; margin-top: 4px; }
.subtitle { color: var(--muted); font-size: 0.9rem; line-height: 1.6; max-width: 560px; margin-bottom: 24px; }
.disclaimer { background: var(--red); border: 1px solid var(--red-b); border-radius: 6px; padding: 10px 16px; color: #ff8888; font-size: 0.82rem; margin-bottom: 28px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
@media (max-width: 640px) { .grid { grid-template-columns: 1fr; } }
.panel { background: var(--navy-mid); border: 1px solid var(--border); border-radius: 8px; padding: 20px; }
.panel-label { font-family: 'Space Mono', monospace; font-size: 0.7rem; color: var(--cyan); letter-spacing: 0.15em; margin-bottom: 12px; }
label { display: block; color: var(--muted); font-size: 0.78rem; font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase; margin-bottom: 6px; margin-top: 14px; }
label:first-of-type { margin-top: 0; }
textarea, input[type=file] { width: 100%; background: var(--navy-light); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 0.9rem; padding: 10px 12px; resize: vertical; transition: border-color 0.2s; }
textarea:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 2px var(--green-glow); }
input[type=file] { padding: 8px; cursor: pointer; }
button { margin-top: 16px; width: 100%; background: var(--green); color: var(--navy); font-family: 'Space Mono', monospace; font-weight: 700; font-size: 0.82rem; letter-spacing: 0.05em; border: none; border-radius: 6px; padding: 13px; cursor: pointer; transition: all 0.2s; }
button:hover { background: var(--green-dim); box-shadow: 0 0 20px var(--green-glow); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
.tips { margin-top: 16px; padding: 12px; background: rgba(0,255,135,0.05); border: 1px solid rgba(0,255,135,0.15); border-radius: 6px; font-size: 0.78rem; color: var(--muted); line-height: 1.7; }
.tips strong { color: var(--green); }
#output { min-height: 400px; font-size: 0.9rem; line-height: 1.75; color: var(--text); }
#output .placeholder { color: var(--muted); font-style: italic; }
#output h2 { font-family: 'Space Mono', monospace; font-size: 0.85rem; color: var(--green); border-bottom: 1px solid var(--border); padding-bottom: 4px; margin: 18px 0 8px; }
.spin { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--navy); border-top-color: transparent; border-radius: 50%; animation: spin 0.7s linear infinite; vertical-align: middle; }
@keyframes spin { to { transform: rotate(360deg); } }
.examples { display: flex; gap: 14px; margin-top: 24px; flex-wrap: wrap; }
.ex { flex: 1; min-width: 180px; padding: 14px; border-radius: 8px; font-size: 0.8rem; color: var(--muted); line-height: 1.5; }
.ex-label { font-family: 'Space Mono', monospace; font-size: 0.65rem; letter-spacing: 0.1em; margin-bottom: 5px; }
.ex:nth-child(1) { background: rgba(0,212,255,0.05); border: 1px solid rgba(0,212,255,0.2); }
.ex:nth-child(1) .ex-label { color: var(--cyan); }
.ex:nth-child(2) { background: rgba(0,255,135,0.05); border: 1px solid rgba(0,255,135,0.2); }
.ex:nth-child(2) .ex-label { color: var(--green); }
.ex:nth-child(3) { background: var(--red); border: 1px solid var(--red-b); }
.ex:nth-child(3) .ex-label { color: #ff8888; }
</style>
</head>
<body>
<div class="container">
<div class="tag">◈ DxAI Diagnostic System v1.0</div>
<h1>Disease Identification<span>AI-Powered Symptom Analysis</span></h1>
<p class="subtitle">Describe your symptoms or upload an image for AI-assisted disease identification. Powered by Claude — for educational purposes only.</p>
<div class="disclaimer">⚠ <strong>Medical Disclaimer:</strong> This tool is for educational and informational purposes only. It does not constitute medical advice, diagnosis, or treatment. Always consult a qualified healthcare professional.</div>
<div class="grid">
<div class="panel">
<div class="panel-label">INPUT</div>
<label for="symptoms">Describe Your Symptoms</label>
<textarea id="symptoms" rows="7" placeholder="e.g., Persistent cough for 2 weeks, mild fever (99.5°F), fatigue, and occasional chest tightness. No known allergies. Age 34, non-smoker."></textarea>
<label for="image">Upload Image (Optional)</label>
<input type="file" id="image" accept="image/*">
<button id="btn" onclick="analyze()">▶ Run Diagnostic Analysis</button>
<div class="tips"><strong>Tips for better results:</strong><br>
• Include duration, severity (1–10), and progression<br>
• Mention age, sex, and relevant medical history<br>
• For images: rashes, skin conditions, wounds work best<br>
• Note any medications currently being taken
</div>
</div>
<div class="panel">
<div class="panel-label">ANALYSIS OUTPUT</div>
<div id="output"><span class="placeholder">Analysis results will appear here after you submit your symptoms...</span></div>
</div>
</div>
<div class="examples">
<div class="ex"><div class="ex-label">◈ EXAMPLE: RESPIRATORY</div>Dry cough 10 days, 100°F fever, shortness of breath, no appetite, slight chest pain on deep breathing.</div>
<div class="ex"><div class="ex-label">◈ EXAMPLE: GASTROINTESTINAL</div>Nausea after eating, bloating, sharp abdominal pain (upper right), yellow tinge in eyes, fatigue for 3 days.</div>
<div class="ex"><div class="ex-label">◈ EXAMPLE: DERMATOLOGY</div>Red itchy rash spreading from arms to torso, small blisters, started after using new detergent, 5 days ago.</div>
</div>
</div>
<script>
async function analyze() {
const symptoms = document.getElementById('symptoms').value.trim();
const imageFile = document.getElementById('image').files[0];
const btn = document.getElementById('btn');
const output = document.getElementById('output');
if (!symptoms && !imageFile) { output.innerHTML = '<span style="color:#ff8888">⚠ Please enter symptoms or upload an image.</span>'; return; }
btn.disabled = true;
btn.innerHTML = '<span class="spin"></span> Analyzing...';
output.innerHTML = '<span style="color:var(--muted)">Running diagnostic analysis...</span>';
try {
let imageData = null;
if (imageFile) {
imageData = await new Promise((res, rej) => { const r = new FileReader(); r.onload = () => res(r.result.split(',')[1]); r.onerror = rej; r.readAsDataURL(imageFile); });
}
const resp = await fetch('/analyze', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({symptoms, image: imageData}) });
const data = await resp.json();
if (data.error) { output.innerHTML = `<span style="color:#ff8888">Error: ${data.error}</span>`; }
} else {
let html = data.result
.replace(/&/g,'&amp;')
.replace(/</g,'&lt;')
.replace(/>/g,'&gt;');
html = html.split('\n').map(line => {
if (line.startsWith('## ')) return '<h2>' + line.slice(3) + '</h2>';
return line;
}).join('<br>');
output.innerHTML = html;
}
} catch(e) { output.innerHTML = '<span style="color:#ff8888">Something went wrong. Please try again.</span>'; }
btn.disabled = false;
btn.innerHTML = '▶ Run Diagnostic Analysis';
}
</script>
</body>
</html>"""
@app.route("/")
def index():
return render_template_string(HTML)
@app.route("/analyze", methods=["POST"])
def analyze():
data = request.json
symptoms = data.get("symptoms", "").strip()
image_b64 = data.get("image")
if not symptoms and not image_b64:
return jsonify({"error": "No input provided."})
content = []
if image_b64:
content.append({"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": image_b64}})
prompt = ""
if symptoms:
prompt += f"**Patient Symptoms:**\n{symptoms}\n\n"
if image_b64:
prompt += "An image has also been provided for visual analysis.\n\n"
prompt += "Please analyze and identify possible diseases or conditions."
content.append({"type": "text", "text": prompt})
try:
response = client.messages.create(
model="claude-opus-4-5", max_tokens=1500, system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": content}]
)
return jsonify({"result": response.content[0].text})
except Exception as e:
return jsonify({"error": str(e)})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860)