diff --git a/backend/app/services/assistant_chat_service.py b/backend/app/services/assistant_chat_service.py index 6e17b8e3..184fd744 100644 --- a/backend/app/services/assistant_chat_service.py +++ b/backend/app/services/assistant_chat_service.py @@ -303,6 +303,8 @@ async def _call_anthropic_cached( } ] + _mcp_active = mcp_servers is not anthropic.NOT_GIVEN + try: response = await client.beta.messages.create( model=settings.AI_MODEL_ANTHROPIC, @@ -313,12 +315,22 @@ async def _call_anthropic_cached( tools=tools, betas=["mcp-client-2025-11-20"], ) - except anthropic.BadRequestError as e: - # MCP server failures (rate limits, connection errors) should not - # block the assistant entirely — retry without MCP tools. - if "MCP server" in str(e) and mcp_servers is not anthropic.NOT_GIVEN: - logger.warning("MCP server error, retrying without MCP: %s", e) - response = await client.beta.messages.create( + except Exception as e: + # MCP server failures surface as many error types — BadRequestError, + # APIStatusError, APIConnectionError, APITimeoutError. Always retry + # without MCP when MCP was active, so a flaky external server never + # blocks the assistant entirely. + _is_mcp_error = _mcp_active and ( + "MCP server" in str(e) + or "mcp" in type(e).__name__.lower() + or isinstance(e, (anthropic.BadRequestError, anthropic.APIStatusError)) + ) + if _is_mcp_error: + logger.warning( + "MCP server error (%s), retrying without MCP: %s", + type(e).__name__, e, + ) + response = await client.messages.create( model=settings.AI_MODEL_ANTHROPIC, max_tokens=max_tokens, system=system_blocks, diff --git a/frontend/src/components/assistant/ChatMessage.tsx b/frontend/src/components/assistant/ChatMessage.tsx index d905aada..c9a214d2 100644 --- a/frontend/src/components/assistant/ChatMessage.tsx +++ b/frontend/src/components/assistant/ChatMessage.tsx @@ -7,9 +7,10 @@ interface ChatMessageProps { role: 'user' | 'assistant' content: string suggestedFlows?: SuggestedFlow[] + imageUrls?: string[] } -export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps) { +export function ChatMessage({ role, content, suggestedFlows, imageUrls }: ChatMessageProps) { return (
{/* Avatar */} @@ -25,6 +26,21 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps) {/* Content */}
+ {/* Image attachments (user messages only) */} + {role === 'user' && imageUrls && imageUrls.length > 0 && ( +
+ {imageUrls.map((url, i) => ( + + {`Attachment + + ))} +
+ )} +
u.status === 'done' && u.result?.id) - .map((u) => u.result!.id) + const completedUploads = pendingUploads.filter((u) => u.status === 'done' && u.result?.id) + const completedUploadIds = completedUploads.map((u) => u.result!.id) + const imageUrls = completedUploads + .filter((u) => u.preview) + .map((u) => u.preview) setInput('') setPendingUploads([]) - setMessages(prev => [...prev, { role: 'user', content: userMessage }]) + setMessages(prev => [...prev, { role: 'user', content: userMessage, imageUrls: imageUrls.length > 0 ? imageUrls : undefined }]) setLoading(true) const sentForChatId = activeChatId @@ -363,11 +366,21 @@ export default function AssistantChatPage() { setActiveActions(response.actions || []) setShowTaskLane(true) } - } catch { - setMessages(prev => [ - ...prev, - { role: 'assistant', content: 'Sorry, something went wrong. Please try again.' }, - ]) + } catch (err: unknown) { + console.error('[AssistantChat] sendChatMessage failed:', err) + const status = (err as { response?: { status?: number } })?.response?.status + let errorMsg: string + if (status === 429) { + errorMsg = "You're sending messages too quickly. Wait a moment and try again." + } else if (status === 502 || status === 503) { + errorMsg = "The AI is temporarily unavailable. Please try again in a few seconds." + } else { + errorMsg = "Something went wrong sending your message. Please try again." + } + // Remove the optimistic user message and restore it to the input so they can retry + setMessages(prev => prev.slice(0, -1)) + setInput(userMessage) + toast.error(errorMsg) } finally { setLoading(false) requestAnimationFrame(() => inputRef.current?.focus()) @@ -421,11 +434,14 @@ export default function AssistantChatPage() { setActiveQuestions([]) setActiveActions([]) } - } catch { - setMessages(prev => [ - ...prev, - { role: 'assistant', content: 'Sorry, something went wrong processing your responses. Please try again.' }, - ]) + } catch (err: unknown) { + console.error('[AssistantChat] handleTaskSubmit failed:', err) + const status = (err as { response?: { status?: number } })?.response?.status + const errorMsg = status === 429 + ? "You're sending messages too quickly. Wait a moment and try again." + : "Something went wrong submitting your responses. Please try again." + setMessages(prev => prev.slice(0, -1)) + toast.error(errorMsg) } finally { setLoading(false) } @@ -832,6 +848,7 @@ export default function AssistantChatPage() { role={msg.role} content={msg.content} suggestedFlows={msg.suggestedFlows} + imageUrls={msg.imageUrls} /> ))} {loading && (