fix(tui): show persona banners and remove OpenRouter warning

- Replace AGENT-CORE logo with dynamic persona ASCII art
- Logo now shows Zee, Stanley, or Johny based on active persona
- Remove OpenRouter warning dialog (was promoting OpenCode Zen)
- Update CLI logo to show Zee banner (default persona)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Artur Do Lago
2026-01-09 21:39:44 +01:00
parent 47d3e7ef9a
commit 4126df9d2b
3 changed files with 51 additions and 100 deletions
@@ -576,20 +576,6 @@ function App() {
},
])
createEffect(() => {
const currentModel = local.model.current()
if (!currentModel) return
if (currentModel.providerID === "openrouter" && !kv.get("openrouter_warning", false)) {
untrack(() => {
DialogAlert.show(
dialog,
"Warning",
"While openrouter is a convenient way to access LLMs your request will often be routed to subpar providers that do not work well in our testing.\n\nFor reliable access to models check out OpenCode Zen\nhttps://opencode.ai/zen",
).then(() => kv.set("openrouter_warning", true))
})
}
})
sdk.event.on(TuiEvent.CommandExecute.type, (evt) => {
command.trigger(evt.properties.command)
})
@@ -1,87 +1,49 @@
import { TextAttributes, RGBA } from "@opentui/core"
import { For, type JSX } from "solid-js"
import { useTheme, tint } from "@tui/context/theme"
import { TextAttributes } from "@opentui/core"
import { For, createMemo } from "solid-js"
import { useLocal } from "@tui/context/local"
// Shadow markers (rendered chars in parens):
// _ = full shadow cell (space with bg=shadow)
// ^ = letter top, shadow bottom (▀ with fg=letter, bg=shadow)
// ~ = shadow top only (▀ with fg=shadow)
const SHADOW_MARKER = /[_^~]/
// agent-core logo (AGENT left dimmed, CORE right bold)
const LOGO_LEFT = [` `, `█▀▀█ █▀▀▀ █▀▀▀ █▀▀▄ ▀▀█▀▀`, `█▀▀█ █░░█ █▀▀▀ █░░█ █ `, `▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀ ▀ ▀ `]
const LOGO_RIGHT = [``, `█▀▀▀ █▀▀█ █▀▀█ █▀▀▀`, `█░░░ █░░█ █▀▀▀ █▀▀▀`, `▀▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀`]
// Persona ASCII art banners
const PERSONA_ART: Record<string, string[]> = {
zee: [
"███████╗███████╗███████╗",
"╚══███╔╝██╔════╝██╔════╝",
" ███╔╝ █████╗ █████╗ ",
" ███╔╝ ██╔══╝ ██╔══╝ ",
"███████╗███████╗███████╗",
"╚══════╝╚══════╝╚══════╝",
],
stanley: [
"███████╗████████╗ █████╗ ███╗ ██╗██╗ ███████╗██╗ ██╗",
"██╔════╝╚══██╔══╝██╔══██╗████╗ ██║██║ ██╔════╝╚██╗ ██╔╝",
"███████╗ ██║ ███████║██╔██╗ ██║██║ █████╗ ╚████╔╝ ",
"╚════██║ ██║ ██╔══██║██║╚██╗██║██║ ██╔══╝ ╚██╔╝ ",
"███████║ ██║ ██║ ██║██║ ╚████║███████╗███████╗ ██║ ",
"╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ╚═╝ ",
],
johny: [
" ██╗ ██████╗ ██╗ ██╗███╗ ██╗██╗ ██╗",
" ██║██╔═══██╗██║ ██║████╗ ██║╚██╗ ██╔╝",
" ██║██║ ██║███████║██╔██╗ ██║ ╚████╔╝ ",
"██ ██║██║ ██║██╔══██║██║╚██╗██║ ╚██╔╝ ",
"╚█████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ",
" ╚════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ",
],
}
export function Logo() {
const { theme } = useTheme()
const local = useLocal()
const renderLine = (line: string, fg: RGBA, bold: boolean): JSX.Element[] => {
const shadow = tint(theme.background, fg, 0.25)
const attrs = bold ? TextAttributes.BOLD : undefined
const elements: JSX.Element[] = []
let i = 0
while (i < line.length) {
const rest = line.slice(i)
const markerIndex = rest.search(SHADOW_MARKER)
if (markerIndex === -1) {
elements.push(
<text fg={fg} attributes={attrs} selectable={false}>
{rest}
</text>,
)
break
}
if (markerIndex > 0) {
elements.push(
<text fg={fg} attributes={attrs} selectable={false}>
{rest.slice(0, markerIndex)}
</text>,
)
}
const marker = rest[markerIndex]
switch (marker) {
case "_":
elements.push(
<text fg={fg} bg={shadow} attributes={attrs} selectable={false}>
{" "}
</text>,
)
break
case "^":
elements.push(
<text fg={fg} bg={shadow} attributes={attrs} selectable={false}>
</text>,
)
break
case "~":
elements.push(
<text fg={shadow} attributes={attrs} selectable={false}>
</text>,
)
break
}
i += markerIndex + 1
}
return elements
}
const agent = createMemo(() => local.agent.current())
const color = createMemo(() => local.agent.color(agent().name))
const art = createMemo(() => PERSONA_ART[agent().name.toLowerCase()] || PERSONA_ART.zee)
return (
<box>
<For each={LOGO_LEFT}>
{(line, index) => (
<box flexDirection="row" gap={1}>
<box flexDirection="row">{renderLine(line, theme.textMuted, false)}</box>
<box flexDirection="row">{renderLine(LOGO_RIGHT[index()], theme.text, true)}</box>
</box>
<box flexDirection="column" alignItems="center">
<For each={art()}>
{(line) => (
<text fg={color()} attributes={TextAttributes.BOLD} selectable={false}>
{line}
</text>
)}
</For>
</box>
+11 -8
View File
@@ -3,12 +3,14 @@ import { EOL } from "os"
import { NamedError } from "@opencode-ai/util/error"
export namespace UI {
// agent-core logo (AGENT left dimmed, CORE right bold)
// Default persona logo (Zee - the default gateway)
const LOGO = [
[` `, ``],
[`█▀▀█ █▀▀▀ █▀▀▀ █▀▀▄ ▀▀█▀▀`, `█▀▀▀ █▀▀█ █▀▀█ █▀▀▀`],
[`█▀▀█ █░░█ █▀▀▀ █░░█ █ `, `█░░░ █░░█ █▀▀▀ █▀▀▀`],
[`▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀ ▀ ▀ `, `▀▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀`],
"███████╗███████╗███████╗",
"╚══███╔╝██╔════╝██╔════╝",
" ███╔╝ █████╗ █████╗ ",
" ███╔╝ ██╔══╝ ██╔══╝ ",
"███████╗███████╗███████╗",
"╚══════╝╚══════╝╚══════╝",
]
export const CancelledError = NamedError.create("UICancelledError", z.void())
@@ -49,12 +51,13 @@ export namespace UI {
export function logo(pad?: string) {
const result = []
// Zee blue color: #2563EB
const color = "\x1b[38;2;37;99;235m"
for (const row of LOGO) {
if (pad) result.push(pad)
result.push(Bun.color("gray", "ansi"))
result.push(row[0])
result.push(color)
result.push(row)
result.push("\x1b[0m")
result.push(row[1])
result.push(EOL)
}
return result.join("").trimEnd()