mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-23 22:34:53 +00:00
tips as plugin
This commit is contained in:
@@ -636,28 +636,12 @@ const home = (input: Cfg): TuiSlotPlugin => ({
|
||||
</box>
|
||||
)
|
||||
},
|
||||
home_tips(ctx, value) {
|
||||
if (!value.show_tips) return null
|
||||
home_bottom(ctx) {
|
||||
const skin = look(ctx.theme.current)
|
||||
const text = "extra content in the unified home bottom slot"
|
||||
|
||||
return (
|
||||
<box height={4} minHeight={0} width="100%" maxWidth={75} alignItems="center" paddingTop={3} flexShrink={1}>
|
||||
<text fg={skin.muted}>
|
||||
<span style={{ fg: skin.accent }}>{input.label}</span> replaces the built-in home tips slot
|
||||
</text>
|
||||
</box>
|
||||
)
|
||||
},
|
||||
home_below_tips(ctx, value) {
|
||||
const skin = look(ctx.theme.current)
|
||||
const text = value.first_time_user
|
||||
? "first-time user state"
|
||||
: value.tips_hidden
|
||||
? "tips are hidden"
|
||||
: "extra content below tips"
|
||||
|
||||
return (
|
||||
<box width="100%" maxWidth={75} alignItems="center" paddingTop={1} flexShrink={0}>
|
||||
<box width="100%" maxWidth={75} alignItems="center" paddingTop={1} flexShrink={0} gap={1}>
|
||||
<box
|
||||
border
|
||||
borderColor={skin.border}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createMemo, createSignal, For } from "solid-js"
|
||||
import { For } from "solid-js"
|
||||
import { DEFAULT_THEMES, useTheme } from "@tui/context/theme"
|
||||
|
||||
const themeCount = Object.keys(DEFAULT_THEMES).length
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { TuiPlugin } from "@opencode-ai/plugin/tui"
|
||||
import { createMemo, Show } from "solid-js"
|
||||
import { Tips } from "./tips-view"
|
||||
|
||||
function View(props: { show: boolean }) {
|
||||
return (
|
||||
<box height={4} minHeight={0} width="100%" maxWidth={75} alignItems="center" paddingTop={3} flexShrink={1}>
|
||||
<Show when={props.show}>
|
||||
<Tips />
|
||||
</Show>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
const tui: TuiPlugin = async (api) => {
|
||||
const hidden = createMemo(() => api.kv.get("tips_hidden", false))
|
||||
const first = createMemo(() => api.state.session.count() === 0)
|
||||
const show = createMemo(() => !first() && !hidden())
|
||||
|
||||
api.command.register(() => [
|
||||
{
|
||||
title: hidden() ? "Show tips" : "Hide tips",
|
||||
value: "tips.toggle",
|
||||
keybind: "tips_toggle",
|
||||
category: "System",
|
||||
hidden: api.route.current.name !== "home",
|
||||
onSelect() {
|
||||
api.kv.set("tips_hidden", !hidden())
|
||||
api.ui.dialog.clear()
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
api.slots.register({
|
||||
order: 100,
|
||||
slots: {
|
||||
home_bottom() {
|
||||
return <View show={show()} />
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
tui,
|
||||
}
|
||||
@@ -131,6 +131,9 @@ function stateApi(sync: ReturnType<typeof useSync>): TuiApi["state"] {
|
||||
return sync.data.provider
|
||||
},
|
||||
session: {
|
||||
count() {
|
||||
return sync.data.session.length
|
||||
},
|
||||
diff(sessionID) {
|
||||
return sync.data.session_diff[sessionID] ?? []
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as HomeTips from "../feature-plugins/home/tips"
|
||||
import * as SidebarTitle from "../feature-plugins/sidebar/title"
|
||||
import * as SidebarContext from "../feature-plugins/sidebar/context"
|
||||
import * as SidebarMcp from "../feature-plugins/sidebar/mcp"
|
||||
@@ -13,6 +14,10 @@ export type InternalTuiPlugin = {
|
||||
}
|
||||
|
||||
export const INTERNAL_TUI_PLUGINS: InternalTuiPlugin[] = [
|
||||
{
|
||||
name: "home-tips",
|
||||
module: HomeTips,
|
||||
},
|
||||
{
|
||||
name: "sidebar-title",
|
||||
module: SidebarTitle,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
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"
|
||||
@@ -12,8 +10,6 @@ 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"
|
||||
import { TuiPluginRuntime } from "../plugin"
|
||||
|
||||
@@ -22,11 +18,9 @@ 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")
|
||||
@@ -36,27 +30,6 @@ export function Home() {
|
||||
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",
|
||||
keybind: "tips_toggle",
|
||||
category: "System",
|
||||
onSelect: (dialog) => {
|
||||
kv.set("tips_hidden", !tipsHidden())
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const Hint = (
|
||||
<box flexShrink={0} flexDirection="row" gap={1}>
|
||||
<Show when={connectedMcpCount() > 0}>
|
||||
@@ -104,8 +77,6 @@ export function Home() {
|
||||
)
|
||||
const directory = useDirectory()
|
||||
|
||||
const keybind = useKeybind()
|
||||
|
||||
return (
|
||||
<>
|
||||
<box flexGrow={1} alignItems="center" paddingLeft={2} paddingRight={2}>
|
||||
@@ -127,25 +98,7 @@ export function Home() {
|
||||
workspaceID={route.workspaceID}
|
||||
/>
|
||||
</box>
|
||||
<TuiPluginRuntime.Slot
|
||||
name="home_tips"
|
||||
mode="replace"
|
||||
show_tips={showTips()}
|
||||
tips_hidden={tipsHidden()}
|
||||
first_time_user={isFirstTimeUser()}
|
||||
>
|
||||
<box height={4} minHeight={0} width="100%" maxWidth={75} alignItems="center" paddingTop={3} flexShrink={1}>
|
||||
<Show when={showTips()}>
|
||||
<Tips />
|
||||
</Show>
|
||||
</box>
|
||||
</TuiPluginRuntime.Slot>
|
||||
<TuiPluginRuntime.Slot
|
||||
name="home_below_tips"
|
||||
show_tips={showTips()}
|
||||
tips_hidden={tipsHidden()}
|
||||
first_time_user={isFirstTimeUser()}
|
||||
/>
|
||||
<TuiPluginRuntime.Slot name="home_bottom" />
|
||||
<box flexGrow={1} minHeight={0} />
|
||||
<Toast />
|
||||
</box>
|
||||
|
||||
@@ -81,20 +81,20 @@ test("disposes tracked event, route, and command hooks", async () => {
|
||||
expect(count.event_drop).toBe(0)
|
||||
expect(count.route_add).toBe(1)
|
||||
expect(count.route_drop).toBe(0)
|
||||
expect(count.command_add).toBe(1)
|
||||
expect(count.command_add).toBe(2)
|
||||
expect(count.command_drop).toBe(1)
|
||||
|
||||
await TuiPluginRuntime.dispose()
|
||||
|
||||
expect(count.event_drop).toBe(1)
|
||||
expect(count.route_drop).toBe(1)
|
||||
expect(count.command_drop).toBe(1)
|
||||
expect(count.command_drop).toBe(2)
|
||||
|
||||
await TuiPluginRuntime.dispose()
|
||||
|
||||
expect(count.event_drop).toBe(1)
|
||||
expect(count.route_drop).toBe(1)
|
||||
expect(count.command_drop).toBe(1)
|
||||
expect(count.command_drop).toBe(2)
|
||||
|
||||
const marker = await fs.readFile(tmp.extra.marker, "utf8")
|
||||
expect(marker).toContain("custom")
|
||||
@@ -231,7 +231,7 @@ export default {
|
||||
mark("two")
|
||||
},
|
||||
slots: {
|
||||
home_tips() {
|
||||
home_bottom() {
|
||||
return null
|
||||
},
|
||||
},
|
||||
|
||||
@@ -244,6 +244,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
|
||||
return opts.state?.provider ?? []
|
||||
},
|
||||
session: {
|
||||
count: opts.state?.session?.count ?? (() => 0),
|
||||
diff: opts.state?.session?.diff ?? (() => []),
|
||||
todo: opts.state?.session?.todo ?? (() => []),
|
||||
messages: opts.state?.session?.messages ?? (() => []),
|
||||
|
||||
@@ -212,6 +212,7 @@ export type TuiState = {
|
||||
readonly config: SdkConfig
|
||||
readonly provider: ReadonlyArray<Provider>
|
||||
session: {
|
||||
count: () => number
|
||||
diff: (sessionID: string) => ReadonlyArray<TuiSidebarFileItem>
|
||||
todo: (sessionID: string) => ReadonlyArray<TuiSidebarTodoItem>
|
||||
messages: (sessionID: string) => ReadonlyArray<Message>
|
||||
@@ -285,16 +286,7 @@ export type TuiSidebarFileItem = {
|
||||
export type TuiSlotMap = {
|
||||
app: {}
|
||||
home_logo: {}
|
||||
home_tips: {
|
||||
show_tips: boolean
|
||||
tips_hidden: boolean
|
||||
first_time_user: boolean
|
||||
}
|
||||
home_below_tips: {
|
||||
show_tips: boolean
|
||||
tips_hidden: boolean
|
||||
first_time_user: boolean
|
||||
}
|
||||
home_bottom: {}
|
||||
sidebar_title: {
|
||||
session_id: string
|
||||
title: string
|
||||
|
||||
Reference in New Issue
Block a user