feat(desktop): Add Export Logs (#26262)
This commit is contained in:
parent
c64ac905e1
commit
bea3ca5b05
18 changed files with 508 additions and 21 deletions
|
|
@ -78,6 +78,7 @@ declare global {
|
|||
}
|
||||
api?: {
|
||||
setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void>
|
||||
exportDebugLogs?: () => Promise<string>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,23 @@ type OpenDirectoryPickerOptions = { title?: string; multiple?: boolean }
|
|||
type OpenFilePickerOptions = { title?: string; multiple?: boolean; accept?: string[]; extensions?: string[] }
|
||||
type SaveFilePickerOptions = { title?: string; defaultPath?: string }
|
||||
type UpdateInfo = { updateAvailable: boolean; version?: string }
|
||||
type PlatformName = "web" | "desktop"
|
||||
type DesktopOS = "macos" | "windows" | "linux"
|
||||
|
||||
export type FatalRendererErrorLog = {
|
||||
error: string
|
||||
url: string
|
||||
version?: string
|
||||
platform: PlatformName
|
||||
os?: DesktopOS
|
||||
}
|
||||
|
||||
export type Platform = {
|
||||
/** Platform discriminator */
|
||||
platform: "web" | "desktop"
|
||||
platform: PlatformName
|
||||
|
||||
/** Desktop OS (Tauri only) */
|
||||
os?: "macos" | "windows" | "linux"
|
||||
os?: DesktopOS
|
||||
|
||||
/** App version */
|
||||
version?: string
|
||||
|
|
@ -91,6 +101,12 @@ export type Platform = {
|
|||
|
||||
/** Read image from clipboard (desktop only) */
|
||||
readClipboardImage?(): Promise<File | null>
|
||||
|
||||
/** Export collected diagnostic logs (desktop only) */
|
||||
exportDebugLogs?(): Promise<string>
|
||||
|
||||
/** Record a fatal renderer error in platform logs (desktop only) */
|
||||
recordFatalRendererError?(error: FatalRendererErrorLog): Promise<void>
|
||||
}
|
||||
|
||||
export type DisplayBackend = "auto" | "wayland"
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ export const DESKTOP_MENU: DesktopMenu[] = [
|
|||
{ type: "item", label: "Settings", command: "settings.open", accelerator: { macos: "Cmd+," } },
|
||||
{ type: "item", label: "Reload Webview", action: "view.reload" },
|
||||
{ type: "item", label: "Restart", action: "app.relaunch" },
|
||||
{ type: "item", label: "Export Logs...", command: "logs.export" },
|
||||
{ type: "separator" },
|
||||
{ type: "item", role: "hide" },
|
||||
{ type: "item", role: "hideOthers" },
|
||||
|
|
@ -201,6 +202,7 @@ export const DESKTOP_MENU: DesktopMenu[] = [
|
|||
items: [
|
||||
{ type: "item", label: "OpenCode Documentation", href: "https://opencode.ai/docs" },
|
||||
{ type: "item", label: "Support Forum", href: "https://discord.com/invite/opencode" },
|
||||
{ type: "item", label: "Export Logs...", command: "logs.export" },
|
||||
{ type: "separator" },
|
||||
{
|
||||
type: "item",
|
||||
|
|
|
|||
|
|
@ -470,6 +470,7 @@ export const dict = {
|
|||
"error.page.action.restart": "Restart",
|
||||
"error.page.action.report": "Report Error",
|
||||
"error.page.action.reported": "Error Reported",
|
||||
"error.page.action.exportLogs": "Export Logs",
|
||||
"error.page.action.checking": "Checking...",
|
||||
"error.page.action.checkUpdates": "Check for updates",
|
||||
"error.page.action.updateTo": "Update to {{version}}",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ export { AppBaseProviders, AppInterface } from "./app"
|
|||
export { ACCEPTED_FILE_EXTENSIONS, ACCEPTED_FILE_TYPES, filePickerFilters } from "./constants/file-picker"
|
||||
export { useCommand } from "./context/command"
|
||||
export { loadLocaleDict, normalizeLocale, type Locale } from "./context/language"
|
||||
export { type DisplayBackend, type Platform, PlatformProvider } from "./context/platform"
|
||||
export { type DisplayBackend, type FatalRendererErrorLog, type Platform, PlatformProvider } from "./context/platform"
|
||||
export { ServerConnection } from "./context/server"
|
||||
export { handleNotificationClick } from "./utils/notification-click"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { TextField } from "@opencode-ai/ui/text-field"
|
|||
import * as Sentry from "@sentry/solid"
|
||||
import { Logo } from "@opencode-ai/ui/logo"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { Component, createSignal, Show } from "solid-js"
|
||||
import { Component, createSignal, onMount, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { useLanguage } from "@/context/language"
|
||||
|
|
@ -221,12 +221,30 @@ interface ErrorPageProps {
|
|||
export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
||||
const platform = usePlatform()
|
||||
const language = useLanguage()
|
||||
const formattedError = () => formatError(props.error, language.t)
|
||||
let recordedFatalError: Promise<void> | undefined
|
||||
const [store, setStore] = createStore({
|
||||
checking: false,
|
||||
version: undefined as string | undefined,
|
||||
actionError: undefined as string | undefined,
|
||||
})
|
||||
|
||||
function ensureFatalErrorRecorded() {
|
||||
recordedFatalError ??=
|
||||
platform.recordFatalRendererError?.({
|
||||
error: formattedError(),
|
||||
url: location.href,
|
||||
version: platform.version,
|
||||
platform: platform.platform,
|
||||
os: platform.os,
|
||||
}) ?? Promise.resolve()
|
||||
return recordedFatalError
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
void ensureFatalErrorRecorded().catch(() => undefined)
|
||||
})
|
||||
|
||||
async function checkForUpdates() {
|
||||
if (!platform.checkUpdate) return
|
||||
setStore("checking", true)
|
||||
|
|
@ -254,6 +272,17 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
|||
})
|
||||
}
|
||||
|
||||
async function exportDebugLogs() {
|
||||
const exportLogs = platform.exportDebugLogs
|
||||
if (!exportLogs) return
|
||||
await ensureFatalErrorRecorded()
|
||||
.then(() => exportLogs())
|
||||
.then(() => setStore("actionError", undefined))
|
||||
.catch((err) => {
|
||||
setStore("actionError", formatError(err, language.t))
|
||||
})
|
||||
}
|
||||
|
||||
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">
|
||||
|
|
@ -263,7 +292,7 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
|||
<p class="text-sm text-text-weak">{language.t("error.page.description")}</p>
|
||||
</div>
|
||||
<TextField
|
||||
value={formatError(props.error, language.t)}
|
||||
value={formattedError()}
|
||||
readOnly
|
||||
copyable
|
||||
multiline
|
||||
|
|
@ -275,6 +304,11 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
|
|||
<Button size="large" onClick={platform.restart}>
|
||||
{language.t("error.page.action.restart")}
|
||||
</Button>
|
||||
<Show when={platform.platform === "desktop" && platform.exportDebugLogs}>
|
||||
<Button size="large" variant="ghost" onClick={exportDebugLogs}>
|
||||
{language.t("error.page.action.exportLogs")}
|
||||
</Button>
|
||||
</Show>
|
||||
<Show when={Sentry.isEnabled}>
|
||||
{(_) => {
|
||||
const [reported, setReported] = createSignal(false)
|
||||
|
|
|
|||
|
|
@ -1081,6 +1081,18 @@ export default function Layout(props: ParentProps) {
|
|||
keybind: "mod+comma",
|
||||
onSelect: () => openSettings(),
|
||||
},
|
||||
...(platform.platform === "desktop" && platform.exportDebugLogs
|
||||
? [
|
||||
{
|
||||
id: "logs.export",
|
||||
title: "Export logs",
|
||||
category: language.t("command.category.settings"),
|
||||
onSelect: () => {
|
||||
void platform.exportDebugLogs?.()
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: "session.previous",
|
||||
title: language.t("command.session.previous"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue