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) => (
+
+
+
+ ))}
+
+ )}
+
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 && (