import { BoxRenderable, RGBA, TextAttributes } from "@opentui/core" import { useKeyboard } from "@opentui/solid" import open from "open" import { createSignal, onCleanup, onMount } from "solid-js" import { selectedForeground, useTheme } from "@tui/context/theme" import { useDialog, type DialogContext } from "@tui/ui/dialog" import { Link } from "@tui/ui/link" import { GoLogo } from "./logo" import { BgPulse, type BgPulseMask } from "./bg-pulse" const GO_URL = "https://opencode.ai/go" const PAD_X = 3 const PAD_TOP_OUTER = 1 export type DialogGoUpsellProps = { onClose?: (dontShowAgain?: boolean) => void } function subscribe(props: DialogGoUpsellProps, dialog: ReturnType) { open(GO_URL).catch(() => {}) props.onClose?.() dialog.clear() } function dismiss(props: DialogGoUpsellProps, dialog: ReturnType) { props.onClose?.(true) dialog.clear() } export function DialogGoUpsell(props: DialogGoUpsellProps) { const dialog = useDialog() const { theme } = useTheme() const fg = selectedForeground(theme) const [selected, setSelected] = createSignal<"dismiss" | "subscribe">("subscribe") const [center, setCenter] = createSignal<{ x: number; y: number } | undefined>() const [masks, setMasks] = createSignal([]) let content: BoxRenderable | undefined let logoBox: BoxRenderable | undefined let headingBox: BoxRenderable | undefined let descBox: BoxRenderable | undefined let buttonsBox: BoxRenderable | undefined const sync = () => { if (!content || !logoBox) return setCenter({ x: logoBox.x - content.x + logoBox.width / 2, y: logoBox.y - content.y + logoBox.height / 2 + PAD_TOP_OUTER, }) const next: BgPulseMask[] = [] const baseY = PAD_TOP_OUTER for (const b of [headingBox, descBox, buttonsBox]) { if (!b) continue next.push({ x: b.x - content.x, y: b.y - content.y + baseY, width: b.width, height: b.height, pad: 2, strength: 0.78, }) } setMasks(next) } onMount(() => { sync() for (const b of [content, logoBox, headingBox, descBox, buttonsBox]) b?.on("resize", sync) }) onCleanup(() => { for (const b of [content, logoBox, headingBox, descBox, buttonsBox]) b?.off("resize", sync) }) useKeyboard((evt) => { if (evt.name === "left" || evt.name === "right" || evt.name === "tab") { setSelected((s) => (s === "subscribe" ? "dismiss" : "subscribe")) return } if (evt.name === "return") { if (selected() === "subscribe") subscribe(props, dialog) else dismiss(props, dialog) } }) return ( (content = item)}> (headingBox = item)} flexDirection="row" justifyContent="space-between"> Free limit reached dialog.clear()}> esc (descBox = item)} gap={0}> Subscribe to OpenCode Go for reliable access to the best open-source models, starting at $5/month. (logoBox = item)}> (buttonsBox = item)} flexDirection="row" justifyContent="space-between"> setSelected("dismiss")} onMouseUp={() => dismiss(props, dialog)} > don't show again setSelected("subscribe")} onMouseUp={() => subscribe(props, dialog)} > subscribe ) } DialogGoUpsell.show = (dialog: DialogContext) => { return new Promise((resolve) => { dialog.replace( () => resolve(dontShow ?? false)} />, () => resolve(false), ) }) }