Files
opencode/packages/opencode/src/cli/cmd/tui/routes/home.tsx
Dax Raad c1a3936b61 Merge remote-tracking branch 'origin/dev' into feat/auto-accept-permissions
# Conflicts:
#	packages/sdk/js/src/v2/gen/types.gen.ts
2026-03-20 10:20:26 -04:00

162 lines
5.1 KiB
TypeScript

import { Prompt, type PromptRef } from "@tui/component/prompt"
import { createEffect, createMemo, Match, on, onMount, Show, Switch } from "solid-js"
import { useTheme } from "@tui/context/theme"
import { useKeybind } from "@tui/context/keybind"
import { Logo } from "../component/logo"
import { Tips } from "../component/tips"
import { Locale } from "@/util/locale"
import { useSync } from "../context/sync"
import { Toast } from "../ui/toast"
import { useArgs } from "../context/args"
import { useDirectory } from "../context/directory"
import { useRouteData } from "@tui/context/route"
import { usePromptRef } from "../context/prompt"
import { Installation } from "@/installation"
import { useKV } from "../context/kv"
import { useCommandDialog } from "../component/dialog-command"
import { useLocal } from "../context/local"
// TODO: what is the best way to do this?
let once = false
export function Home() {
const sync = useSync()
const kv = useKV()
const { theme } = useTheme()
const route = useRouteData("home")
const promptRef = usePromptRef()
const command = useCommandDialog()
const mcp = createMemo(() => Object.keys(sync.data.mcp).length > 0)
const mcpError = createMemo(() => {
return Object.values(sync.data.mcp).some((x) => x.status === "failed")
})
const connectedMcpCount = createMemo(() => {
return Object.values(sync.data.mcp).filter((x) => x.status === "connected").length
})
const isFirstTimeUser = createMemo(() => sync.data.session.length === 0)
const tipsHidden = createMemo(() => kv.get("tips_hidden", false))
const showTips = createMemo(() => {
// Don't show tips for first-time users
if (isFirstTimeUser()) return false
return !tipsHidden()
})
command.register(() => [
{
title: tipsHidden() ? "Show tips" : "Hide tips",
value: "tips.toggle",
search: "toggle tips",
keybind: "tips_toggle",
category: "System",
onSelect: (dialog) => {
kv.set("tips_hidden", !tipsHidden())
dialog.clear()
},
},
])
const Hint = (
<Show when={connectedMcpCount() > 0}>
<box flexShrink={0} flexDirection="row" gap={1}>
<text fg={theme.text}>
<Switch>
<Match when={mcpError()}>
<span style={{ fg: theme.error }}></span> mcp errors{" "}
<span style={{ fg: theme.textMuted }}>ctrl+x s</span>
</Match>
<Match when={true}>
<span style={{ fg: theme.success }}></span>{" "}
{Locale.pluralize(connectedMcpCount(), "{} mcp server", "{} mcp servers")}
</Match>
</Switch>
</text>
</box>
</Show>
)
let prompt: PromptRef
const args = useArgs()
const local = useLocal()
onMount(() => {
if (once) return
if (route.initialPrompt) {
prompt.set(route.initialPrompt)
once = true
} else if (args.prompt) {
prompt.set({ input: args.prompt, parts: [] })
once = true
}
})
// Wait for sync and model store to be ready before auto-submitting --prompt
createEffect(
on(
() => sync.ready && local.model.ready,
(ready) => {
if (!ready) return
if (!args.prompt) return
if (prompt.current?.input !== args.prompt) return
prompt.submit()
},
),
)
const directory = useDirectory()
const keybind = useKeybind()
return (
<>
<box flexGrow={1} alignItems="center" paddingLeft={2} paddingRight={2}>
<box flexGrow={1} minHeight={0} />
<box height={4} minHeight={0} flexShrink={1} />
<box flexShrink={0}>
<Logo />
</box>
<box height={1} minHeight={0} flexShrink={1} />
<box width="100%" maxWidth={75} zIndex={1000} paddingTop={1} flexShrink={0}>
<Prompt
ref={(r) => {
prompt = r
promptRef.set(r)
}}
hint={Hint}
workspaceID={route.workspaceID}
/>
</box>
<box height={4} minHeight={0} width="100%" maxWidth={75} alignItems="center" paddingTop={3} flexShrink={1}>
<Show when={showTips()}>
<Tips />
</Show>
</box>
<box flexGrow={1} minHeight={0} />
<Toast />
</box>
<box paddingTop={1} paddingBottom={1} paddingLeft={2} paddingRight={2} flexDirection="row" flexShrink={0} gap={2}>
<text fg={theme.textMuted}>{directory()}</text>
<box gap={1} flexDirection="row" flexShrink={0}>
<Show when={mcp()}>
<text fg={theme.text}>
<Switch>
<Match when={mcpError()}>
<span style={{ fg: theme.error }}> </span>
</Match>
<Match when={true}>
<span style={{ fg: connectedMcpCount() > 0 ? theme.success : theme.textMuted }}> </span>
</Match>
</Switch>
{connectedMcpCount()} MCP
</text>
<text fg={theme.textMuted}>/status</text>
</Show>
</box>
<box flexGrow={1} />
<box flexShrink={0}>
<text fg={theme.textMuted}>{Installation.VERSION}</text>
</box>
</box>
</>
)
}