|
|
@@ -1,12 +1,57 @@
|
|
|
import React, { useState, useCallback, memo } from "react"
|
|
|
import { useTranslation } from "react-i18next"
|
|
|
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
|
|
-import { MessageCircleWarning } from "lucide-react"
|
|
|
+import { BookOpenText, MessageCircleWarning } from "lucide-react"
|
|
|
import { useCopyToClipboard } from "@src/utils/clipboard"
|
|
|
+import { vscode } from "@src/utils/vscode"
|
|
|
import CodeBlock from "../common/CodeBlock"
|
|
|
|
|
|
+/**
|
|
|
+ * Unified error display component for all error types in the chat.
|
|
|
+ * Provides consistent styling, icons, and optional documentation links across all errors.
|
|
|
+ *
|
|
|
+ * @param type - Error type determines icon and default title
|
|
|
+ * @param title - Optional custom title (overrides default for error type)
|
|
|
+ * @param message - Error message text (required)
|
|
|
+ * @param docsURL - Optional documentation link URL (shown as "Learn more" with book icon)
|
|
|
+ * @param showCopyButton - Whether to show copy button for error message
|
|
|
+ * @param expandable - Whether error content can be expanded/collapsed
|
|
|
+ * @param defaultExpanded - Whether expandable content starts expanded
|
|
|
+ * @param additionalContent - Optional React nodes to render after message
|
|
|
+ * @param headerClassName - Custom CSS classes for header section
|
|
|
+ * @param messageClassName - Custom CSS classes for message section
|
|
|
+ *
|
|
|
+ * @example
|
|
|
+ * // Simple error
|
|
|
+ * <ErrorRow type="error" message="File not found" />
|
|
|
+ *
|
|
|
+ * @example
|
|
|
+ * // Error with documentation link
|
|
|
+ * <ErrorRow
|
|
|
+ * type="api_failure"
|
|
|
+ * message="API key missing"
|
|
|
+ * docsURL="https://docs.example.com/api-setup"
|
|
|
+ * />
|
|
|
+ *
|
|
|
+ * @example
|
|
|
+ * // Expandable error with code
|
|
|
+ * <ErrorRow
|
|
|
+ * type="diff_error"
|
|
|
+ * message="Patch failed to apply"
|
|
|
+ * expandable={true}
|
|
|
+ * defaultExpanded={false}
|
|
|
+ * additionalContent={<pre>{errorDetails}</pre>}
|
|
|
+ * />
|
|
|
+ */
|
|
|
export interface ErrorRowProps {
|
|
|
- type: "error" | "mistake_limit" | "api_failure" | "diff_error" | "streaming_failed" | "cancelled"
|
|
|
+ type:
|
|
|
+ | "error"
|
|
|
+ | "mistake_limit"
|
|
|
+ | "api_failure"
|
|
|
+ | "diff_error"
|
|
|
+ | "streaming_failed"
|
|
|
+ | "cancelled"
|
|
|
+ | "api_req_retry_delayed"
|
|
|
title?: string
|
|
|
message: string
|
|
|
showCopyButton?: boolean
|
|
|
@@ -15,6 +60,8 @@ export interface ErrorRowProps {
|
|
|
additionalContent?: React.ReactNode
|
|
|
headerClassName?: string
|
|
|
messageClassName?: string
|
|
|
+ code?: number
|
|
|
+ docsURL?: string // NEW: Optional documentation link
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -31,6 +78,8 @@ export const ErrorRow = memo(
|
|
|
additionalContent,
|
|
|
headerClassName,
|
|
|
messageClassName,
|
|
|
+ docsURL,
|
|
|
+ code,
|
|
|
}: ErrorRowProps) => {
|
|
|
const { t } = useTranslation()
|
|
|
const [isExpanded, setIsExpanded] = useState(defaultExpanded)
|
|
|
@@ -48,6 +97,8 @@ export const ErrorRow = memo(
|
|
|
return t("chat:troubleMessage")
|
|
|
case "api_failure":
|
|
|
return t("chat:apiRequest.failed")
|
|
|
+ case "api_req_retry_delayed":
|
|
|
+ return t("chat:apiRequest.errorTitle", { code: code ? ` · ${code}` : "" })
|
|
|
case "streaming_failed":
|
|
|
return t("chat:apiRequest.streamingFailed")
|
|
|
case "cancelled":
|
|
|
@@ -84,17 +135,17 @@ export const ErrorRow = memo(
|
|
|
// For diff_error type with expandable content
|
|
|
if (type === "diff_error" && expandable) {
|
|
|
return (
|
|
|
- <div className="mt-0 overflow-hidden mb-2">
|
|
|
+ <div className="mt-0 overflow-hidden mb-2 pr-1 group">
|
|
|
<div
|
|
|
- className={`font-normal text-vscode-editor-foreground flex items-center justify-between cursor-pointer ${
|
|
|
- isExpanded ? "border-b border-vscode-editorGroup-border" : ""
|
|
|
- }`}
|
|
|
+ className="font-sm text-vscode-editor-foreground flex items-center justify-between cursor-pointer"
|
|
|
onClick={handleToggleExpand}>
|
|
|
- <div className="flex items-center gap-2 flex-grow">
|
|
|
- <MessageCircleWarning className="w-4 text-vscode-errorForeground" />
|
|
|
- <span className="font-bold">{errorTitle}</span>
|
|
|
+ <div className="flex items-center gap-2 flex-grow text-vscode-errorForeground">
|
|
|
+ <MessageCircleWarning className="w-4" />
|
|
|
+ <span className="text-vscode-errorForeground font-bold grow cursor-pointer">
|
|
|
+ {errorTitle}
|
|
|
+ </span>
|
|
|
</div>
|
|
|
- <div className="flex items-center">
|
|
|
+ <div className="flex items-center transition-opacity opacity-0 group-hover:opacity-100">
|
|
|
{showCopyButton && (
|
|
|
<VSCodeButton
|
|
|
appearance="icon"
|
|
|
@@ -107,7 +158,7 @@ export const ErrorRow = memo(
|
|
|
</div>
|
|
|
</div>
|
|
|
{isExpanded && (
|
|
|
- <div className="p-2 bg-vscode-editor-background border-t-0">
|
|
|
+ <div className="px-2 py-1 mt-2 bg-vscode-editor-background ml-6 rounded-lg">
|
|
|
<CodeBlock source={message} language="xml" />
|
|
|
</div>
|
|
|
)}
|
|
|
@@ -117,21 +168,36 @@ export const ErrorRow = memo(
|
|
|
|
|
|
// Standard error display
|
|
|
return (
|
|
|
- <>
|
|
|
+ <div className="group pr-2">
|
|
|
{errorTitle && (
|
|
|
- <div className={headerClassName || "flex items-center gap-2 break-words"}>
|
|
|
+ <div className={headerClassName || "flex items-center justify-between gap-2 break-words"}>
|
|
|
<MessageCircleWarning className="w-4 text-vscode-errorForeground" />
|
|
|
- <span className="text-vscode-errorForeground font-bold">{errorTitle}</span>
|
|
|
+ <span className="text-vscode-errorForeground font-bold grow cursor-default">{errorTitle}</span>
|
|
|
+ {docsURL && (
|
|
|
+ <a
|
|
|
+ href={docsURL}
|
|
|
+ className="text-sm flex items-center gap-1 transition-opacity opacity-0 group-hover:opacity-100"
|
|
|
+ onClick={(e) => {
|
|
|
+ e.preventDefault()
|
|
|
+ vscode.postMessage({ type: "openExternal", url: docsURL })
|
|
|
+ }}>
|
|
|
+ <BookOpenText className="size-3 mt-[3px]" />
|
|
|
+ {t("chat:apiRequest.errorMessage.docs")}
|
|
|
+ </a>
|
|
|
+ )}
|
|
|
</div>
|
|
|
)}
|
|
|
- <p
|
|
|
- className={
|
|
|
- messageClassName || "ml-6 my-0 whitespace-pre-wrap break-words text-vscode-errorForeground"
|
|
|
- }>
|
|
|
- {message}
|
|
|
- </p>
|
|
|
- {additionalContent}
|
|
|
- </>
|
|
|
+ <div className="pl-6 py-1">
|
|
|
+ <p
|
|
|
+ className={
|
|
|
+ messageClassName ||
|
|
|
+ "my-0 font-light whitespace-pre-wrap break-words text-vscode-errorForeground"
|
|
|
+ }>
|
|
|
+ {message}
|
|
|
+ </p>
|
|
|
+ {additionalContent}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
)
|
|
|
},
|
|
|
)
|