// @refresh reload import { ACCEPTED_FILE_EXTENSIONS, ACCEPTED_FILE_TYPES, AppBaseProviders, AppInterface, handleNotificationClick, loadLocaleDict, normalizeLocale, type Locale, type Platform, PlatformProvider, ServerConnection, useCommand, } from "@opencode-ai/app" import * as Sentry from "@sentry/solid" import type { AsyncStorage } from "@solid-primitives/storage" import { MemoryRouter } from "@solidjs/router" import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js" import { render } from "solid-js/web" import pkg from "../../package.json" import { initI18n, t } from "./i18n" import { webviewZoom } from "./webview-zoom" import "./styles.css" import { useTheme } from "@opencode-ai/ui/theme" const root = document.getElementById("root") if (import.meta.env.DEV && !(root instanceof HTMLElement)) { throw new Error(t("error.dev.rootNotFound")) } if (import.meta.env.VITE_SENTRY_DSN) { Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN, environment: import.meta.env.VITE_SENTRY_ENVIRONMENT ?? import.meta.env.MODE, release: import.meta.env.VITE_SENTRY_RELEASE ?? `desktop-electron@${pkg.version}`, initialScope: { tags: { platform: "desktop-electron", }, }, integrations: (integrations) => { return integrations.filter( (i) => i.name !== "Breadcrumbs" && !(import.meta.env.OPENCODE_CHANNEL === "prod" && i.name === "GlobalHandlers"), ) }, }) } void initI18n() const deepLinkEvent = "opencode:deep-link" const emitDeepLinks = (urls: string[]) => { if (urls.length === 0) return window.__OPENCODE__ ??= {} const pending = window.__OPENCODE__.deepLinks ?? [] window.__OPENCODE__.deepLinks = [...pending, ...urls] window.dispatchEvent(new CustomEvent(deepLinkEvent, { detail: { urls } })) } const listenForDeepLinks = () => { void window.api.consumeInitialDeepLinks().then((urls) => emitDeepLinks(urls)) return window.api.onDeepLink((urls) => emitDeepLinks(urls)) } const createPlatform = (): Platform => { const os = (() => { const ua = navigator.userAgent if (ua.includes("Mac")) return "macos" if (ua.includes("Windows")) return "windows" if (ua.includes("Linux")) return "linux" return undefined })() const isWslEnabled = async () => { if (os !== "windows") return false return window.api .getWslConfig() .then((config) => config.enabled) .catch(() => false) } const wslHome = async () => { if (!(await isWslEnabled())) return undefined return window.api.wslPath("~", "windows").catch(() => undefined) } const handleWslPicker = async (result: T | null): Promise => { if (!result || !(await isWslEnabled())) return result if (Array.isArray(result)) { return Promise.all(result.map((path) => window.api.wslPath(path, "linux").catch(() => path))) as any } return window.api.wslPath(result, "linux").catch(() => result) as any } const storage = (() => { const cache = new Map() const createStorage = (name: string) => { const api: AsyncStorage = { getItem: (key: string) => window.api.storeGet(name, key), setItem: (key: string, value: string) => window.api.storeSet(name, key, value), removeItem: (key: string) => window.api.storeDelete(name, key), clear: () => window.api.storeClear(name), key: async (index: number) => (await window.api.storeKeys(name))[index], getLength: () => window.api.storeLength(name), get length() { return api.getLength() }, } return api } return (name = "default.dat") => { const cached = cache.get(name) if (cached) return cached const api = createStorage(name) cache.set(name, api) return api } })() return { platform: "desktop", os, version: pkg.version, async openDirectoryPickerDialog(opts) { const defaultPath = await wslHome() const result = await window.api.openDirectoryPicker({ multiple: opts?.multiple ?? false, title: opts?.title ?? t("desktop.dialog.chooseFolder"), defaultPath, }) return await handleWslPicker(result) }, async openFilePickerDialog(opts) { const result = await window.api.openFilePicker({ multiple: opts?.multiple ?? false, title: opts?.title ?? t("desktop.dialog.chooseFile"), accept: opts?.accept ?? ACCEPTED_FILE_TYPES, extensions: opts?.extensions ?? ACCEPTED_FILE_EXTENSIONS, }) return handleWslPicker(result) }, async saveFilePickerDialog(opts) { const result = await window.api.saveFilePicker({ title: opts?.title ?? t("desktop.dialog.saveFile"), defaultPath: opts?.defaultPath, }) return handleWslPicker(result) }, openLink(url: string) { window.api.openLink(url) }, async openPath(path: string, app?: string) { if (os === "windows") { const resolvedApp = app ? await window.api.resolveAppPath(app).catch(() => null) : null const resolvedPath = await (async () => { if (await isWslEnabled()) { const converted = await window.api.wslPath(path, "windows").catch(() => null) if (converted) return converted } return path })() return window.api.openPath(resolvedPath, resolvedApp ?? undefined) } return window.api.openPath(path, app) }, back() { window.history.back() }, forward() { window.history.forward() }, storage, checkUpdate: async () => { const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false })) if (!config.updaterEnabled) return { updateAvailable: false } return window.api.checkUpdate() }, updateAndRestart: async () => { const config = await window.api.getWindowConfig().catch(() => ({ updaterEnabled: false })) if (!config.updaterEnabled) return await window.api.installUpdate() }, restart: async () => { await window.api.killSidecar().catch(() => undefined) window.api.relaunch() }, notify: async (title, description, href) => { const focused = await window.api.getWindowFocused().catch(() => document.hasFocus()) if (focused) return const notification = new Notification(title, { body: description ?? "", icon: "https://opencode.ai/favicon-96x96-v3.png", }) notification.onclick = () => { void window.api.showWindow() void window.api.setWindowFocus() handleNotificationClick(href) notification.close() } }, fetch: (input, init) => { if (input instanceof Request) return fetch(input) return fetch(input, init) }, getWslEnabled: () => isWslEnabled(), setWslEnabled: async (enabled) => { await window.api.setWslConfig({ enabled }) }, getDefaultServer: async () => { const url = await window.api.getDefaultServerUrl().catch(() => null) if (!url) return null return ServerConnection.Key.make(url) }, setDefaultServer: async (url: string | null) => { await window.api.setDefaultServerUrl(url) }, getDisplayBackend: async () => { return window.api.getDisplayBackend().catch(() => null) }, setDisplayBackend: async (backend) => { await window.api.setDisplayBackend(backend) }, parseMarkdown: (markdown: string) => window.api.parseMarkdownCommand(markdown), webviewZoom, checkAppExists: async (appName: string) => { return window.api.checkAppExists(appName) }, async readClipboardImage() { const image = await window.api.readClipboardImage().catch(() => null) if (!image) return null const blob = new Blob([image.buffer], { type: "image/png" }) return new File([blob], `pasted-image-${Date.now()}.png`, { type: "image/png", }) }, } } let menuTrigger = null as null | ((id: string) => void) window.api.onMenuCommand((id) => { menuTrigger?.(id) }) listenForDeepLinks() render(() => { const platform = createPlatform() const [windowConfig] = createResource(() => window.api.getWindowConfig().catch(() => ({ updaterEnabled: false }))) const loadLocale = async () => { const current = await platform.storage?.("opencode.global.dat").getItem("language") const legacy = current ? undefined : await platform.storage?.().getItem("language.v1") const raw = current ?? legacy if (!raw) return const locale = raw.match(/"locale"\s*:\s*"([^"]+)"/)?.[1] if (!locale) return const next = normalizeLocale(locale) if (next !== "en") await loadLocaleDict(next) return next satisfies Locale } const [windowCount] = createResource(() => window.api.getWindowCount()) // Fetch sidecar credentials (available immediately, before health check) const [sidecar] = createResource(() => window.api.awaitInitialization(() => undefined)) const [defaultServer] = createResource(() => platform.getDefaultServer?.().then((url) => { if (url) return ServerConnection.key({ type: "http", http: { url } }) }), ) const [locale] = createResource(loadLocale) const servers = () => { const data = sidecar() if (!data) return [] const server: ServerConnection.Sidecar = { displayName: "Local Server", type: "sidecar", variant: "base", http: { url: data.url, username: data.username ?? undefined, password: data.password ?? undefined, }, } return [server] as ServerConnection.Any[] } function handleClick(e: MouseEvent) { const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null if (link?.href) { e.preventDefault() platform.openLink(link.href) } } function Inner() { const cmd = useCommand() menuTrigger = (id) => cmd.trigger(id) const theme = useTheme() createEffect(() => { theme.themeId() theme.mode() const bg = getComputedStyle(document.documentElement).getPropertyValue("--background-base").trim() if (bg) { void window.api.setBackgroundColor(bg) } }) return null } onMount(() => { document.addEventListener("click", handleClick) onCleanup(() => { document.removeEventListener("click", handleClick) }) }) return ( {(_) => { return ( ) }} ) }, root!)