Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion web/src/components/chat/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AlertCircleIcon } from "lucide-react";
import { useMemo, type ReactNode } from "react";
import type { AgentMessage, PendingTool, ToolExecution, ToolResultContentPart } from "@/lib/pi-events";
import { contentText } from "@/lib/pi-events";
import { friendlyErrorMessage } from "@/lib/error-messages";
import { MessageShell } from "@/components/chat-ui/MessageShell";
import { ZeroLoader } from "@/components/chat-ui/ZeroLoader";
import { MessageView } from "./pi-transcript";
Expand Down Expand Up @@ -183,7 +184,7 @@ export function MessageList({
<MessageShell role="assistant">
<div className="flex items-center gap-3 text-destructive text-sm">
<AlertCircleIcon className="size-4 shrink-0" />
<span>Something went wrong.</span>
<span>{friendlyErrorMessage(error.message)}</span>
</div>
</MessageShell>
)}
Expand Down
5 changes: 4 additions & 1 deletion web/src/components/chat/pi-transcript/MessageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
UserMessage,
} from "@/lib/pi-events";
import { contentText } from "@/lib/pi-events";
import { friendlyErrorMessage } from "@/lib/error-messages";
import { MessageShell } from "@/components/chat-ui/MessageShell";
import { Markdown } from "@/components/chat-ui/Markdown";
import { ToolCallCard } from "./ToolCallCard";
Expand Down Expand Up @@ -147,7 +148,9 @@ function AssistantMessageView({
{message.stopReason === "aborted" ? (
<div className="text-sm text-muted-foreground italic">Agent was interrupted.</div>
) : (
<div className="text-sm text-destructive">{message.errorMessage}</div>
<div className="text-sm text-destructive">
{friendlyErrorMessage(message.errorMessage)}
</div>
)}
</MessageShell>
)}
Expand Down
50 changes: 50 additions & 0 deletions web/src/lib/error-messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Chat errors arrive from the server as raw technical strings (an exception's
* `err.message`: stack-y SDK errors, HTTP status text, provider payloads). We
* don't want to surface those verbatim — they're noise to the user and can leak
* internals. This maps a raw error to a short, generic, user-facing line.
*
* A few broad, genuinely-actionable categories get a tailored (but still
* non-technical) message; everything else falls back to a single generic line.
*/
const GENERIC = "Something went wrong. Please try again.";

const CATEGORIES: Array<{ match: RegExp; message: string }> = [
{
// Rate limits / provider overload (429, "overloaded", "capacity").
match: /\b(429|rate.?limit|overloaded|too many requests|capacity)\b/i,
message: "The service is busy right now. Please try again in a moment.",
},
{
// Network / connectivity failures.
match: /\b(network|fetch failed|econnrefused|enotfound|econnreset|socket|offline|dns)\b/i,
message: "Connection problem. Please check your network and try again.",
},
{
// Request took too long.
match: /\b(timed?.?out|timeout|etimedout|deadline)\b/i,
message: "The request timed out. Please try again.",
},
{
// Conversation exceeded the model's context window.
match: /\b(context length|context window|too many tokens|maximum.*tokens|token limit)\b/i,
message: "This conversation is too long for the model. Try starting a new chat.",
},
{
// Auth / permission issues.
match: /\b(401|403|unauthorized|forbidden|invalid api key|authentication)\b/i,
message: "There was an authentication problem. Please reconnect and try again.",
},
];

/**
* Turn a raw error string into a generic, user-facing message. Returns the
* generic fallback when the input is empty or unrecognized.
*/
export function friendlyErrorMessage(raw?: string | null): string {
if (!raw) return GENERIC;
for (const { match, message } of CATEGORIES) {
if (match.test(raw)) return message;
}
return GENERIC;
}
21 changes: 14 additions & 7 deletions web/src/pages/CanvasPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,20 @@ export function CanvasPage() {
onPointerMove={onCanvasPointerMove}
onWheel={onWheel}
>
{/* Background dot grid — pans and zooms with the view. */}
<defs>
<pattern
id="canvas-dots"
width={24 * view.scale}
height={24 * view.scale}
patternUnits="userSpaceOnUse"
patternTransform={`translate(${view.tx} ${view.ty})`}
>
<circle cx={1} cy={1} r={1} fill="currentColor" className="text-border" />
</pattern>
</defs>
<rect x={0} y={0} width="100%" height="100%" fill="url(#canvas-dots)" pointerEvents="none" />

<g transform={`translate(${view.tx} ${view.ty}) scale(${view.scale})`}>
{list.map((s) => (
<ShapeView
Expand Down Expand Up @@ -880,13 +894,6 @@ export function CanvasPage() {
/>
)}

{list.length === 0 && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<p className="text-sm text-muted-foreground">
Pick a tool and click to start — or ask the agent to draw here.
</p>
</div>
)}
</div>

<AlertDialog open={confirmClearOpen} onOpenChange={setConfirmClearOpen}>
Expand Down
Loading