Spaces:
Sleeping
Sleeping
Claude Code Claude Opus 4.6 commited on
Commit ·
12c98ee
1
Parent(s): ec76dfd
Claude Code: Add UI/UX improvements to Cain's chat interface
Browse files- Add conversation controls: Clear chat, Export TXT/JSON, Toggle details
- Add loading indicators with animated dots during chat processing
- Add session footer showing session ID and message count
- Add system health badge status indicator
- Enhanced CSS for chat bubble differentiation (user vs agent)
- Markdown rendering enabled in chat responses
- Session tracking variables and helper functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- .gitignore +1 -0
- app.py +302 -8
.gitignore
CHANGED
|
@@ -10,3 +10,4 @@ node_modules/
|
|
| 10 |
*.log
|
| 11 |
.DS_Store
|
| 12 |
frontend/index.html
|
|
|
|
|
|
| 10 |
*.log
|
| 11 |
.DS_Store
|
| 12 |
frontend/index.html
|
| 13 |
+
__pycache__/
|
app.py
CHANGED
|
@@ -314,6 +314,72 @@ class CircuitBreaker:
|
|
| 314 |
# Global circuit breaker for httpcore operations
|
| 315 |
httpcore_breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=30.0)
|
| 316 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
|
| 318 |
def track_async_task(task: asyncio.Task):
|
| 319 |
"""Track an async task for graceful shutdown."""
|
|
@@ -1969,6 +2035,148 @@ CUSTOM_CSS = """
|
|
| 1969 |
0%, 80%, 100% { transform: scale(0); opacity: 0.5; }
|
| 1970 |
40% { transform: scale(1); opacity: 1; }
|
| 1971 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1972 |
"""
|
| 1973 |
|
| 1974 |
# ========== Main Application ==========
|
|
@@ -2042,11 +2250,20 @@ def create_agent_office():
|
|
| 2042 |
with gr.Column(scale=2):
|
| 2043 |
# ========== Chat Interface ==========
|
| 2044 |
with gr.Group():
|
| 2045 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2046 |
|
| 2047 |
chatbot = gr.Chatbot(
|
| 2048 |
label="Conversation",
|
| 2049 |
-
height=400
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2050 |
)
|
| 2051 |
|
| 2052 |
with gr.Row():
|
|
@@ -2058,11 +2275,30 @@ def create_agent_office():
|
|
| 2058 |
)
|
| 2059 |
send_btn = gr.Button("Send", variant="primary", scale=1)
|
| 2060 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2061 |
chat_status = gr.Markdown(
|
| 2062 |
value="Ready to chat",
|
| 2063 |
label="Status"
|
| 2064 |
)
|
| 2065 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2066 |
gr.Markdown("""
|
| 2067 |
**Instructions:**
|
| 2068 |
- Type a message and click Send to chat with Cain
|
|
@@ -2070,6 +2306,11 @@ def create_agent_office():
|
|
| 2070 |
- Responses are generated based on the conversation_process tool
|
| 2071 |
""")
|
| 2072 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2073 |
# ========== Brain Information ==========
|
| 2074 |
with gr.Accordion("🧠 Brain Information", open=False):
|
| 2075 |
brain_info_display = gr.Markdown(
|
|
@@ -2282,19 +2523,72 @@ def create_agent_office():
|
|
| 2282 |
)
|
| 2283 |
|
| 2284 |
# Chat handlers
|
| 2285 |
-
def handle_chat(message: str, history: List[Tuple[str, str]]) -> Tuple[List[Tuple[str, str]], str]:
|
| 2286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2287 |
|
| 2288 |
send_btn.click(
|
| 2289 |
-
fn=
|
| 2290 |
inputs=[chat_input, chatbot],
|
| 2291 |
-
outputs=[chatbot, chat_status]
|
| 2292 |
)
|
| 2293 |
|
| 2294 |
chat_input.submit(
|
| 2295 |
-
fn=
|
| 2296 |
inputs=[chat_input, chatbot],
|
| 2297 |
-
outputs=[chatbot, chat_status]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2298 |
)
|
| 2299 |
|
| 2300 |
# Refresh handlers
|
|
|
|
| 314 |
# Global circuit breaker for httpcore operations
|
| 315 |
httpcore_breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=30.0)
|
| 316 |
|
| 317 |
+
# ========== Session Tracking for Chat ==========
|
| 318 |
+
_session_id: str = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
|
| 319 |
+
_message_count: int = 0
|
| 320 |
+
_system_status_visible: bool = True
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def get_session_info() -> tuple[str, str]:
|
| 324 |
+
"""Get current session info (session_id, message_count)."""
|
| 325 |
+
global _message_count
|
| 326 |
+
return _session_id, str(_message_count)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def increment_message_count() -> int:
|
| 330 |
+
"""Increment and return the message count."""
|
| 331 |
+
global _message_count
|
| 332 |
+
_message_count += 1
|
| 333 |
+
return _message_count
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def reset_session():
|
| 337 |
+
"""Reset session with a new ID and zero message count."""
|
| 338 |
+
global _session_id, _message_count
|
| 339 |
+
_session_id = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
|
| 340 |
+
_message_count = 0
|
| 341 |
+
return _session_id, str(_message_count), [], "Session reset. Ready for new conversation."
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
def toggle_system_status() -> tuple[bool, str]:
|
| 345 |
+
"""Toggle system status visibility."""
|
| 346 |
+
global _system_status_visible
|
| 347 |
+
_system_status_visible = not _system_status_visible
|
| 348 |
+
status_text = "✅ System details shown" if _system_status_visible else "🔒 System details hidden"
|
| 349 |
+
return _system_status_visible, status_text
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def export_chat(history: list, format_type: str = "txt") -> str:
|
| 353 |
+
"""Export chat history to a file."""
|
| 354 |
+
if not history:
|
| 355 |
+
return "No conversation to export."
|
| 356 |
+
|
| 357 |
+
import tempfile
|
| 358 |
+
timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
|
| 359 |
+
|
| 360 |
+
if format_type == "json":
|
| 361 |
+
filepath = tempfile.gettempdir() / f"cain_chat_{timestamp}.json"
|
| 362 |
+
export_data = {
|
| 363 |
+
"session_id": _session_id,
|
| 364 |
+
"timestamp": datetime.utcnow().isoformat(),
|
| 365 |
+
"messages": [{"role": "user" if i % 2 == 0 else "assistant", "content": msg}
|
| 366 |
+
for pair in history for i, msg in enumerate(pair)]
|
| 367 |
+
}
|
| 368 |
+
with open(filepath, "w") as f:
|
| 369 |
+
json.dump(export_data, f, indent=2)
|
| 370 |
+
else: # txt
|
| 371 |
+
filepath = tempfile.gettempdir() / f"cain_chat_{timestamp}.txt"
|
| 372 |
+
with open(filepath, "w") as f:
|
| 373 |
+
f.write(f"Cain Chat Export - Session: {_session_id}\n")
|
| 374 |
+
f.write(f"Exported: {datetime.utcnow().isoformat()}\n")
|
| 375 |
+
f.write("=" * 50 + "\n\n")
|
| 376 |
+
for user_msg, bot_msg in history:
|
| 377 |
+
f.write(f"User: {user_msg}\n\n")
|
| 378 |
+
f.write(f"Cain: {bot_msg}\n\n")
|
| 379 |
+
f.write("-" * 30 + "\n\n")
|
| 380 |
+
|
| 381 |
+
return f"✅ Exported to {filepath.name}"
|
| 382 |
+
|
| 383 |
|
| 384 |
def track_async_task(task: asyncio.Task):
|
| 385 |
"""Track an async task for graceful shutdown."""
|
|
|
|
| 2035 |
0%, 80%, 100% { transform: scale(0); opacity: 0.5; }
|
| 2036 |
40% { transform: scale(1); opacity: 1; }
|
| 2037 |
}
|
| 2038 |
+
|
| 2039 |
+
/* ========== Enhanced Chat Interface Styles ========== */
|
| 2040 |
+
.chatbot-container {
|
| 2041 |
+
background: var(--bg-secondary) !important;
|
| 2042 |
+
border-radius: 16px !important;
|
| 2043 |
+
border: 1px solid var(--border-color) !important;
|
| 2044 |
+
}
|
| 2045 |
+
|
| 2046 |
+
/* Chat message bubbles - user vs agent differentiation */
|
| 2047 |
+
.chatbot-message.user {
|
| 2048 |
+
background: linear-gradient(135deg, var(--accent-purple), var(--accent-blue)) !important;
|
| 2049 |
+
color: white !important;
|
| 2050 |
+
border-radius: 18px 18px 4px 18px !important;
|
| 2051 |
+
padding: 14px 20px !important;
|
| 2052 |
+
margin: 8px 0 8px auto !important;
|
| 2053 |
+
max-width: 75% !important;
|
| 2054 |
+
box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3) !important;
|
| 2055 |
+
}
|
| 2056 |
+
|
| 2057 |
+
.chatbot-message.bot {
|
| 2058 |
+
background: var(--bg-tertiary) !important;
|
| 2059 |
+
border: 1px solid var(--border-color) !important;
|
| 2060 |
+
color: var(--text-primary) !important;
|
| 2061 |
+
border-radius: 18px 18px 18px 4px !important;
|
| 2062 |
+
padding: 14px 20px !important;
|
| 2063 |
+
margin: 8px auto 8px 0 !important;
|
| 2064 |
+
max-width: 80% !important;
|
| 2065 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2) !important;
|
| 2066 |
+
}
|
| 2067 |
+
|
| 2068 |
+
/* Markdown styling within chat bubbles */
|
| 2069 |
+
.chatbot-message.bot .markdown {
|
| 2070 |
+
color: var(--text-primary) !important;
|
| 2071 |
+
}
|
| 2072 |
+
|
| 2073 |
+
.chatbot-message.bot .markdown h1,
|
| 2074 |
+
.chatbot-message.bot .markdown h2,
|
| 2075 |
+
.chatbot-message.bot .markdown h3 {
|
| 2076 |
+
color: var(--text-primary) !important;
|
| 2077 |
+
margin-top: 12px !important;
|
| 2078 |
+
}
|
| 2079 |
+
|
| 2080 |
+
.chatbot-message.bot .markdown code {
|
| 2081 |
+
background: rgba(59, 130, 246, 0.2) !important;
|
| 2082 |
+
color: var(--accent-blue) !important;
|
| 2083 |
+
padding: 3px 8px !important;
|
| 2084 |
+
border-radius: 6px !important;
|
| 2085 |
+
font-family: 'Fira Code', 'Consolas', monospace !important;
|
| 2086 |
+
}
|
| 2087 |
+
|
| 2088 |
+
.chatbot-message.bot .markdown pre {
|
| 2089 |
+
background: rgba(0, 0, 0, 0.3) !important;
|
| 2090 |
+
border-radius: 8px !important;
|
| 2091 |
+
padding: 16px !important;
|
| 2092 |
+
overflow-x: auto !important;
|
| 2093 |
+
border: 1px solid var(--border-color) !important;
|
| 2094 |
+
}
|
| 2095 |
+
|
| 2096 |
+
.chatbot-message.bot .markdown pre code {
|
| 2097 |
+
background: transparent !important;
|
| 2098 |
+
padding: 0 !important;
|
| 2099 |
+
}
|
| 2100 |
+
|
| 2101 |
+
/* ========== Session Footer ========== */
|
| 2102 |
+
.session-footer {
|
| 2103 |
+
background: linear-gradient(135deg, var(--bg-tertiary), var(--bg-secondary)) !important;
|
| 2104 |
+
border: 1px solid var(--border-color) !important;
|
| 2105 |
+
border-radius: 10px !important;
|
| 2106 |
+
padding: 12px 20px !important;
|
| 2107 |
+
margin-top: 10px !important;
|
| 2108 |
+
text-align: center !important;
|
| 2109 |
+
color: var(--text-secondary) !important;
|
| 2110 |
+
font-size: 0.9em !important;
|
| 2111 |
+
font-family: 'Segoe UI', system-ui, sans-serif !important;
|
| 2112 |
+
display: flex !important;
|
| 2113 |
+
justify-content: center !important;
|
| 2114 |
+
align-items: center !important;
|
| 2115 |
+
gap: 20px !important;
|
| 2116 |
+
}
|
| 2117 |
+
|
| 2118 |
+
.session-footer span {
|
| 2119 |
+
display: inline-flex !important;
|
| 2120 |
+
align-items: center !important;
|
| 2121 |
+
gap: 6px !important;
|
| 2122 |
+
}
|
| 2123 |
+
|
| 2124 |
+
/* ========== Enhanced Button Styles ========== */
|
| 2125 |
+
.gradio-button.secondary {
|
| 2126 |
+
background: var(--bg-tertiary) !important;
|
| 2127 |
+
border: 1px solid var(--border-color) !important;
|
| 2128 |
+
color: var(--text-secondary) !important;
|
| 2129 |
+
}
|
| 2130 |
+
|
| 2131 |
+
.gradio-button.secondary:hover {
|
| 2132 |
+
background: var(--bg-secondary) !important;
|
| 2133 |
+
border-color: var(--accent-purple) !important;
|
| 2134 |
+
color: var(--text-primary) !important;
|
| 2135 |
+
}
|
| 2136 |
+
|
| 2137 |
+
/* ========== Loading Animation Enhancement ========== */
|
| 2138 |
+
.streaming-indicator {
|
| 2139 |
+
display: flex !important;
|
| 2140 |
+
justify-content: center !important;
|
| 2141 |
+
align-items: center !important;
|
| 2142 |
+
gap: 6px !important;
|
| 2143 |
+
padding: 16px !important;
|
| 2144 |
+
background: var(--bg-tertiary) !important;
|
| 2145 |
+
border-radius: 12px !important;
|
| 2146 |
+
margin: 8px 0 !important;
|
| 2147 |
+
}
|
| 2148 |
+
|
| 2149 |
+
.streaming-dot {
|
| 2150 |
+
width: 10px !important;
|
| 2151 |
+
height: 10px !important;
|
| 2152 |
+
background: var(--accent-purple) !important;
|
| 2153 |
+
border-radius: 50% !important;
|
| 2154 |
+
animation: streamingBounce 1.4s infinite ease-in-out !important;
|
| 2155 |
+
box-shadow: 0 0 10px rgba(124, 58, 237, 0.6) !important;
|
| 2156 |
+
}
|
| 2157 |
+
|
| 2158 |
+
/* ========== Health Badge Styles ========== */
|
| 2159 |
+
.status-indicator {
|
| 2160 |
+
display: inline-flex !important;
|
| 2161 |
+
align-items: center !important;
|
| 2162 |
+
gap: 8px !important;
|
| 2163 |
+
padding: 8px 16px !important;
|
| 2164 |
+
border-radius: 24px !important;
|
| 2165 |
+
font-weight: 600 !important;
|
| 2166 |
+
font-size: 0.9em !important;
|
| 2167 |
+
}
|
| 2168 |
+
|
| 2169 |
+
.status-indicator.online {
|
| 2170 |
+
background: rgba(16, 185, 129, 0.15) !important;
|
| 2171 |
+
color: var(--accent-green) !important;
|
| 2172 |
+
border: 1px solid rgba(16, 185, 129, 0.4) !important;
|
| 2173 |
+
}
|
| 2174 |
+
|
| 2175 |
+
.status-indicator.offline {
|
| 2176 |
+
background: rgba(239, 68, 68, 0.15) !important;
|
| 2177 |
+
color: var(--accent-red) !important;
|
| 2178 |
+
border: 1px solid rgba(239, 68, 68, 0.4) !important;
|
| 2179 |
+
}
|
| 2180 |
"""
|
| 2181 |
|
| 2182 |
# ========== Main Application ==========
|
|
|
|
| 2250 |
with gr.Column(scale=2):
|
| 2251 |
# ========== Chat Interface ==========
|
| 2252 |
with gr.Group():
|
| 2253 |
+
# Chat header with status badge
|
| 2254 |
+
with gr.Row():
|
| 2255 |
+
gr.Markdown("## 💬 Chat with Cain")
|
| 2256 |
+
system_healthy_badge = gr.HTML(
|
| 2257 |
+
value='<span class="status-indicator online"><span class="status-dot online"></span>System Healthy</span>'
|
| 2258 |
+
)
|
| 2259 |
|
| 2260 |
chatbot = gr.Chatbot(
|
| 2261 |
label="Conversation",
|
| 2262 |
+
height=400,
|
| 2263 |
+
show_label=True,
|
| 2264 |
+
elem_classes=["chatbot-container"],
|
| 2265 |
+
render_markdown=True,
|
| 2266 |
+
bubble_full_width=False
|
| 2267 |
)
|
| 2268 |
|
| 2269 |
with gr.Row():
|
|
|
|
| 2275 |
)
|
| 2276 |
send_btn = gr.Button("Send", variant="primary", scale=1)
|
| 2277 |
|
| 2278 |
+
# Loading indicator (hidden by default)
|
| 2279 |
+
loading_indicator = gr.HTML(
|
| 2280 |
+
value="",
|
| 2281 |
+
visible=False
|
| 2282 |
+
)
|
| 2283 |
+
|
| 2284 |
chat_status = gr.Markdown(
|
| 2285 |
value="Ready to chat",
|
| 2286 |
label="Status"
|
| 2287 |
)
|
| 2288 |
|
| 2289 |
+
# Conversation Controls
|
| 2290 |
+
with gr.Row():
|
| 2291 |
+
clear_chat_btn = gr.Button("🗑️ Clear", size="sm", variant="stop")
|
| 2292 |
+
export_txt_btn = gr.Button("📄 Export TXT", size="sm")
|
| 2293 |
+
export_json_btn = gr.Button("📋 Export JSON", size="sm")
|
| 2294 |
+
toggle_status_btn = gr.Button("🔍 Toggle Details", size="sm")
|
| 2295 |
+
|
| 2296 |
+
export_status = gr.Markdown(
|
| 2297 |
+
value="",
|
| 2298 |
+
label="Export Status",
|
| 2299 |
+
visible=False
|
| 2300 |
+
)
|
| 2301 |
+
|
| 2302 |
gr.Markdown("""
|
| 2303 |
**Instructions:**
|
| 2304 |
- Type a message and click Send to chat with Cain
|
|
|
|
| 2306 |
- Responses are generated based on the conversation_process tool
|
| 2307 |
""")
|
| 2308 |
|
| 2309 |
+
# Session Info Footer
|
| 2310 |
+
session_info_footer = gr.HTML(
|
| 2311 |
+
value=f'<div class="session-footer">Session: {_session_id} | Messages: 0</div>'
|
| 2312 |
+
)
|
| 2313 |
+
|
| 2314 |
# ========== Brain Information ==========
|
| 2315 |
with gr.Accordion("🧠 Brain Information", open=False):
|
| 2316 |
brain_info_display = gr.Markdown(
|
|
|
|
| 2523 |
)
|
| 2524 |
|
| 2525 |
# Chat handlers
|
| 2526 |
+
def handle_chat(message: str, history: List[Tuple[str, str]]) -> Tuple[List[Tuple[str, str]], str, str, str]:
|
| 2527 |
+
"""Handle chat with loading indicator and session tracking."""
|
| 2528 |
+
increment_message_count()
|
| 2529 |
+
session_id, msg_count = get_session_info()
|
| 2530 |
+
history, status = chat_with_brain(message, history)
|
| 2531 |
+
session_html = f'<div class="session-footer">Session: {session_id} | Messages: {msg_count}</div>'
|
| 2532 |
+
return history, status, "", session_html
|
| 2533 |
+
|
| 2534 |
+
def show_loading():
|
| 2535 |
+
"""Show loading indicator."""
|
| 2536 |
+
return '<div class="streaming-indicator"><div class="streaming-dot"></div><div class="streaming-dot"></div><div class="streaming-dot"></div></div>'
|
| 2537 |
+
|
| 2538 |
+
# Send button with loading indicator
|
| 2539 |
+
def send_with_loading(message: str, history: List[Tuple[str, str]]) -> Tuple:
|
| 2540 |
+
loading_html = show_loading()
|
| 2541 |
+
return handle_chat(message, history) + (loading_html,)
|
| 2542 |
|
| 2543 |
send_btn.click(
|
| 2544 |
+
fn=send_with_loading,
|
| 2545 |
inputs=[chat_input, chatbot],
|
| 2546 |
+
outputs=[chatbot, chat_status, loading_indicator, session_info_footer]
|
| 2547 |
)
|
| 2548 |
|
| 2549 |
chat_input.submit(
|
| 2550 |
+
fn=send_with_loading,
|
| 2551 |
inputs=[chat_input, chatbot],
|
| 2552 |
+
outputs=[chatbot, chat_status, loading_indicator, session_info_footer]
|
| 2553 |
+
)
|
| 2554 |
+
|
| 2555 |
+
# Clear chat button
|
| 2556 |
+
clear_chat_btn.click(
|
| 2557 |
+
fn=reset_session,
|
| 2558 |
+
inputs=[],
|
| 2559 |
+
outputs=[session_info_footer, chat_status, chatbot, export_status]
|
| 2560 |
+
)
|
| 2561 |
+
|
| 2562 |
+
# Export buttons
|
| 2563 |
+
def export_txt(history):
|
| 2564 |
+
result = export_chat(history, "txt")
|
| 2565 |
+
return result, result
|
| 2566 |
+
|
| 2567 |
+
def export_json(history):
|
| 2568 |
+
result = export_chat(history, "json")
|
| 2569 |
+
return result, result
|
| 2570 |
+
|
| 2571 |
+
export_txt_btn.click(
|
| 2572 |
+
fn=export_txt,
|
| 2573 |
+
inputs=[chatbot],
|
| 2574 |
+
outputs=[export_status, chat_status]
|
| 2575 |
+
)
|
| 2576 |
+
|
| 2577 |
+
export_json_btn.click(
|
| 2578 |
+
fn=export_json,
|
| 2579 |
+
inputs=[chatbot],
|
| 2580 |
+
outputs=[export_status, chat_status]
|
| 2581 |
+
)
|
| 2582 |
+
|
| 2583 |
+
# Toggle status details
|
| 2584 |
+
def update_status_visibility():
|
| 2585 |
+
visible, status_text = toggle_system_status()
|
| 2586 |
+
return status_text
|
| 2587 |
+
|
| 2588 |
+
toggle_status_btn.click(
|
| 2589 |
+
fn=update_status_visibility,
|
| 2590 |
+
inputs=[],
|
| 2591 |
+
outputs=[chat_status]
|
| 2592 |
)
|
| 2593 |
|
| 2594 |
# Refresh handlers
|