codev / desktop /src /components /layout /StartupErrorView.tsx
chenbhao's picture
feat: desktop
1f21206
Raw
History Blame
3.63 kB
import { Copy, RefreshCw } from 'lucide-react'
import { useMemo, useState } from 'react'
import { useTranslation } from '../../i18n'
import { Button } from '../shared/Button'
import { DoctorPanel } from '../doctor/DoctorPanel'
import { copyTextToClipboard } from '../chat/clipboard'
const LOG_MARKER = '\n\nRecent server logs:\n'
export function splitStartupError(error: string) {
const markerIndex = error.indexOf(LOG_MARKER)
if (markerIndex === -1) {
return {
message: error,
logs: '',
diagnostics: error,
}
}
const message = error.slice(0, markerIndex).trim()
const logs = error.slice(markerIndex + LOG_MARKER.length).trim()
return {
message,
logs,
diagnostics: `${message}\n\nRecent server logs:\n${logs}`,
}
}
type StartupErrorViewProps = {
error: string
}
export function StartupErrorView({ error }: StartupErrorViewProps) {
const t = useTranslation()
const { message, logs, diagnostics } = useMemo(() => splitStartupError(error), [error])
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
const ok = await copyTextToClipboard(diagnostics)
if (!ok) return
setCopied(true)
window.setTimeout(() => setCopied(false), 1600)
}
return (
<div className="h-screen flex items-center justify-center bg-[var(--color-surface)] px-6">
<section className="w-full max-w-3xl rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-container-low)] p-6 shadow-[var(--shadow-md)]">
<div className="flex flex-col gap-4">
<div>
<h1 className="text-lg font-semibold text-[var(--color-text-primary)]">
{t('app.serverFailed')}
</h1>
<p className="mt-2 text-sm text-[var(--color-text-secondary)]">
{t('app.serverFailedHint')}
</p>
</div>
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<div className="text-xs font-medium uppercase text-[var(--color-text-tertiary)]">
{t('app.startupError')}
</div>
<pre className="mt-2 max-h-28 overflow-auto whitespace-pre-wrap break-words font-mono text-xs text-[var(--color-error)]">
{message}
</pre>
</div>
{logs ? (
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<div className="text-xs font-medium uppercase text-[var(--color-text-tertiary)]">
{t('app.serverLogs')}
</div>
<pre className="mt-2 max-h-64 overflow-auto whitespace-pre-wrap break-words font-mono text-xs leading-relaxed text-[var(--color-text-secondary)]">
{logs}
</pre>
</div>
) : null}
<div className="flex flex-wrap items-center gap-2">
<Button
type="button"
variant="secondary"
size="sm"
icon={<Copy className="h-4 w-4" aria-hidden="true" />}
onClick={handleCopy}
>
{copied ? t('app.copiedDiagnostics') : t('app.copyDiagnostics')}
</Button>
<Button
type="button"
variant="ghost"
size="sm"
icon={<RefreshCw className="h-4 w-4" aria-hidden="true" />}
onClick={() => window.location.reload()}
>
{t('common.retry')}
</Button>
</div>
<DoctorPanel compact />
</div>
</section>
</div>
)
}