Buckets:
| import "@/index.css" | |
| import * as Sentry from "@sentry/solid" | |
| import { I18nProvider } from "@opencode-ai/ui/context" | |
| import { DialogProvider } from "@opencode-ai/ui/context/dialog" | |
| import { FileComponentProvider } from "@opencode-ai/ui/context/file" | |
| import { MarkedProvider } from "@opencode-ai/ui/context/marked" | |
| import { File } from "@opencode-ai/ui/file" | |
| import { Font } from "@opencode-ai/ui/font" | |
| import { Splash } from "@opencode-ai/ui/logo" | |
| import { ThemeProvider } from "@opencode-ai/ui/theme/context" | |
| import { MetaProvider } from "@solidjs/meta" | |
| import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router" | |
| import { QueryClient, QueryClientProvider } from "@tanstack/solid-query" | |
| import { Effect } from "effect" | |
| import { | |
| type Component, | |
| createEffect, | |
| createMemo, | |
| createResource, | |
| createSignal, | |
| ErrorBoundary, | |
| For, | |
| type JSX, | |
| lazy, | |
| onCleanup, | |
| type ParentProps, | |
| Show, | |
| } from "solid-js" | |
| import { Dynamic } from "solid-js/web" | |
| import { CommandProvider } from "@/context/command" | |
| import { CommentsProvider } from "@/context/comments" | |
| import { FileProvider } from "@/context/file" | |
| import { ServerSDKProvider } from "@/context/server-sdk" | |
| import { ServerSyncProvider } from "@/context/server-sync" | |
| import { GlobalProvider } from "@/context/global" | |
| import { HighlightsProvider } from "@/context/highlights" | |
| import { LanguageProvider, type Locale, useLanguage } from "@/context/language" | |
| import { LayoutProvider } from "@/context/layout" | |
| import { ModelsProvider } from "@/context/models" | |
| import { NotificationProvider } from "@/context/notification" | |
| import { PermissionProvider } from "@/context/permission" | |
| import { PromptProvider } from "@/context/prompt" | |
| import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server" | |
| import { SettingsProvider, useSettings } from "@/context/settings" | |
| import { TerminalProvider } from "@/context/terminal" | |
| import { TabsProvider, useTabs, type DraftTab } from "@/context/tabs" | |
| import { SDKProvider, useSDK } from "@/context/sdk" | |
| import { WslServersProvider } from "@/wsl/context" | |
| import DirectoryLayout, { DirectoryDataProvider } from "@/pages/directory-layout" | |
| import Layout from "@/pages/layout" | |
| import { ErrorPage } from "./pages/error" | |
| import { useCheckServerHealth } from "./utils/server-health" | |
| const HomeRoute = lazy(() => import("@/pages/home")) | |
| const Session = lazy(() => import("@/pages/session")) | |
| const NewSession = lazy(() => import("@/pages/new-session")) | |
| const SessionRoute = Object.assign( | |
| () => { | |
| const settings = useSettings() | |
| const params = useParams() | |
| const [search] = useSearchParams<{ draftId?: string; prompt?: string }>() | |
| const sdk = useSDK() | |
| const server = useServer() | |
| const tabs = useTabs() | |
| // When the new layout is enabled, the legacy new-session route (/:dir/session with no id) | |
| // is replaced by a draft at /new-session?draftId=… | |
| createEffect(() => { | |
| if (!settings.general.newLayoutDesigns()) return | |
| if (params.id || search.draftId) return | |
| if (!tabs.ready() || !sdk.directory) return | |
| tabs.newDraft({ server: server.key, directory: sdk.directory }, search.prompt) | |
| }) | |
| return ( | |
| <SessionProviders> | |
| <Session /> | |
| </SessionProviders> | |
| ) | |
| }, | |
| { preload: Session.preload }, | |
| ) | |
| function DraftRoute() { | |
| const [search] = useSearchParams<{ draftId?: string }>() | |
| const tabs = useTabs() | |
| return ( | |
| <Show when={tabs.ready()}> | |
| <Show when={search.draftId} keyed fallback={<Navigate href="/" />}> | |
| {(draftID) => <ResolvedDraftRoute draftID={draftID} />} | |
| </Show> | |
| </Show> | |
| ) | |
| } | |
| function ResolvedDraftRoute(props: { draftID: string }) { | |
| const server = useServer() | |
| const tabs = useTabs() | |
| const draft = createMemo(() => | |
| tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === props.draftID), | |
| ) | |
| createEffect(() => { | |
| const current = draft() | |
| if (current && current.server !== server.key) server.setActive(current.server) | |
| }) | |
| // Key on the directory so retargeting the draft's project re-instantiates the | |
| // SDK/data providers for the new directory while keeping the same draft id. | |
| const directory = () => draft()?.directory | |
| return ( | |
| <Show when={directory()} keyed> | |
| {(dir) => ( | |
| <SDKProvider directory={dir}> | |
| <DirectoryDataProvider directory={dir} draftID={props.draftID}> | |
| <DraftProviders> | |
| <NewSession /> | |
| </DraftProviders> | |
| </DirectoryDataProvider> | |
| </SDKProvider> | |
| )} | |
| </Show> | |
| ) | |
| } | |
| function UiI18nBridge(props: ParentProps) { | |
| const language = useLanguage() | |
| return <I18nProvider value={{ locale: language.intl, t: language.t }}>{props.children}</I18nProvider> | |
| } | |
| declare global { | |
| interface Window { | |
| __OPENCODE__?: { | |
| deepLinks?: string[] | |
| } | |
| api?: { | |
| setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void> | |
| exportDebugLogs?: () => Promise<string> | |
| } | |
| } | |
| } | |
| function QueryProvider(props: ParentProps) { | |
| const client = new QueryClient({ | |
| defaultOptions: { | |
| queries: { | |
| refetchOnReconnect: false, | |
| refetchOnMount: false, | |
| refetchOnWindowFocus: false, | |
| }, | |
| }, | |
| }) | |
| return <QueryClientProvider client={client}>{props.children}</QueryClientProvider> | |
| } | |
| function BodyDesignClass() { | |
| const settings = useSettings() | |
| createEffect(() => { | |
| if (typeof document === "undefined") return | |
| const enabled = settings.general.newLayoutDesigns() | |
| document.body.classList.toggle("text-12-regular", !enabled) | |
| document.body.classList.toggle("font-(family-name:--font-family-text)", enabled) | |
| document.body.classList.toggle("text-[13px]", enabled) | |
| document.body.classList.toggle("font-[440]", enabled) | |
| }) | |
| return null | |
| } | |
| function AppShellProviders(props: ParentProps) { | |
| return ( | |
| <SettingsProvider> | |
| <BodyDesignClass /> | |
| <PermissionProvider> | |
| <LayoutProvider> | |
| <NotificationProvider> | |
| <ModelsProvider> | |
| <CommandProvider> | |
| <HighlightsProvider> | |
| <Layout>{props.children}</Layout> | |
| </HighlightsProvider> | |
| </CommandProvider> | |
| </ModelsProvider> | |
| </NotificationProvider> | |
| </LayoutProvider> | |
| </PermissionProvider> | |
| </SettingsProvider> | |
| ) | |
| } | |
| function SessionProviders(props: ParentProps) { | |
| return ( | |
| <TerminalProvider> | |
| <FileProvider> | |
| <PromptProvider> | |
| <CommentsProvider>{props.children}</CommentsProvider> | |
| </PromptProvider> | |
| </FileProvider> | |
| </TerminalProvider> | |
| ) | |
| } | |
| // The draft page only renders the prompt composer, so it drops TerminalProvider. | |
| // FileProvider and CommentsProvider stay because PromptInput uses file search and comment context. | |
| function DraftProviders(props: ParentProps) { | |
| return ( | |
| <FileProvider> | |
| <PromptProvider> | |
| <CommentsProvider>{props.children}</CommentsProvider> | |
| </PromptProvider> | |
| </FileProvider> | |
| ) | |
| } | |
| function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) { | |
| return ( | |
| <AppShellProviders> | |
| {/*<Suspense fallback={<Loading />}>*/} | |
| {props.appChildren} | |
| {props.children} | |
| {/*</Suspense>*/} | |
| </AppShellProviders> | |
| ) | |
| } | |
| export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) { | |
| return ( | |
| <MetaProvider> | |
| <Font /> | |
| <ThemeProvider | |
| onThemeApplied={(_, mode) => { | |
| void window.api?.setTitlebar?.({ mode }) | |
| }} | |
| > | |
| <LanguageProvider locale={props.locale}> | |
| <UiI18nBridge> | |
| <ErrorBoundary | |
| fallback={(error) => { | |
| Sentry.captureException(error) | |
| return <ErrorPage error={error} /> | |
| }} | |
| > | |
| <QueryProvider> | |
| <WslServersProvider> | |
| <DialogProvider> | |
| <MarkedProvider> | |
| <FileComponentProvider component={File}>{props.children}</FileComponentProvider> | |
| </MarkedProvider> | |
| </DialogProvider> | |
| </WslServersProvider> | |
| </QueryProvider> | |
| </ErrorBoundary> | |
| </UiI18nBridge> | |
| </LanguageProvider> | |
| </ThemeProvider> | |
| </MetaProvider> | |
| ) | |
| } | |
| function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) { | |
| const server = useServer() | |
| const checkServerHealth = useCheckServerHealth() | |
| const [checkMode, setCheckMode] = createSignal<"blocking" | "background">("blocking") | |
| // performs repeated health check with a grace period for | |
| // non-http connections, otherwise fails instantly | |
| const [startupHealthCheck, healthCheckActions] = createResource(() => | |
| props.disableHealthCheck | |
| ? true | |
| : Effect.gen(function* () { | |
| if (!server.current) return true | |
| const { http, type } = server.current | |
| while (true) { | |
| const res = yield* Effect.promise(() => checkServerHealth(http)) | |
| if (res.healthy) return true | |
| if (checkMode() === "background" || type === "http") return false | |
| } | |
| }).pipe( | |
| Effect.timeoutOrElse({ duration: "10 seconds", orElse: () => Effect.succeed(false) }), | |
| Effect.ensuring(Effect.sync(() => setCheckMode("background"))), | |
| Effect.runPromise, | |
| ), | |
| ) | |
| const checking = createMemo( | |
| () => checkMode() === "blocking" && ["unresolved", "pending"].includes(startupHealthCheck.state), | |
| ) | |
| return ( | |
| <Show | |
| when={!checking()} | |
| fallback={ | |
| <div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base"> | |
| <Splash class="w-16 h-20 opacity-50 animate-pulse" /> | |
| </div> | |
| } | |
| > | |
| <Show | |
| when={startupHealthCheck.latest} | |
| fallback={ | |
| <ConnectionError | |
| onRetry={() => { | |
| if (checkMode() === "background") void healthCheckActions.refetch() | |
| }} | |
| onServerSelected={(key) => { | |
| setCheckMode("blocking") | |
| server.setActive(key) | |
| void healthCheckActions.refetch() | |
| }} | |
| /> | |
| } | |
| > | |
| {props.children} | |
| </Show> | |
| </Show> | |
| ) | |
| } | |
| function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key: ServerConnection.Key) => void }) { | |
| const language = useLanguage() | |
| const server = useServer() | |
| const others = () => server.list.filter((s) => ServerConnection.key(s) !== server.key) | |
| const name = createMemo(() => server.name || server.key) | |
| const serverToken = "\u0000server\u0000" | |
| const unreachable = createMemo(() => language.t("app.server.unreachable", { server: serverToken }).split(serverToken)) | |
| const timer = setInterval(() => props.onRetry?.(), 1000) | |
| onCleanup(() => clearInterval(timer)) | |
| return ( | |
| <div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base gap-6 p-6"> | |
| <div class="flex flex-col items-center max-w-md text-center"> | |
| <Splash class="w-12 h-15 mb-4" /> | |
| <p class="text-14-regular text-text-base"> | |
| {unreachable()[0]} | |
| <span class="text-text-strong font-medium">{name()}</span> | |
| {unreachable()[1]} | |
| </p> | |
| <p class="mt-1 text-12-regular text-text-weak">{language.t("app.server.retrying")}</p> | |
| </div> | |
| <Show when={others().length > 0}> | |
| <div class="flex flex-col gap-2 w-full max-w-sm"> | |
| <span class="text-12-regular text-text-base text-center">{language.t("app.server.otherServers")}</span> | |
| <div class="flex flex-col gap-1 bg-surface-base rounded-lg p-2"> | |
| <For each={others()}> | |
| {(conn) => { | |
| const key = ServerConnection.key(conn) | |
| return ( | |
| <button | |
| type="button" | |
| class="flex items-center gap-3 w-full px-3 py-2 rounded-md hover:bg-surface-raised-base-hover transition-colors text-left" | |
| onClick={() => props.onServerSelected?.(key)} | |
| > | |
| <span class="text-14-regular text-text-strong truncate">{serverName(conn)}</span> | |
| </button> | |
| ) | |
| }} | |
| </For> | |
| </div> | |
| </div> | |
| </Show> | |
| </div> | |
| ) | |
| } | |
| function ServerKey(props: ParentProps) { | |
| const server = useServer() | |
| return ( | |
| <Show when={server.key} keyed> | |
| {props.children} | |
| </Show> | |
| ) | |
| } | |
| export function AppInterface(props: { | |
| children?: JSX.Element | |
| defaultServer: ServerConnection.Key | |
| canonicalLocalServer?: ServerConnection.Key | |
| servers?: Array<ServerConnection.Any> | |
| router?: Component<BaseRouterProps> | |
| disableHealthCheck?: boolean | |
| }) { | |
| return ( | |
| <ServerProvider | |
| defaultServer={props.defaultServer} | |
| canonicalLocalServer={props.canonicalLocalServer} | |
| servers={props.servers} | |
| > | |
| <GlobalProvider> | |
| <ConnectionGate disableHealthCheck={props.disableHealthCheck}> | |
| <Dynamic | |
| component={props.router ?? Router} | |
| root={(routerProps) => ( | |
| <TabsProvider> | |
| <ServerKey> | |
| <QueryProvider> | |
| <ServerSDKProvider> | |
| <ServerSyncProvider> | |
| <RouterRoot appChildren={props.children}>{routerProps.children}</RouterRoot> | |
| </ServerSyncProvider> | |
| </ServerSDKProvider> | |
| </QueryProvider> | |
| </ServerKey> | |
| </TabsProvider> | |
| )} | |
| > | |
| <Route path="/" component={HomeRoute} /> | |
| <Route path="/new-session" component={DraftRoute} /> | |
| <Route path="/:dir" component={DirectoryLayout}> | |
| <Route path="/" component={() => <Navigate href="session" />} /> | |
| <Route path="/session/:id?" component={SessionRoute} /> | |
| </Route> | |
| </Dynamic> | |
| </ConnectionGate> | |
| </GlobalProvider> | |
| </ServerProvider> | |
| ) | |
| } | |
Xet Storage Details
- Size:
- 14.1 kB
- Xet hash:
- 356c12ddcdf356af609fa01c9d161e8c7428bdbcaea589de04668e2f4738f966
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.