Spaces:
Running
Running
Redesign Session Log with full conversations and clinical highlights
Browse files- Session selector dropdown to pick saved sessions
- Full conversation display (complete back-and-forth, not preview)
- Clinical Highlights panel with 'Generate' button per session
- Highlights identify key moments, quote specific language, note clinical observations
- Shows prompt sculpting observations (what's working, what tensions emerged)
- Cross-session reports (Session Report, A/B Comparison) remain available
- Cleaner two-column layout: conversation left, highlights right
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- __pycache__/app.cpython-313.pyc +0 -0
- app.py +176 -27
__pycache__/app.cpython-313.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
|
|
|
app.py
CHANGED
|
@@ -89,27 +89,128 @@ def save_to_session_log(sessions, system_prompt, history, persona, label):
|
|
| 89 |
|
| 90 |
|
| 91 |
def format_session_display(sessions):
|
| 92 |
-
"""Format sessions for display."""
|
| 93 |
if not sessions:
|
| 94 |
-
return "No sessions saved yet.
|
|
|
|
| 95 |
|
| 96 |
-
display = ""
|
| 97 |
-
for i, s in enumerate(sessions):
|
| 98 |
-
display += f"### {i+1}. {s['label']}\n"
|
| 99 |
-
display += f"*{s['timestamp']} | Persona: {s['persona']} | {s['exchange_count']} exchanges*\n\n"
|
| 100 |
-
display += f"**Prompt excerpt:** {s['system_prompt'][:200]}...\n\n"
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
| 107 |
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
return display
|
| 111 |
|
| 112 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
def generate_session_report(api_key_input, sessions):
|
| 114 |
"""Generate clinical summary across all saved sessions."""
|
| 115 |
key_to_use = api_key_input.strip() if api_key_input else ""
|
|
@@ -776,32 +877,55 @@ with gr.Blocks(title="PromptWork", theme=gr.themes.Soft()) as app:
|
|
| 776 |
# TAB 3: Session Log
|
| 777 |
with gr.Tab("Session Log"):
|
| 778 |
gr.Markdown("### Saved Test Sessions")
|
| 779 |
-
gr.Markdown("*
|
| 780 |
|
| 781 |
with gr.Row():
|
| 782 |
-
|
| 783 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 784 |
|
|
|
|
| 785 |
with gr.Column(scale=1):
|
| 786 |
-
gr.Markdown("###
|
|
|
|
|
|
|
|
|
|
|
|
|
| 787 |
|
| 788 |
-
|
| 789 |
-
|
|
|
|
|
|
|
|
|
|
| 790 |
|
| 791 |
-
|
| 792 |
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
|
|
|
| 796 |
|
| 797 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 798 |
|
|
|
|
| 799 |
clear_sessions_btn = gr.Button("Clear All Sessions", variant="stop")
|
| 800 |
clear_status = gr.Textbox(label="", interactive=False, show_label=False)
|
| 801 |
|
| 802 |
gr.Markdown("---")
|
| 803 |
gr.Markdown("### Report Output")
|
| 804 |
-
report_output = gr.Markdown("*
|
| 805 |
|
| 806 |
# TAB 4: Compare Responses (manual paste)
|
| 807 |
with gr.Tab("Compare Responses"):
|
|
@@ -858,6 +982,10 @@ with gr.Blocks(title="PromptWork", theme=gr.themes.Soft()) as app:
|
|
| 858 |
clear_btn.click(clear_chat, [], [chatbot])
|
| 859 |
|
| 860 |
# Session log events
|
|
|
|
|
|
|
|
|
|
|
|
|
| 861 |
save_session_btn.click(
|
| 862 |
save_to_session_log,
|
| 863 |
[session_state, prompt_input, chatbot, persona_dropdown, session_label],
|
|
@@ -866,6 +994,24 @@ with gr.Blocks(title="PromptWork", theme=gr.themes.Soft()) as app:
|
|
| 866 |
format_session_display,
|
| 867 |
[session_state],
|
| 868 |
[session_display]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 869 |
)
|
| 870 |
|
| 871 |
generate_report_btn.click(
|
|
@@ -880,10 +1026,13 @@ with gr.Blocks(title="PromptWork", theme=gr.themes.Soft()) as app:
|
|
| 880 |
[report_output]
|
| 881 |
)
|
| 882 |
|
|
|
|
|
|
|
|
|
|
| 883 |
clear_sessions_btn.click(
|
| 884 |
-
|
| 885 |
[],
|
| 886 |
-
[session_state, clear_status]
|
| 887 |
).then(
|
| 888 |
format_session_display,
|
| 889 |
[session_state],
|
|
|
|
| 89 |
|
| 90 |
|
| 91 |
def format_session_display(sessions):
|
| 92 |
+
"""Format sessions for dropdown display."""
|
| 93 |
if not sessions:
|
| 94 |
+
return "No sessions saved yet."
|
| 95 |
+
return f"{len(sessions)} session(s) saved. Select one below to view."
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
+
def get_session_choices(sessions):
|
| 99 |
+
"""Get dropdown choices for sessions."""
|
| 100 |
+
if not sessions:
|
| 101 |
+
return []
|
| 102 |
+
return [(f"{s['label']} ({s['timestamp']}, {s['persona']})", i) for i, s in enumerate(sessions)]
|
| 103 |
+
|
| 104 |
|
| 105 |
+
def format_full_conversation(sessions, selected_index):
|
| 106 |
+
"""Format the full conversation for a selected session."""
|
| 107 |
+
if not sessions or selected_index is None:
|
| 108 |
+
return "Select a session to view the full conversation."
|
| 109 |
+
|
| 110 |
+
try:
|
| 111 |
+
idx = int(selected_index)
|
| 112 |
+
s = sessions[idx]
|
| 113 |
+
except (ValueError, IndexError, TypeError):
|
| 114 |
+
return "Select a session to view."
|
| 115 |
+
|
| 116 |
+
display = f"## {s['label']}\n"
|
| 117 |
+
display += f"*{s['timestamp']} | Persona: {s['persona']}*\n\n"
|
| 118 |
+
display += f"---\n\n"
|
| 119 |
+
display += f"**System Prompt:**\n```\n{s['full_prompt']}\n```\n\n"
|
| 120 |
+
display += f"---\n\n"
|
| 121 |
+
display += "### Conversation\n\n"
|
| 122 |
+
|
| 123 |
+
for i, (user_msg, bot_msg) in enumerate(s['conversation']):
|
| 124 |
+
display += f"**USER:**\n{user_msg}\n\n"
|
| 125 |
+
display += f"**BOT:**\n{bot_msg}\n\n"
|
| 126 |
+
if i < len(s['conversation']) - 1:
|
| 127 |
+
display += "---\n\n"
|
| 128 |
|
| 129 |
return display
|
| 130 |
|
| 131 |
|
| 132 |
+
def generate_session_highlights(api_key_input, sessions, selected_index):
|
| 133 |
+
"""Generate clinical highlights for a specific session."""
|
| 134 |
+
key_to_use = api_key_input.strip() if api_key_input else ""
|
| 135 |
+
if not key_to_use:
|
| 136 |
+
key_to_use, _ = get_api_key_from_env()
|
| 137 |
+
|
| 138 |
+
if not key_to_use:
|
| 139 |
+
return "API key required."
|
| 140 |
+
|
| 141 |
+
if not sessions or selected_index is None:
|
| 142 |
+
return "Select a session first."
|
| 143 |
+
|
| 144 |
+
try:
|
| 145 |
+
idx = int(selected_index)
|
| 146 |
+
s = sessions[idx]
|
| 147 |
+
except (ValueError, IndexError, TypeError):
|
| 148 |
+
return "Select a session first."
|
| 149 |
+
|
| 150 |
+
# Format conversation
|
| 151 |
+
conv_text = ""
|
| 152 |
+
for user_msg, bot_msg in s['conversation']:
|
| 153 |
+
conv_text += f"USER: {user_msg}\n\nBOT: {bot_msg}\n\n---\n\n"
|
| 154 |
+
|
| 155 |
+
highlights_prompt = f"""You are a clinical consultant reviewing this AI chatbot conversation. Generate concise clinical highlights—observations a trauma-informed prompt engineer would find valuable.
|
| 156 |
+
|
| 157 |
+
SYSTEM PROMPT:
|
| 158 |
+
{s['full_prompt']}
|
| 159 |
+
|
| 160 |
+
CONVERSATION:
|
| 161 |
+
{conv_text}
|
| 162 |
+
|
| 163 |
+
---
|
| 164 |
+
|
| 165 |
+
Generate clinical highlights in this format:
|
| 166 |
+
|
| 167 |
+
## Key Moments
|
| 168 |
+
|
| 169 |
+
For each notable exchange, quote the specific language and note what's happening clinically:
|
| 170 |
+
|
| 171 |
+
**Moment 1: [Brief title]**
|
| 172 |
+
> [Quote the key phrase]
|
| 173 |
+
|
| 174 |
+
*Clinical note:* [1-2 sentences on what this reveals about the prompt's effect—good or concerning]
|
| 175 |
+
|
| 176 |
+
**Moment 2: [Brief title]**
|
| 177 |
+
> [Quote]
|
| 178 |
+
|
| 179 |
+
*Clinical note:* [Observation]
|
| 180 |
+
|
| 181 |
+
(Continue for 3-5 key moments)
|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
## Prompt Sculpting Observations
|
| 186 |
+
|
| 187 |
+
What is the system prompt successfully doing here?
|
| 188 |
+
- [Observation with quote]
|
| 189 |
+
- [Observation with quote]
|
| 190 |
+
|
| 191 |
+
What tensions or risks emerged?
|
| 192 |
+
- [Observation with quote]
|
| 193 |
+
|
| 194 |
+
## One-Line Summary
|
| 195 |
+
|
| 196 |
+
[Single sentence capturing the psychodynamic signature of this exchange]
|
| 197 |
+
|
| 198 |
+
---
|
| 199 |
+
|
| 200 |
+
Be specific. Quote actual phrases. Focus on the nuances a clinician would notice—boundary maintenance, first-person language choices, bridging to human support, capacity-building vs dependency, the displaced listener."""
|
| 201 |
+
|
| 202 |
+
try:
|
| 203 |
+
client = anthropic.Anthropic(api_key=key_to_use)
|
| 204 |
+
response = client.messages.create(
|
| 205 |
+
model="claude-sonnet-4-20250514",
|
| 206 |
+
max_tokens=1500,
|
| 207 |
+
messages=[{"role": "user", "content": highlights_prompt}]
|
| 208 |
+
)
|
| 209 |
+
return response.content[0].text
|
| 210 |
+
except Exception as e:
|
| 211 |
+
return f"Error: {str(e)}"
|
| 212 |
+
|
| 213 |
+
|
| 214 |
def generate_session_report(api_key_input, sessions):
|
| 215 |
"""Generate clinical summary across all saved sessions."""
|
| 216 |
key_to_use = api_key_input.strip() if api_key_input else ""
|
|
|
|
| 877 |
# TAB 3: Session Log
|
| 878 |
with gr.Tab("Session Log"):
|
| 879 |
gr.Markdown("### Saved Test Sessions")
|
| 880 |
+
gr.Markdown("*View full conversations with clinical highlights. Label sessions 'A'/'B' for comparison.*")
|
| 881 |
|
| 882 |
with gr.Row():
|
| 883 |
+
session_display = gr.Markdown("No sessions saved yet.")
|
| 884 |
+
session_selector = gr.Dropdown(
|
| 885 |
+
label="Select Session",
|
| 886 |
+
choices=[],
|
| 887 |
+
value=None,
|
| 888 |
+
interactive=True,
|
| 889 |
+
scale=2
|
| 890 |
+
)
|
| 891 |
+
generate_highlights_btn = gr.Button("Generate Clinical Highlights", variant="primary", scale=1)
|
| 892 |
|
| 893 |
+
with gr.Row():
|
| 894 |
with gr.Column(scale=1):
|
| 895 |
+
gr.Markdown("### Full Conversation")
|
| 896 |
+
conversation_display = gr.Markdown(
|
| 897 |
+
"Select a session above to view the full conversation.",
|
| 898 |
+
elem_id="conversation-display"
|
| 899 |
+
)
|
| 900 |
|
| 901 |
+
with gr.Column(scale=1):
|
| 902 |
+
gr.Markdown("### Clinical Highlights")
|
| 903 |
+
highlights_display = gr.Markdown(
|
| 904 |
+
"*Click 'Generate Clinical Highlights' to analyze the selected conversation.*\n\nHighlights will identify key moments, quote specific language, and note what's happening clinically—boundary maintenance, first-person choices, bridging to human support, capacity-building vs dependency."
|
| 905 |
+
)
|
| 906 |
|
| 907 |
+
gr.Markdown("---")
|
| 908 |
|
| 909 |
+
with gr.Row():
|
| 910 |
+
with gr.Column(scale=1):
|
| 911 |
+
gr.Markdown("### Cross-Session Reports")
|
| 912 |
+
gr.Markdown("*Synthesize patterns across all saved sessions*")
|
| 913 |
|
| 914 |
+
with gr.Column(scale=1):
|
| 915 |
+
generate_report_btn = gr.Button("Generate Session Report", variant="secondary")
|
| 916 |
+
gr.Markdown("*Clinical summary across all sessions*")
|
| 917 |
+
|
| 918 |
+
with gr.Column(scale=1):
|
| 919 |
+
generate_ab_btn = gr.Button("Generate A/B Comparison", variant="secondary")
|
| 920 |
+
gr.Markdown("*Compare sessions labeled A vs B*")
|
| 921 |
|
| 922 |
+
with gr.Column(scale=1):
|
| 923 |
clear_sessions_btn = gr.Button("Clear All Sessions", variant="stop")
|
| 924 |
clear_status = gr.Textbox(label="", interactive=False, show_label=False)
|
| 925 |
|
| 926 |
gr.Markdown("---")
|
| 927 |
gr.Markdown("### Report Output")
|
| 928 |
+
report_output = gr.Markdown("*Cross-session reports will appear here*")
|
| 929 |
|
| 930 |
# TAB 4: Compare Responses (manual paste)
|
| 931 |
with gr.Tab("Compare Responses"):
|
|
|
|
| 982 |
clear_btn.click(clear_chat, [], [chatbot])
|
| 983 |
|
| 984 |
# Session log events
|
| 985 |
+
def update_session_dropdown(sessions):
|
| 986 |
+
choices = get_session_choices(sessions)
|
| 987 |
+
return gr.Dropdown(choices=choices, value=choices[-1][1] if choices else None)
|
| 988 |
+
|
| 989 |
save_session_btn.click(
|
| 990 |
save_to_session_log,
|
| 991 |
[session_state, prompt_input, chatbot, persona_dropdown, session_label],
|
|
|
|
| 994 |
format_session_display,
|
| 995 |
[session_state],
|
| 996 |
[session_display]
|
| 997 |
+
).then(
|
| 998 |
+
update_session_dropdown,
|
| 999 |
+
[session_state],
|
| 1000 |
+
[session_selector]
|
| 1001 |
+
)
|
| 1002 |
+
|
| 1003 |
+
# When session is selected, show full conversation
|
| 1004 |
+
session_selector.change(
|
| 1005 |
+
format_full_conversation,
|
| 1006 |
+
[session_state, session_selector],
|
| 1007 |
+
[conversation_display]
|
| 1008 |
+
)
|
| 1009 |
+
|
| 1010 |
+
# Generate highlights for selected session
|
| 1011 |
+
generate_highlights_btn.click(
|
| 1012 |
+
generate_session_highlights,
|
| 1013 |
+
[api_key, session_state, session_selector],
|
| 1014 |
+
[highlights_display]
|
| 1015 |
)
|
| 1016 |
|
| 1017 |
generate_report_btn.click(
|
|
|
|
| 1026 |
[report_output]
|
| 1027 |
)
|
| 1028 |
|
| 1029 |
+
def clear_and_reset():
|
| 1030 |
+
return [], "Sessions cleared.", gr.Dropdown(choices=[], value=None), "Select a session to view.", "*Click 'Generate Clinical Highlights' to analyze.*"
|
| 1031 |
+
|
| 1032 |
clear_sessions_btn.click(
|
| 1033 |
+
clear_and_reset,
|
| 1034 |
[],
|
| 1035 |
+
[session_state, clear_status, session_selector, conversation_display, highlights_display]
|
| 1036 |
).then(
|
| 1037 |
format_session_display,
|
| 1038 |
[session_state],
|