Flame-Forged's picture
Update app.py
f1be842 verified
Raw
History Blame Contribute Delete
10 kB
import os
import gradio as gr
import pandas as pd
from anthropic import Anthropic
from datetime import datetime
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
client = Anthropic(api_key=ANTHROPIC_API_KEY) if ANTHROPIC_API_KEY else None
SYSTEM_PROMPT = """You are a Hybrid Intelligence (HI) research classifier.
Analyze human-AI interaction excerpts and return a structured classification in this exact markdown format:
**Primary Classification:** [one of: Collaborative Synthesis / Delegative / Augmentative / Corrective / Exploratory / Dependent / Resistant / Co-creative]
**Confidence:** [High / Medium / Low]
**Confidence Reasoning:** [1 sentence explaining your confidence level]
**Secondary Tags:** [comma-separated list of relevant tags e.g. user-led, task-completion, emotional-labour, error-correction, creative-expansion]
**Interaction Dynamics:** [2-3 sentences on power balance, initiative, and who is driving the interaction]
**HI Research Notes:** [2-3 sentences on what makes this excerpt significant for Hybrid Intelligence research]
**Recruiter-Facing Summary:** [1-2 sentences in plain language describing what happened, suitable for a non-technical audience]
Be precise and consistent. Use only the classification categories listed above."""
def classify_single(excerpt, notes):
if not excerpt.strip():
return "Please paste an interaction excerpt first.", None
if client is None:
return "Missing ANTHROPIC_API_KEY — add it in Space Settings → Secrets.", None
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1000,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": f"Classify this interaction:\n\n{excerpt}"}]
)
result = response.content[0].text
log_entry = {
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"excerpt": excerpt[:200] + "..." if len(excerpt) > 200 else excerpt,
"classification": result,
"user_notes": notes
}
return result, log_entry
except Exception as e:
return f"API error: {str(e)}", None
def classify_batch(batch_text):
if not batch_text.strip():
return "Please paste at least one excerpt.", []
if client is None:
return "Missing ANTHROPIC_API_KEY — add it in Space Settings → Secrets.", []
excerpts = [e.strip() for e in batch_text.split("---") if e.strip()]
if not excerpts:
return "No excerpts found. Separate them with --- on its own line.", []
results = []
log_entries = []
for i, excerpt in enumerate(excerpts, 1):
try:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1000,
system=SYSTEM_PROMPT,
messages=[{"role": "user", "content": f"Classify this interaction:\n\n{excerpt}"}]
)
result = response.content[0].text
results.append(f"## Excerpt {i}\n\n{result}\n\n---")
log_entries.append({
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"excerpt": excerpt[:200] + "..." if len(excerpt) > 200 else excerpt,
"classification": result,
"user_notes": f"Batch item {i}"
})
except Exception as e:
results.append(f"## Excerpt {i}\n\nAPI error: {str(e)}\n\n---")
return "\n\n".join(results), log_entries
def add_to_log(current_log, new_entry):
if new_entry:
current_log.append(new_entry)
return current_log, render_log(current_log)
def add_batch_to_log(current_log, new_entries):
current_log.extend(new_entries)
return current_log, render_log(current_log)
def render_log(current_log):
if not current_log:
return pd.DataFrame(columns=["timestamp", "excerpt", "classification", "user_notes"])
return pd.DataFrame(current_log)
def export_log(current_log):
if not current_log:
return None
df = pd.DataFrame(current_log)
file_path = "hi_classifications_export.csv"
df.to_csv(file_path, index=False)
return file_path
custom_css = """
body, .gradio-container {
background-color: #0d0d1a !important;
color: #e0e0f0 !important;
font-family: 'Georgia', serif !important;
}
.gradio-container h1 {
font-size: 2.2em !important;
font-weight: 900 !important;
background: linear-gradient(90deg, #a855f7, #f59e0b) !important;
-webkit-background-clip: text !important;
-webkit-text-fill-color: transparent !important;
padding-bottom: 6px !important;
}
.gradio-container p, .gradio-container label {
color: #c4b5fd !important;
}
.tab-nav {
background: #1a1a2e !important;
border-bottom: 2px solid #7c3aed !important;
}
.tab-nav button {
color: #a0aec0 !important;
font-weight: 600 !important;
font-size: 1em !important;
border-radius: 6px 6px 0 0 !important;
padding: 10px 24px !important;
}
.tab-nav button.selected {
background: #7c3aed !important;
color: #ffffff !important;
border-bottom: none !important;
}
input[type="text"], textarea, select, .gr-box {
background-color: #1a1a2e !important;
color: #e0e0f0 !important;
border: 1px solid #4c1d95 !important;
border-radius: 6px !important;
}
input[type="text"]:focus, textarea:focus {
border-color: #a855f7 !important;
outline: none !important;
box-shadow: 0 0 0 2px rgba(168, 85, 247, 0.3) !important;
}
button.primary {
background: linear-gradient(90deg, #7c3aed, #a855f7) !important;
color: white !important;
border: none !important;
font-weight: 700 !important;
font-size: 1em !important;
padding: 10px 28px !important;
border-radius: 8px !important;
cursor: pointer !important;
transition: opacity 0.2s !important;
}
button.primary:hover { opacity: 0.85 !important; }
button.secondary {
background: #1a1a2e !important;
color: #a855f7 !important;
border: 1px solid #7c3aed !important;
font-weight: 600 !important;
padding: 10px 28px !important;
border-radius: 8px !important;
cursor: pointer !important;
}
table {
background-color: #12122a !important;
border-collapse: collapse !important;
width: 100% !important;
}
th {
background-color: #4c1d95 !important;
color: #f59e0b !important;
font-weight: 700 !important;
text-transform: uppercase !important;
font-size: 0.78em !important;
letter-spacing: 0.08em !important;
padding: 10px 14px !important;
border-bottom: 2px solid #7c3aed !important;
}
td {
background-color: #0d0d1a !important;
color: #e0e0f0 !important;
padding: 6px 14px !important;
border-bottom: 1px solid #1e1e3a !important;
font-size: 0.9em !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
vertical-align: middle !important;
}
tr:hover td { background-color: #1a1a2e !important; }
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: #0d0d1a; }
::-webkit-scrollbar-thumb { background: #7c3aed; border-radius: 3px; }
"""
with gr.Blocks(title="HI Interaction Classifier") as demo:
gr.Markdown("# HI Interaction Classifier")
gr.Markdown(
"Classify human-AI interaction excerpts for the Hybrid Intelligence research dataset."
)
log_state = gr.State([])
with gr.Tab("Classify"):
excerpt_input = gr.Textbox(
label="Interaction Excerpt",
placeholder="Paste the interaction excerpt here...",
lines=8
)
notes_input = gr.Textbox(
label="Your Notes (optional)",
placeholder="Context, observations, flags...",
lines=2
)
with gr.Row():
classify_btn = gr.Button("Classify", variant="primary")
save_btn = gr.Button("Save to Log", variant="secondary")
classification_output = gr.Markdown(label="Classification")
pending_entry = gr.State(None)
classify_btn.click(
fn=classify_single,
inputs=[excerpt_input, notes_input],
outputs=[classification_output, pending_entry]
)
save_btn.click(
fn=add_to_log,
inputs=[log_state, pending_entry],
outputs=[log_state, gr.Dataframe(visible=False)]
).then(lambda: "✅ Saved to log!", outputs=gr.Markdown())
with gr.Tab("Batch Classify"):
gr.Markdown(
"Paste multiple excerpts separated by `---` on its own line. "
"All results will be auto-saved to the log."
)
batch_input = gr.Textbox(
label="Batch Excerpts",
placeholder="Excerpt one...\n---\nExcerpt two...\n---\nExcerpt three...",
lines=12
)
batch_btn = gr.Button("Classify All", variant="primary")
batch_output = gr.Markdown(label="Batch Results")
batch_entries = gr.State([])
batch_btn.click(
fn=classify_batch,
inputs=[batch_input],
outputs=[batch_output, batch_entries]
).then(
fn=add_batch_to_log,
inputs=[log_state, batch_entries],
outputs=[log_state, gr.Dataframe(visible=False)]
)
with gr.Tab("Log & Export"):
refresh_btn = gr.Button("Refresh Log", variant="primary")
log_table = gr.Dataframe(
label="Classification History",
wrap=False,
elem_classes=["table-wrap"]
)
export_btn = gr.Button("Export as CSV", variant="secondary")
download = gr.File(label="Download CSV")
refresh_btn.click(
fn=render_log,
inputs=[log_state],
outputs=[log_table]
)
export_btn.click(
fn=export_log,
inputs=[log_state],
outputs=[download]
)
demo.launch(css=custom_css)