mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-15 04:27:20 +03:00
feat(desktop): show migration progress (#42495)
This commit is contained in:
committed by
GitHub
Unverified
parent
ae14e78ba6
commit
72d37cda81
@@ -579,6 +579,7 @@ export const dict = {
|
||||
|
||||
"toast.session.listFailed.title": "Failed to load sessions for {{project}}",
|
||||
"toast.project.reloadFailed.title": "Failed to reload {{project}}",
|
||||
"toast.migration.failed.title": "Data migration failed",
|
||||
|
||||
"toast.update.title": "Update available",
|
||||
"toast.update.description": "A new version of OpenCode ({{version}}) is now available to install.",
|
||||
|
||||
@@ -29,6 +29,7 @@ import { DesktopFirstLaunchOnboarding } from "./onboarding"
|
||||
import { resetZoom, setPinchZoomEnabled, webviewZoom, zoomIn, zoomOut } from "./webview-zoom"
|
||||
import { windowFullscreen } from "./window-fullscreen"
|
||||
import { availableStartupServer, readyWslConnections } from "./wsl/connections"
|
||||
import { MigrationStatus } from "./migration-status"
|
||||
import "./styles.css"
|
||||
import { Splash } from "@opencode-ai/ui/logo"
|
||||
import { useTheme } from "@opencode-ai/ui/theme/context"
|
||||
@@ -357,7 +358,7 @@ function DesktopRoot(props: { windowState: DesktopWindowState }) {
|
||||
)
|
||||
const onboarding = Promise.withResolvers<void>()
|
||||
|
||||
function Inner() {
|
||||
function DesktopEffects() {
|
||||
const cmd = useCommand()
|
||||
menuTrigger = (id) => cmd.trigger(id)
|
||||
|
||||
@@ -412,7 +413,10 @@ function DesktopRoot(props: { windowState: DesktopWindowState }) {
|
||||
onLoaded={onboarding.resolve}
|
||||
serverKey={key}
|
||||
/>
|
||||
<Inner />
|
||||
<DesktopEffects />
|
||||
<Show when={initializationData(sidecar)} keyed>
|
||||
{(server) => <MigrationStatus server={server} />}
|
||||
</Show>
|
||||
</AppInterface>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { OpenCode, type MigrationV1StatusOutput } from "@opencode-ai/client/promise"
|
||||
import { useLanguage } from "@opencode-ai/app"
|
||||
import { LoaderV2 } from "@opencode-ai/ui/v2/loader-v2"
|
||||
import { showToastV2, toasterV2, ToastV2 } from "@opencode-ai/ui/v2/toast-v2"
|
||||
import { createRoot, createSignal, onCleanup, onMount } from "solid-js"
|
||||
import type { ServerReadyData } from "../preload/types"
|
||||
|
||||
type Progress = Extract<MigrationV1StatusOutput, { status: "running" }>["progress"]
|
||||
|
||||
export function MigrationStatus(props: { server: ServerReadyData }) {
|
||||
const language = useLanguage()
|
||||
const [progress, setProgress] = createSignal<Progress>()
|
||||
const abort = new AbortController()
|
||||
let toastID: number | undefined
|
||||
let disposeToast: (() => void) | undefined
|
||||
|
||||
const hide = () => {
|
||||
if (toastID !== undefined) toasterV2.dismiss(toastID)
|
||||
toastID = undefined
|
||||
disposeToast?.()
|
||||
disposeToast = undefined
|
||||
}
|
||||
|
||||
const show = () => {
|
||||
if (toastID !== undefined) return
|
||||
toastID = toasterV2.show(
|
||||
({ toastId }) =>
|
||||
createRoot((dispose) => {
|
||||
disposeToast?.()
|
||||
disposeToast = dispose
|
||||
return (
|
||||
<ToastV2 toastId={toastId}>
|
||||
<div data-slot="toast-v2-header" class="col-span-full">
|
||||
<ToastV2.Icon>
|
||||
<LoaderV2 />
|
||||
</ToastV2.Icon>
|
||||
<ToastV2.Content>
|
||||
<ToastV2.Title dir="auto">{format(progress())}</ToastV2.Title>
|
||||
</ToastV2.Content>
|
||||
</div>
|
||||
</ToastV2>
|
||||
)
|
||||
}),
|
||||
{ persistent: true },
|
||||
)
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await wait(1_000, abort.signal)
|
||||
if (abort.signal.aborted) return
|
||||
|
||||
const client = OpenCode.make({
|
||||
baseUrl: props.server.url,
|
||||
headers: props.server.password
|
||||
? { Authorization: `Basic ${btoa(`${props.server.username ?? "opencode"}:${props.server.password}`)}` }
|
||||
: undefined,
|
||||
})
|
||||
|
||||
void (async () => {
|
||||
while (true) {
|
||||
const status = await client.migration.v1.status({ signal: abort.signal })
|
||||
setProgress(status.status === "running" ? status.progress : undefined)
|
||||
if (status.status === "running") show()
|
||||
else hide()
|
||||
if (status.status === "completed") return
|
||||
if (status.status === "error") throw new Error(status.error)
|
||||
await wait(1_000, abort.signal)
|
||||
}
|
||||
})().catch((error) => {
|
||||
if (abort.signal.aborted) return
|
||||
hide()
|
||||
showToastV2({
|
||||
variant: "error",
|
||||
title: language.t("toast.migration.failed.title"),
|
||||
description: error instanceof Error ? error.message : String(error),
|
||||
duration: 10_000,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
abort.abort()
|
||||
hide()
|
||||
})
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function format(progress: Progress | undefined) {
|
||||
if (!progress) return ""
|
||||
if (progress.numerator === undefined) return progress.label
|
||||
if (progress.denominator === undefined) return `${progress.label} ${progress.numerator}`
|
||||
return `${progress.label} ${progress.numerator}/${progress.denominator}`
|
||||
}
|
||||
|
||||
function wait(delay: number, signal: AbortSignal) {
|
||||
return new Promise<void>((resolve) => {
|
||||
const timer = setTimeout(done, delay)
|
||||
signal.addEventListener("abort", done, { once: true })
|
||||
function done() {
|
||||
clearTimeout(timer)
|
||||
signal.removeEventListener("abort", done)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user