Spaces:
Sleeping
Sleeping
Sửa để animation chạy liên tục cho đến khi có response từ API hoặc timeout
Browse files
app.py
CHANGED
|
@@ -537,14 +537,14 @@ class UIService:
|
|
| 537 |
|
| 538 |
@staticmethod
|
| 539 |
async def create_typing_animation():
|
| 540 |
-
"""Create typing animation effect"""
|
| 541 |
msg = cl.Message(content="", author="assistant")
|
| 542 |
await msg.send()
|
| 543 |
|
| 544 |
# Typing animation frames
|
| 545 |
typing_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
| 546 |
|
| 547 |
-
for i in range(
|
| 548 |
frame = typing_frames[i % len(typing_frames)]
|
| 549 |
msg.content = f"{frame} Đang suy nghĩ..."
|
| 550 |
await msg.update()
|
|
@@ -553,6 +553,25 @@ class UIService:
|
|
| 553 |
return msg
|
| 554 |
|
| 555 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
# HELPER FUNCTIONS: Session management with proper async error handling
|
| 557 |
async def ensure_session_state() -> Optional[ConversationState]:
|
| 558 |
"""Ensure session state exists, create if not"""
|
|
@@ -803,7 +822,7 @@ async def on_debug_sessions(action):
|
|
| 803 |
|
| 804 |
@cl.on_message
|
| 805 |
async def main(message: cl.Message):
|
| 806 |
-
"""Main message handler with
|
| 807 |
app_state = await ensure_session_state()
|
| 808 |
if app_state is None:
|
| 809 |
await cl.Message(content="Error: Session state not found", author="assistant").send()
|
|
@@ -817,14 +836,37 @@ async def main(message: cl.Message):
|
|
| 817 |
image_path = element.path
|
| 818 |
break
|
| 819 |
|
| 820 |
-
#
|
| 821 |
-
|
|
|
|
| 822 |
|
| 823 |
-
#
|
| 824 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 825 |
|
| 826 |
-
# Update
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
await typing_msg.update()
|
|
|
|
| 537 |
|
| 538 |
@staticmethod
|
| 539 |
async def create_typing_animation():
|
| 540 |
+
"""Create typing animation effect (legacy method - kept for compatibility)"""
|
| 541 |
msg = cl.Message(content="", author="assistant")
|
| 542 |
await msg.send()
|
| 543 |
|
| 544 |
# Typing animation frames
|
| 545 |
typing_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
| 546 |
|
| 547 |
+
for i in range(27): # Show animation for ~2 seconds
|
| 548 |
frame = typing_frames[i % len(typing_frames)]
|
| 549 |
msg.content = f"{frame} Đang suy nghĩ..."
|
| 550 |
await msg.update()
|
|
|
|
| 553 |
return msg
|
| 554 |
|
| 555 |
|
| 556 |
+
async def run_typing_animation(msg: cl.Message):
|
| 557 |
+
"""Run typing animation until cancelled"""
|
| 558 |
+
typing_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
| 559 |
+
frame_index = 0
|
| 560 |
+
|
| 561 |
+
try:
|
| 562 |
+
while True: # Run indefinitely until cancelled
|
| 563 |
+
frame = typing_frames[frame_index % len(typing_frames)]
|
| 564 |
+
msg.content = f"{frame} Đang suy nghĩ..."
|
| 565 |
+
await msg.update()
|
| 566 |
+
await asyncio.sleep(0.25)
|
| 567 |
+
frame_index += 1
|
| 568 |
+
|
| 569 |
+
except asyncio.CancelledError:
|
| 570 |
+
# Animation was cancelled, this is expected
|
| 571 |
+
print("🎬 Animation cancelled - API response received")
|
| 572 |
+
raise
|
| 573 |
+
|
| 574 |
+
|
| 575 |
# HELPER FUNCTIONS: Session management with proper async error handling
|
| 576 |
async def ensure_session_state() -> Optional[ConversationState]:
|
| 577 |
"""Ensure session state exists, create if not"""
|
|
|
|
| 822 |
|
| 823 |
@cl.on_message
|
| 824 |
async def main(message: cl.Message):
|
| 825 |
+
"""Main message handler with concurrent animation and API call"""
|
| 826 |
app_state = await ensure_session_state()
|
| 827 |
if app_state is None:
|
| 828 |
await cl.Message(content="Error: Session state not found", author="assistant").send()
|
|
|
|
| 836 |
image_path = element.path
|
| 837 |
break
|
| 838 |
|
| 839 |
+
# Create initial message for animation
|
| 840 |
+
msg = cl.Message(content="", author="assistant")
|
| 841 |
+
await msg.send()
|
| 842 |
|
| 843 |
+
# Create concurrent tasks for animation and API call
|
| 844 |
+
animation_task = asyncio.create_task(run_typing_animation(msg))
|
| 845 |
+
api_task = asyncio.create_task(ChatService.respond_to_chat(app_state, message.content, image_path))
|
| 846 |
+
|
| 847 |
+
try:
|
| 848 |
+
# Wait for API response (this will complete first usually)
|
| 849 |
+
response = await api_task
|
| 850 |
+
|
| 851 |
+
# Cancel animation task since we have the response
|
| 852 |
+
animation_task.cancel()
|
| 853 |
+
|
| 854 |
+
# Wait a bit for graceful animation cancellation
|
| 855 |
+
try:
|
| 856 |
+
await asyncio.wait_for(animation_task, timeout=0.1)
|
| 857 |
+
except (asyncio.CancelledError, asyncio.TimeoutError):
|
| 858 |
+
pass
|
| 859 |
+
|
| 860 |
+
except Exception as e:
|
| 861 |
+
# If API fails, cancel animation and show error
|
| 862 |
+
animation_task.cancel()
|
| 863 |
+
try:
|
| 864 |
+
await asyncio.wait_for(animation_task, timeout=0.1)
|
| 865 |
+
except (asyncio.CancelledError, asyncio.TimeoutError):
|
| 866 |
+
pass
|
| 867 |
+
response = f"Error: {e}"
|
| 868 |
|
| 869 |
+
# Update message with final response and buttons
|
| 870 |
+
msg.content = response
|
| 871 |
+
msg.actions = UIService.create_action_buttons(app_state)
|
| 872 |
+
await msg.update()
|
|
|