summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src/pages/error.tsx
blob: 352b9f3e8e962071da7ac6ef23187048213078bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { TextField } from "@opencode-ai/ui/text-field"
import { Logo } from "@opencode-ai/ui/logo"
import { Button } from "@opencode-ai/ui/button"
import { Component } from "solid-js"
import { usePlatform } from "@/context/platform"
import { Icon } from "@opencode-ai/ui/icon"

export type InitError = {
  name: string
  data: Record<string, unknown>
}

function isInitError(error: unknown): error is InitError {
  return (
    typeof error === "object" &&
    error !== null &&
    "name" in error &&
    "data" in error &&
    typeof (error as InitError).data === "object"
  )
}

function formatInitError(error: InitError): string {
  const data = error.data
  switch (error.name) {
    case "MCPFailed":
      return `MCP server "${data.name}" failed. Note, opencode does not support MCP authentication yet.`
    case "ProviderModelNotFoundError": {
      const { providerID, modelID, suggestions } = data as {
        providerID: string
        modelID: string
        suggestions?: string[]
      }
      return [
        `Model not found: ${providerID}/${modelID}`,
        ...(Array.isArray(suggestions) && suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
        `Check your config (opencode.json) provider/model names`,
      ].join("\n")
    }
    case "ProviderInitError":
      return `Failed to initialize provider "${data.providerID}". Check credentials and configuration.`
    case "ConfigJsonError":
      return `Config file at ${data.path} is not valid JSON(C)` + (data.message ? `: ${data.message}` : "")
    case "ConfigDirectoryTypoError":
      return `Directory "${data.dir}" in ${data.path} is not valid. Rename the directory to "${data.suggestion}" or remove it. This is a common typo.`
    case "ConfigFrontmatterError":
      return `Failed to parse frontmatter in ${data.path}:\n${data.message}`
    case "ConfigInvalidError": {
      const issues = Array.isArray(data.issues)
        ? data.issues.map(
            (issue: { message: string; path: string[] }) => "↳ " + issue.message + " " + issue.path.join("."),
          )
        : []
      return [`Config file at ${data.path} is invalid` + (data.message ? `: ${data.message}` : ""), ...issues].join(
        "\n",
      )
    }
    case "UnknownError":
      return String(data.message)
    default:
      return data.message ? String(data.message) : JSON.stringify(data, null, 2)
  }
}

function formatError(error: unknown): string {
  if (!error) return "Unknown error"
  if (isInitError(error)) return formatInitError(error)
  if (error instanceof Error) return `${error.name}: ${error.message}\n\n${error.stack}`
  if (typeof error === "string") return error
  return JSON.stringify(error, null, 2)
}

interface ErrorPageProps {
  error: unknown
}

export const ErrorPage: Component<ErrorPageProps> = (props) => {
  const platform = usePlatform()
  return (
    <div class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center bg-background-base font-sans">
      <div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
        <Logo class="w-58.5 opacity-12 shrink-0" />
        <div class="flex flex-col items-center gap-2 text-center">
          <h1 class="text-lg font-medium text-text-strong">Something went wrong</h1>
          <p class="text-sm text-text-weak">An error occurred while loading the application.</p>
        </div>
        <TextField
          value={formatError(props.error)}
          readOnly
          copyable
          multiline
          class="max-h-96 w-full font-mono text-xs no-scrollbar whitespace-pre"
          label="Error Details"
          hideLabel
        />
        <Button size="large" onClick={platform.restart}>
          Restart
        </Button>
        <div class="flex items-center justify-center gap-1">
          Please report this error to the OpenCode team
          <button
            type="button"
            class="flex items-center text-text-interactive-base gap-1"
            onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
          >
            <div>on Discord</div>
            <Icon name="discord" class="text-text-interactive-base" />
          </button>
        </div>
      </div>
    </div>
  )
}