Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +169 -162
src/streamlit_app.py
CHANGED
|
@@ -10,22 +10,30 @@ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
| 10 |
from reportlab.lib.units import inch
|
| 11 |
from reportlab.lib import colors
|
| 12 |
|
| 13 |
-
config: dict = {'api-url': 'https://chat.voxio.in/agents'}
|
| 14 |
API_KEY = 'vk_JZgdwJHVmqsWeNvO9b2QvcThfUxl16moNr3wxoDyf2QER97rpi5w3XJCL0H9NRKE'
|
| 15 |
|
| 16 |
-
st.set_page_config(layout='wide')
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
|
| 31 |
def generate_pdf(summary: str, feedback: str) -> BytesIO:
|
|
@@ -34,202 +42,201 @@ def generate_pdf(summary: str, feedback: str) -> BytesIO:
|
|
| 34 |
topMargin=0.75*inch, bottomMargin=0.75*inch,
|
| 35 |
leftMargin=inch, rightMargin=inch)
|
| 36 |
styles = getSampleStyleSheet()
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
fontSize=14, textColor=colors.HexColor('#1a56db'),
|
| 46 |
-
spaceBefore=16, spaceAfter=8)
|
| 47 |
-
body_style = ParagraphStyle('Body', parent=styles['Normal'],
|
| 48 |
-
fontSize=11, leading=17)
|
| 49 |
-
|
| 50 |
-
story = [
|
| 51 |
-
Paragraph("Session Evaluation Report", title_style),
|
| 52 |
-
Paragraph(f"Generated: {datetime.now().strftime('%d %B %Y, %H:%M')}", meta_style),
|
| 53 |
-
Paragraph("Summary", h2_style),
|
| 54 |
-
]
|
| 55 |
-
for line in (summary or "No summary available.").split('\n'):
|
| 56 |
if line.strip():
|
| 57 |
-
story.append(Paragraph(line.strip(),
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
story.append(Spacer(1,
|
| 61 |
-
|
| 62 |
-
for line in (feedback or "No feedback available.").split('\n'):
|
| 63 |
if line.strip():
|
| 64 |
-
story.append(Paragraph(line.strip(),
|
| 65 |
-
story.append(Spacer(1, 4))
|
| 66 |
|
| 67 |
doc.build(story)
|
| 68 |
buf.seek(0)
|
| 69 |
return buf
|
| 70 |
|
| 71 |
|
| 72 |
-
def process_stream(response):
|
| 73 |
-
for line in response.iter_lines():
|
| 74 |
-
if not line:
|
| 75 |
-
continue
|
| 76 |
-
try:
|
| 77 |
-
data: dict = json.loads(line)
|
| 78 |
-
except json.JSONDecodeError:
|
| 79 |
-
continue
|
| 80 |
-
|
| 81 |
-
# Handle errors first β these are the only early exits
|
| 82 |
-
if 'error' in data:
|
| 83 |
-
st.error(f'Server Error: {data["error"]}')
|
| 84 |
-
st.session_state.messages.append({'role': 'error', 'content': data['error']})
|
| 85 |
-
continue
|
| 86 |
-
if 'detail' in data:
|
| 87 |
-
st.error(f'API Error: {data["detail"]}')
|
| 88 |
-
st.session_state.messages.append({'role': 'error', 'content': str(data['detail'])})
|
| 89 |
-
continue
|
| 90 |
-
|
| 91 |
-
# ββ Process every field independently β NO early continue ββββββββββββββ
|
| 92 |
-
|
| 93 |
-
# 1. Spoken NPC content β always capture this first
|
| 94 |
-
if data.get('speak'):
|
| 95 |
-
st.session_state.messages.append({
|
| 96 |
-
'role': 'assistant',
|
| 97 |
-
'content': data['speak']
|
| 98 |
-
})
|
| 99 |
-
|
| 100 |
-
# 2. Input gate β agent is waiting for trainee response
|
| 101 |
-
if data.get('waiting_for_input'):
|
| 102 |
-
st.session_state.waiting_for_var = data.get('required_variable')
|
| 103 |
-
|
| 104 |
-
# 3. Actions (accumulate across turns)
|
| 105 |
-
if data.get('actions'):
|
| 106 |
-
for a in data['actions']:
|
| 107 |
-
if a and a not in st.session_state.actions:
|
| 108 |
-
st.session_state.actions.append(a)
|
| 109 |
-
|
| 110 |
-
# 4. Session end flag
|
| 111 |
-
if str(data.get('end_diagnosis', '')).lower() in ['yes', 'true', '1']:
|
| 112 |
-
st.session_state.end_diagnosis_reached = True
|
| 113 |
-
|
| 114 |
-
# 5. Summary from API
|
| 115 |
-
if data.get('summary'):
|
| 116 |
-
st.session_state.summary = data['summary']
|
| 117 |
-
|
| 118 |
-
# 6. LLM evaluation block
|
| 119 |
-
if data.get('llm_response'):
|
| 120 |
-
st.session_state.llm_response = data['llm_response']
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
def reset_session():
|
| 124 |
-
for key in ('session_id', 'waiting_for_var', 'summary', 'llm_response'):
|
| 125 |
-
st.session_state[key] = None if key in ('session_id', 'waiting_for_var') else ''
|
| 126 |
-
st.session_state.messages = []
|
| 127 |
-
st.session_state.actions = []
|
| 128 |
-
st.session_state.end_diagnosis_reached = False
|
| 129 |
|
|
|
|
| 130 |
|
| 131 |
-
|
| 132 |
-
with st.sidebar:
|
| 133 |
-
st.title("βοΈ Session Settings")
|
| 134 |
|
| 135 |
-
|
| 136 |
-
st.info('π Session active')
|
| 137 |
|
| 138 |
-
|
| 139 |
-
st.divider()
|
| 140 |
-
st.caption("ACTIONS TRIGGERED")
|
| 141 |
-
for action in st.session_state.actions:
|
| 142 |
-
st.success(f"β
`{action}`")
|
| 143 |
|
| 144 |
-
|
| 145 |
-
st.warning("βΉοΈ Session ended by agent")
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
st.rerun()
|
| 151 |
-
|
|
|
|
| 152 |
st.write("No active session.")
|
| 153 |
|
| 154 |
-
#
|
| 155 |
-
if st.session_state.
|
| 156 |
-
st.session_state.summary or st.session_state.llm_response
|
| 157 |
-
):
|
| 158 |
st.divider()
|
| 159 |
-
st.subheader("π
|
| 160 |
-
pdf_bytes = generate_pdf(st.session_state.summary, st.session_state.llm_response)
|
| 161 |
st.download_button(
|
| 162 |
label="π₯ Download PDF",
|
| 163 |
data=pdf_bytes,
|
| 164 |
file_name=f"report_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
|
| 165 |
mime="application/pdf",
|
| 166 |
)
|
| 167 |
-
with st.expander("ποΈ Preview
|
| 168 |
-
if st.session_state.summary:
|
| 169 |
-
st.markdown("##
|
| 170 |
st.markdown(st.session_state.summary)
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
|
| 175 |
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
if st.button("π Start Session", type="primary"):
|
| 179 |
-
|
| 180 |
-
payload = {
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
-
with st.spinner('Initializingβ¦'):
|
| 184 |
-
try:
|
| 185 |
-
with requests.post(
|
| 186 |
-
f'{config["api-url"]}/start_session',
|
| 187 |
-
json=payload, stream=True
|
| 188 |
-
) as r:
|
| 189 |
if r.status_code == 200:
|
| 190 |
process_stream(r)
|
| 191 |
st.rerun()
|
| 192 |
-
|
|
|
|
| 193 |
st.error(f"Error {r.status_code}: {r.text}")
|
| 194 |
st.session_state.session_id = None
|
| 195 |
-
|
|
|
|
|
|
|
| 196 |
st.error(f"Connection Error: {e}")
|
| 197 |
st.session_state.session_id = None
|
| 198 |
|
|
|
|
| 199 |
|
| 200 |
-
|
| 201 |
-
for msg in st.session_state.messages:
|
| 202 |
-
with st.chat_message(msg['role']):
|
| 203 |
-
st.write(msg['content'])
|
| 204 |
|
|
|
|
| 205 |
|
| 206 |
-
|
| 207 |
-
if st.session_state.session_id:
|
| 208 |
-
if st.session_state.waiting_for_var:
|
| 209 |
-
with st.chat_message("assistant"):
|
| 210 |
-
st.write(f"Waiting for your responseβ¦")
|
| 211 |
|
| 212 |
-
|
| 213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
payload = {
|
| 216 |
-
'session_id': st.session_state.session_id,
|
| 217 |
-
'user_input': {st.session_state.waiting_for_var: user_text}
|
| 218 |
-
if st.session_state.waiting_for_var
|
| 219 |
-
else user_text
|
| 220 |
}
|
|
|
|
| 221 |
st.session_state.waiting_for_var = None
|
| 222 |
|
| 223 |
-
with st.spinner('Processing
|
| 224 |
-
|
|
|
|
|
|
|
| 225 |
with requests.post(
|
| 226 |
-
f'{config["api-url"]}/process_input',
|
| 227 |
-
json=payload
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
| 230 |
process_stream(r)
|
| 231 |
st.rerun()
|
| 232 |
-
|
|
|
|
| 233 |
st.error("Server Error processing input.")
|
| 234 |
-
|
|
|
|
| 235 |
st.error(f"Connection Error: {e}")
|
|
|
|
| 10 |
from reportlab.lib.units import inch
|
| 11 |
from reportlab.lib import colors
|
| 12 |
|
| 13 |
+
config : dict = {'api-url' : 'https://chat.voxio.in/agents'}
|
| 14 |
API_KEY = 'vk_JZgdwJHVmqsWeNvO9b2QvcThfUxl16moNr3wxoDyf2QER97rpi5w3XJCL0H9NRKE'
|
| 15 |
|
| 16 |
+
st.set_page_config(layout = 'wide')
|
| 17 |
|
| 18 |
+
if 'messages' not in st.session_state :
|
| 19 |
+
st.session_state.messages = []
|
| 20 |
+
|
| 21 |
+
if 'session_id' not in st.session_state :
|
| 22 |
+
st.session_state.session_id = None
|
| 23 |
+
|
| 24 |
+
if 'waiting_for_var' not in st.session_state :
|
| 25 |
+
st.session_state.waiting_for_var = None
|
| 26 |
+
|
| 27 |
+
# ββ NEW: only these two additions βββββββββββββββββββββββββββββββββββββββββββββ
|
| 28 |
+
if 'actions' not in st.session_state :
|
| 29 |
+
st.session_state.actions = []
|
| 30 |
+
|
| 31 |
+
if 'llm_response' not in st.session_state :
|
| 32 |
+
st.session_state.llm_response = None
|
| 33 |
+
|
| 34 |
+
if 'summary' not in st.session_state :
|
| 35 |
+
st.session_state.summary = None
|
| 36 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 37 |
|
| 38 |
|
| 39 |
def generate_pdf(summary: str, feedback: str) -> BytesIO:
|
|
|
|
| 42 |
topMargin=0.75*inch, bottomMargin=0.75*inch,
|
| 43 |
leftMargin=inch, rightMargin=inch)
|
| 44 |
styles = getSampleStyleSheet()
|
| 45 |
+
h1 = ParagraphStyle('H1', parent=styles['Heading1'], fontSize=20,
|
| 46 |
+
textColor=colors.HexColor('#1a56db'), spaceAfter=6)
|
| 47 |
+
body = ParagraphStyle('Body', parent=styles['Normal'], fontSize=11, leading=17)
|
| 48 |
+
|
| 49 |
+
story = []
|
| 50 |
+
story.append(Paragraph("# Summary", h1))
|
| 51 |
+
story.append(Spacer(1, 6))
|
| 52 |
+
for line in (summary or '').split('\n'):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
if line.strip():
|
| 54 |
+
story.append(Paragraph(line.strip(), body))
|
| 55 |
+
story.append(Spacer(1, 20))
|
| 56 |
+
story.append(Paragraph("# Feedback", h1))
|
| 57 |
+
story.append(Spacer(1, 6))
|
| 58 |
+
for line in (feedback or '').split('\n'):
|
|
|
|
| 59 |
if line.strip():
|
| 60 |
+
story.append(Paragraph(line.strip(), body))
|
|
|
|
| 61 |
|
| 62 |
doc.build(story)
|
| 63 |
buf.seek(0)
|
| 64 |
return buf
|
| 65 |
|
| 66 |
|
| 67 |
+
def process_stream(response) :
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
+
for line in response.iter_lines() :
|
| 70 |
|
| 71 |
+
if line :
|
|
|
|
|
|
|
| 72 |
|
| 73 |
+
try :
|
|
|
|
| 74 |
|
| 75 |
+
data : dict = json.loads(line)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
+
if 'error' in data :
|
|
|
|
| 78 |
|
| 79 |
+
st.error(f'Server Error: {data["error"]}')
|
| 80 |
+
st.session_state.messages.append({'role' : 'error' , 'content' : data['error']})
|
| 81 |
+
|
| 82 |
+
elif 'detail' in data :
|
| 83 |
+
|
| 84 |
+
st.error(f'API Error: {data["detail"]}')
|
| 85 |
+
st.session_state.messages.append({'role' : 'error' , 'content' : str(data['detail'])})
|
| 86 |
+
|
| 87 |
+
elif data.get('waiting_for_input') :
|
| 88 |
+
|
| 89 |
+
req_var = data.get('required_variable')
|
| 90 |
+
st.session_state.waiting_for_var = req_var
|
| 91 |
+
|
| 92 |
+
else :
|
| 93 |
+
|
| 94 |
+
content = data.get('out' , data)
|
| 95 |
+
st.session_state.messages.append({'role' : 'assistant' , 'content' : str(content)})
|
| 96 |
+
|
| 97 |
+
# ββ NEW: capture actions, summary, llm_response ββββββββ
|
| 98 |
+
if data.get('actions') :
|
| 99 |
+
for a in data['actions'] :
|
| 100 |
+
if a and a not in st.session_state.actions :
|
| 101 |
+
st.session_state.actions.append(a)
|
| 102 |
+
|
| 103 |
+
if data.get('summary') :
|
| 104 |
+
st.session_state.summary = data['summary']
|
| 105 |
+
|
| 106 |
+
if data.get('llm_response') :
|
| 107 |
+
st.session_state.llm_response = data['llm_response']
|
| 108 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 109 |
+
|
| 110 |
+
except json.JSONDecodeError :
|
| 111 |
+
st.error(f'Failed to decode JSON line: {line}')
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
with st.sidebar :
|
| 115 |
+
|
| 116 |
+
st.title("Settings")
|
| 117 |
+
|
| 118 |
+
if st.session_state.session_id :
|
| 119 |
+
|
| 120 |
+
st.info(f'Active Session: `{st.session_state.session_id}`')
|
| 121 |
+
|
| 122 |
+
# ββ NEW: show actions ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 123 |
+
if st.session_state.actions :
|
| 124 |
+
st.divider()
|
| 125 |
+
for a in st.session_state.actions :
|
| 126 |
+
st.warning(f'β‘ Action: `{a}`')
|
| 127 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 128 |
+
|
| 129 |
+
if st.button('End Session' , type = 'primary') :
|
| 130 |
+
|
| 131 |
+
st.session_state.session_id = None
|
| 132 |
+
st.session_state.messages = []
|
| 133 |
+
st.session_state.waiting_for_var = None
|
| 134 |
+
st.session_state.actions = []
|
| 135 |
+
st.session_state.llm_response = None
|
| 136 |
+
st.session_state.summary = None
|
| 137 |
st.rerun()
|
| 138 |
+
|
| 139 |
+
else :
|
| 140 |
st.write("No active session.")
|
| 141 |
|
| 142 |
+
# ββ NEW: PDF download once llm_response arrives ββββββββββββββββββββββββ
|
| 143 |
+
if st.session_state.llm_response :
|
|
|
|
|
|
|
| 144 |
st.divider()
|
| 145 |
+
st.subheader("π Report Ready")
|
| 146 |
+
pdf_bytes = generate_pdf(st.session_state.summary or '', st.session_state.llm_response)
|
| 147 |
st.download_button(
|
| 148 |
label="π₯ Download PDF",
|
| 149 |
data=pdf_bytes,
|
| 150 |
file_name=f"report_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
|
| 151 |
mime="application/pdf",
|
| 152 |
)
|
| 153 |
+
with st.expander("ποΈ Preview") :
|
| 154 |
+
if st.session_state.summary :
|
| 155 |
+
st.markdown("## Summary")
|
| 156 |
st.markdown(st.session_state.summary)
|
| 157 |
+
st.markdown("## Feedback")
|
| 158 |
+
st.markdown(st.session_state.llm_response)
|
| 159 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 160 |
|
| 161 |
|
| 162 |
+
if not st.session_state.session_id :
|
| 163 |
+
|
| 164 |
+
if st.button("π Start Session" , type = "primary") :
|
| 165 |
+
|
| 166 |
+
payload = {
|
| 167 |
+
"session_id" : str(uuid4()) ,
|
| 168 |
+
"api_key" : API_KEY
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
st.session_state.session_id = payload['session_id']
|
| 172 |
+
|
| 173 |
+
with st.spinner('Initializing Workflow...') :
|
| 174 |
+
|
| 175 |
+
try :
|
| 176 |
+
|
| 177 |
+
with requests.post(f'{config["api-url"]}/start_session', json=payload, stream=True) as r:
|
| 178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
if r.status_code == 200:
|
| 180 |
process_stream(r)
|
| 181 |
st.rerun()
|
| 182 |
+
|
| 183 |
+
else :
|
| 184 |
st.error(f"Error {r.status_code}: {r.text}")
|
| 185 |
st.session_state.session_id = None
|
| 186 |
+
|
| 187 |
+
except Exception as e :
|
| 188 |
+
|
| 189 |
st.error(f"Connection Error: {e}")
|
| 190 |
st.session_state.session_id = None
|
| 191 |
|
| 192 |
+
chat_container = st.container()
|
| 193 |
|
| 194 |
+
with chat_container :
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
+
for msg in st.session_state.messages :
|
| 197 |
|
| 198 |
+
with st.chat_message(msg['role']) :
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
|
| 200 |
+
if msg['role'] == 'assistant' :
|
| 201 |
+
st.write(msg['content'])
|
| 202 |
+
|
| 203 |
+
else :
|
| 204 |
+
st.write(msg['content'])
|
| 205 |
+
|
| 206 |
+
if st.session_state.session_id and st.session_state.waiting_for_var :
|
| 207 |
+
|
| 208 |
+
with st.chat_message("assistant") :
|
| 209 |
+
|
| 210 |
+
st.write(f"I need a value for: **{st.session_state.waiting_for_var}**")
|
| 211 |
+
|
| 212 |
+
if user_text := st.chat_input("Enter response...") :
|
| 213 |
+
|
| 214 |
+
st.session_state.messages.append({'role' : 'user' , 'content' : user_text})
|
| 215 |
|
| 216 |
payload = {
|
| 217 |
+
'session_id' : st.session_state.session_id ,
|
| 218 |
+
'user_input' : {st.session_state.waiting_for_var : user_text}
|
|
|
|
|
|
|
| 219 |
}
|
| 220 |
+
|
| 221 |
st.session_state.waiting_for_var = None
|
| 222 |
|
| 223 |
+
with st.spinner('Processing...') :
|
| 224 |
+
|
| 225 |
+
try :
|
| 226 |
+
|
| 227 |
with requests.post(
|
| 228 |
+
f'{config["api-url"]}/process_input' ,
|
| 229 |
+
json = payload ,
|
| 230 |
+
stream = True
|
| 231 |
+
) as r :
|
| 232 |
+
|
| 233 |
+
if r.status_code == 200 :
|
| 234 |
+
|
| 235 |
process_stream(r)
|
| 236 |
st.rerun()
|
| 237 |
+
|
| 238 |
+
else :
|
| 239 |
st.error("Server Error processing input.")
|
| 240 |
+
|
| 241 |
+
except Exception as e :
|
| 242 |
st.error(f"Connection Error: {e}")
|