inplace-demo-streamlit / streamlit_app.py
Chae
fix: code fonts
86f15b3
# streamlit_app.py
import streamlit as st
import pandas as pd
import numpy as np
from pathlib import Path
import uuid
from cards import (
chat_card,
inplace_chat_card
)
# ---------- Global state init ----------
if "init" not in st.session_state:
st.session_state.init = True
st.session_state.conversations = {} # {chat_id: {"title": str, "messages": [...]}}
st.session_state.current_chat_id = None # which conversation is open
def create_new_conversation():
chat_id = str(uuid.uuid4())
st.session_state.conversations[chat_id] = {
"title": "New chat",
"messages": [],
"editable": False,
"prev_text": "",
"edited_text": "",
"original_user_prompt": "",
"edit_history": []
}
st.session_state.current_chat_id = chat_id
# ensure at least one conversation exists
if not st.session_state.conversations:
create_new_conversation()
# Fix code block font rendering - force monospace
st.markdown("""
<style>
code, pre, pre code {
font-family: 'Space Mono', monospace !important;
font-size: 0.95rem !important;
line-height: 1.4 !important;
font-weight: 400 !important;
}
</style>
""", unsafe_allow_html=True)
pages = [
st.Page(
"inplace_chat.py",
title="In-place Feedback Chat",
icon=":material/edit_document:"
),
st.Page(
"chat.py",
title="Chat",
icon=":material/chat:"
)
]
page = st.navigation(pages)
# ---------- Sidebar: ChatGPT-style history ----------
with st.sidebar:
# new chat button
st.write("This is a simple demo for CSED499 Inplace feedback.")
st.button(
"+ New chat",
use_container_width=True,
on_click=create_new_conversation,
)
st.markdown("---")
st.markdown("### Chat History")
# Add custom CSS for hover effect on chat history items
st.markdown("""
<style>
/* Chat history item buttons */
.stButton > button[kind="secondary"] {
background-color: transparent;
border: none;
color: inherit;
padding: 8px 12px;
text-align: left;
width: 100%;
border-radius: 8px;
transition: background-color 0.2s;
justify-content: flex-start;
}
.stButton > button[kind="secondary"]:hover {
background-color: rgba(128, 128, 128, 0.2);
}
.stButton > button[kind="secondary"]:focus {
box-shadow: none;
}
/* Delete button styling */
.stButton > button[kind="secondary"].delete-btn {
width: auto;
padding: 4px 8px;
color: rgba(128, 128, 128, 0.6);
}
.stButton > button[kind="secondary"].delete-btn:hover {
color: rgba(128, 128, 128, 1);
background-color: rgba(128, 128, 128, 0.15);
}
</style>
""", unsafe_allow_html=True)
# list existing conversations as clickable text
for chat_id, conv in st.session_state.conversations.items():
# truncate title like ChatGPT
label = conv["title"]
if len(label) > 35:
label = label[:35] + "…"
# Create columns for chat item and delete button
col_chat, col_delete = st.columns([0.85, 0.15])
with col_chat:
# highlight current chat with a marker
if chat_id == st.session_state.current_chat_id:
st.markdown(f"**▶ {label}**")
else:
# Make entire text clickable with hover effect
if st.button(label, key=f"chat-{chat_id}", type="secondary", use_container_width=True):
st.session_state.current_chat_id = chat_id
st.rerun()
with col_delete:
# Delete button - only show for non-active chats or show for all
if st.button("✕", key=f"delete-{chat_id}", type="secondary", help="Delete chat"):
# If deleting current chat, switch to another one first
if chat_id == st.session_state.current_chat_id:
remaining = [cid for cid in st.session_state.conversations.keys() if cid != chat_id]
if remaining:
st.session_state.current_chat_id = remaining[0]
else:
st.session_state.current_chat_id = None
# Delete the conversation
del st.session_state.conversations[chat_id]
st.rerun()
# run selected page (after sidebar is defined)
page.run()
# === Edit History Panel ===
st.sidebar.markdown("### Edit History")
if st.session_state.current_chat_id:
current_conv = st.session_state.conversations.get(st.session_state.current_chat_id, {})
edit_history = current_conv.get("edit_history", [])
if edit_history:
st.sidebar.markdown("""
<style>
.diff-container {
font-family: 'Space Mono', monospace;
font-size: 0.85em;
line-height: 1.4;
margin: 8px 0;
padding: 8px;
background-color: rgba(128, 128, 128, 0.05);
border-radius: 4px;
}
.diff-delete {
background-color: rgba(255, 0, 0, 0.15);
color: #d73a49;
text-decoration: line-through;
padding: 2px 4px;
border-radius: 2px;
}
.diff-insert {
background-color: rgba(0, 255, 0, 0.15);
color: #22863a;
padding: 2px 4px;
border-radius: 2px;
}
.diff-equal {
color: inherit;
}
</style>
""", unsafe_allow_html=True)
for idx, edit in enumerate(reversed(edit_history)):
edit_num = len(edit_history) - idx
# Each edit as a collapsible expander
with st.sidebar.expander(f"Edit #{edit_num} at {edit['timestamp']}", expanded=False):
# Build the diff HTML
diff_html = "<div class='diff-container'>"
for chunk in edit.get('diff_chunks', []):
text = chunk['text'].replace('<', '&lt;').replace('>', '&gt;')
if chunk['tag'] == 'delete':
diff_html += f"<span class='diff-delete'>{text}</span>"
elif chunk['tag'] == 'insert':
diff_html += f"<span class='diff-insert'>{text}</span>"
else: # equal
# Truncate long unchanged sections
if len(text) > 100:
text = text[:50] + "..." + text[-50:]
diff_html += f"<span class='diff-equal'>{text}</span>"
diff_html += "</div>"
st.markdown(diff_html, unsafe_allow_html=True)
else:
st.sidebar.write("No edits yet in this conversation.")
else:
st.sidebar.write("No conversation selected.")
# st.markdown("---")
st.sidebar.caption(
"This app uses [Space Grotesk](https://fonts.google.com/specimen/Space+Grotesk) "
"and [Space Mono](https://fonts.google.com/specimen/Space+Mono) fonts."
)