@agentprojectcontext/apx 1.15.5 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +40 -5
- package/src/cli/commands/log.js +113 -0
- package/src/cli/commands/overlay.js +253 -0
- package/src/cli/commands/sys.js +88 -16
- package/src/cli/index.js +23 -1
- package/src/cli/terminal-chat/renderer.js +71 -56
- package/src/cli-ts/commands/agent.ts +173 -0
- package/src/cli-ts/commands/chat.ts +119 -0
- package/src/cli-ts/commands/daemon.ts +112 -0
- package/src/cli-ts/commands/exec.ts +109 -0
- package/src/cli-ts/commands/mcp.ts +235 -0
- package/src/cli-ts/commands/session.ts +224 -0
- package/src/cli-ts/commands/status.ts +61 -0
- package/src/cli-ts/http.ts +36 -0
- package/src/cli-ts/index.ts +73 -0
- package/src/cli-ts/ui.ts +107 -0
- package/src/core/logging.js +81 -0
- package/src/daemon/api.js +58 -0
- package/src/daemon/engines/anthropic.js +60 -1
- package/src/daemon/engines/index.js +2 -1
- package/src/daemon/engines/ollama.js +70 -3
- package/src/daemon/index.js +58 -0
- package/src/daemon/overlay-ws.js +40 -0
- package/src/daemon/plugins/index.js +2 -1
- package/src/daemon/plugins/overlay.js +177 -0
- package/src/daemon/plugins/telegram.js +15 -3
- package/src/daemon/super-agent.js +102 -19
- package/src/daemon/transcription.js +262 -59
- package/src/daemon/wakeup.js +14 -19
- package/src/daemon/whisper-server.py +57 -6
- package/src/overlay/index.html +44 -0
- package/src/overlay/main.js +480 -0
- package/src/overlay/package.json +3 -0
- package/src/overlay/preload.js +34 -0
- package/src/overlay/renderer.js +371 -0
- package/src/overlay/style.css +250 -0
- package/src/tui/_shims/cli-error.ts +6 -0
- package/src/tui/_shims/cli-logo.ts +18 -0
- package/src/tui/_shims/cli-ui.ts +1 -0
- package/src/tui/_shims/config-console-state.ts +7 -0
- package/src/tui/_shims/core-any.ts +30 -0
- package/src/tui/_shims/core-binary.ts +13 -0
- package/src/tui/_shims/core-flag.ts +3 -0
- package/src/tui/_shims/core-log.ts +14 -0
- package/src/tui/_shims/lsp-language.ts +1 -0
- package/src/tui/_shims/opencode-any.ts +135 -0
- package/src/tui/_shims/opencode-sdk-v2.ts +48 -0
- package/src/tui/_shims/plugin-tui.ts +13 -0
- package/src/tui/_shims/provider-provider.ts +10 -0
- package/src/tui/_shims/session-retry.ts +1 -0
- package/src/tui/_shims/session-schema.ts +15 -0
- package/src/tui/_shims/session-session.ts +3 -0
- package/src/tui/_shims/snapshot.ts +4 -0
- package/src/tui/_shims/tool-any.ts +18 -0
- package/src/tui/_shims/util-error.ts +7 -0
- package/src/tui/_shims/util-filesystem.ts +79 -0
- package/src/tui/_shims/util-format.ts +7 -0
- package/src/tui/_shims/util-iife.ts +3 -0
- package/src/tui/_shims/util-locale.ts +10 -0
- package/src/tui/_shims/util-process.ts +38 -0
- package/src/tui/app.tsx +783 -0
- package/src/tui/asset/charge.wav +0 -0
- package/src/tui/asset/pulse-a.wav +0 -0
- package/src/tui/asset/pulse-b.wav +0 -0
- package/src/tui/asset/pulse-c.wav +0 -0
- package/src/tui/attach.ts +100 -0
- package/src/tui/component/bg-pulse-render.ts +436 -0
- package/src/tui/component/bg-pulse.tsx +99 -0
- package/src/tui/component/border.tsx +21 -0
- package/src/tui/component/dialog-agent.tsx +31 -0
- package/src/tui/component/dialog-console-org.tsx +103 -0
- package/src/tui/component/dialog-mcp.tsx +85 -0
- package/src/tui/component/dialog-model.tsx +175 -0
- package/src/tui/component/dialog-provider.tsx +456 -0
- package/src/tui/component/dialog-retry-action.tsx +160 -0
- package/src/tui/component/dialog-session-delete-failed.tsx +99 -0
- package/src/tui/component/dialog-session-list.tsx +323 -0
- package/src/tui/component/dialog-session-rename.tsx +31 -0
- package/src/tui/component/dialog-skill.tsx +36 -0
- package/src/tui/component/dialog-stash.tsx +87 -0
- package/src/tui/component/dialog-status.tsx +168 -0
- package/src/tui/component/dialog-tag.tsx +44 -0
- package/src/tui/component/dialog-theme-list.tsx +50 -0
- package/src/tui/component/dialog-variant.tsx +39 -0
- package/src/tui/component/dialog-workspace-create.tsx +302 -0
- package/src/tui/component/dialog-workspace-file-changes.tsx +138 -0
- package/src/tui/component/dialog-workspace-unavailable.tsx +69 -0
- package/src/tui/component/error-component.tsx +92 -0
- package/src/tui/component/logo.tsx +896 -0
- package/src/tui/component/plugin-route-missing.tsx +14 -0
- package/src/tui/component/prompt/autocomplete.tsx +869 -0
- package/src/tui/component/prompt/cwd.ts +0 -0
- package/src/tui/component/prompt/frecency.tsx +90 -0
- package/src/tui/component/prompt/history.tsx +108 -0
- package/src/tui/component/prompt/index.tsx +1809 -0
- package/src/tui/component/prompt/part.ts +16 -0
- package/src/tui/component/prompt/stash.tsx +101 -0
- package/src/tui/component/prompt/traits.ts +35 -0
- package/src/tui/component/spinner.tsx +24 -0
- package/src/tui/component/startup-loading.tsx +63 -0
- package/src/tui/component/todo-item.tsx +32 -0
- package/src/tui/component/use-connected.tsx +9 -0
- package/src/tui/component/workspace-label.tsx +19 -0
- package/src/tui/config/cwd.ts +5 -0
- package/src/tui/config/keybind.ts +432 -0
- package/src/tui/config/tui-migrate.ts +154 -0
- package/src/tui/config/tui-schema.ts +34 -0
- package/src/tui/config/tui.ts +46 -0
- package/src/tui/context/aggregate-failures.ts +34 -0
- package/src/tui/context/args.tsx +15 -0
- package/src/tui/context/command-palette.tsx +163 -0
- package/src/tui/context/directory.ts +15 -0
- package/src/tui/context/editor-zed.ts +283 -0
- package/src/tui/context/editor.ts +468 -0
- package/src/tui/context/event-apx.ts +22 -0
- package/src/tui/context/event.ts +6 -0
- package/src/tui/context/exit.tsx +60 -0
- package/src/tui/context/helper.tsx +25 -0
- package/src/tui/context/kv.tsx +81 -0
- package/src/tui/context/local.tsx +608 -0
- package/src/tui/context/path-format.tsx +39 -0
- package/src/tui/context/project-apx.tsx +48 -0
- package/src/tui/context/project.tsx +7 -0
- package/src/tui/context/prompt.tsx +18 -0
- package/src/tui/context/route.tsx +52 -0
- package/src/tui/context/sdk-apx.tsx +185 -0
- package/src/tui/context/sdk.tsx +6 -0
- package/src/tui/context/sync-apx.tsx +178 -0
- package/src/tui/context/sync-v2.tsx +16 -0
- package/src/tui/context/sync.tsx +118 -0
- package/src/tui/context/theme/aura.json +69 -0
- package/src/tui/context/theme/ayu.json +80 -0
- package/src/tui/context/theme/carbonfox.json +248 -0
- package/src/tui/context/theme/catppuccin-frappe.json +230 -0
- package/src/tui/context/theme/catppuccin-macchiato.json +230 -0
- package/src/tui/context/theme/catppuccin.json +112 -0
- package/src/tui/context/theme/cobalt2.json +225 -0
- package/src/tui/context/theme/cursor.json +249 -0
- package/src/tui/context/theme/dracula.json +219 -0
- package/src/tui/context/theme/everforest.json +241 -0
- package/src/tui/context/theme/flexoki.json +237 -0
- package/src/tui/context/theme/github.json +233 -0
- package/src/tui/context/theme/gruvbox.json +242 -0
- package/src/tui/context/theme/kanagawa.json +77 -0
- package/src/tui/context/theme/lucent-orng.json +234 -0
- package/src/tui/context/theme/material.json +235 -0
- package/src/tui/context/theme/matrix.json +77 -0
- package/src/tui/context/theme/mercury.json +252 -0
- package/src/tui/context/theme/monokai.json +221 -0
- package/src/tui/context/theme/nightowl.json +221 -0
- package/src/tui/context/theme/nord.json +223 -0
- package/src/tui/context/theme/one-dark.json +84 -0
- package/src/tui/context/theme/opencode.json +245 -0
- package/src/tui/context/theme/orng.json +249 -0
- package/src/tui/context/theme/osaka-jade.json +93 -0
- package/src/tui/context/theme/palenight.json +222 -0
- package/src/tui/context/theme/rosepine.json +234 -0
- package/src/tui/context/theme/solarized.json +223 -0
- package/src/tui/context/theme/synthwave84.json +226 -0
- package/src/tui/context/theme/tokyonight.json +243 -0
- package/src/tui/context/theme/vercel.json +245 -0
- package/src/tui/context/theme/vesper.json +218 -0
- package/src/tui/context/theme/zenburn.json +223 -0
- package/src/tui/context/theme.tsx +1247 -0
- package/src/tui/context/tui-config.tsx +9 -0
- package/src/tui/event.ts +16 -0
- package/src/tui/feature-plugins/home/footer.tsx +94 -0
- package/src/tui/feature-plugins/home/tips-view.tsx +166 -0
- package/src/tui/feature-plugins/home/tips.tsx +59 -0
- package/src/tui/feature-plugins/sidebar/context.tsx +65 -0
- package/src/tui/feature-plugins/sidebar/files.tsx +63 -0
- package/src/tui/feature-plugins/sidebar/footer.tsx +94 -0
- package/src/tui/feature-plugins/sidebar/lsp.tsx +65 -0
- package/src/tui/feature-plugins/sidebar/mcp.tsx +97 -0
- package/src/tui/feature-plugins/sidebar/todo.tsx +49 -0
- package/src/tui/feature-plugins/system/plugins.tsx +269 -0
- package/src/tui/feature-plugins/system/session-v2.tsx +1143 -0
- package/src/tui/feature-plugins/system/which-key.tsx +608 -0
- package/src/tui/keymap.tsx +166 -0
- package/src/tui/layer.ts +6 -0
- package/src/tui/plugin/api.tsx +381 -0
- package/src/tui/plugin/command-shim.ts +109 -0
- package/src/tui/plugin/internal.ts +33 -0
- package/src/tui/plugin/runtime.ts +1069 -0
- package/src/tui/plugin/slots.tsx +60 -0
- package/src/tui/routes/home.tsx +96 -0
- package/src/tui/routes/session/dialog-fork-from-timeline.tsx +76 -0
- package/src/tui/routes/session/dialog-message.tsx +108 -0
- package/src/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/tui/routes/session/footer.tsx +91 -0
- package/src/tui/routes/session/index.tsx +188 -0
- package/src/tui/routes/session/permission.tsx +722 -0
- package/src/tui/routes/session/question.tsx +490 -0
- package/src/tui/routes/session/sidebar.tsx +102 -0
- package/src/tui/routes/session/subagent-footer.tsx +133 -0
- package/src/tui/run.ts +84 -0
- package/src/tui/thread.ts +261 -0
- package/src/tui/tsconfig.json +40 -0
- package/src/tui/ui/dialog-alert.tsx +66 -0
- package/src/tui/ui/dialog-confirm.tsx +108 -0
- package/src/tui/ui/dialog-export-options.tsx +217 -0
- package/src/tui/ui/dialog-help.tsx +40 -0
- package/src/tui/ui/dialog-prompt.tsx +101 -0
- package/src/tui/ui/dialog-select.tsx +553 -0
- package/src/tui/ui/dialog.tsx +211 -0
- package/src/tui/ui/link.tsx +34 -0
- package/src/tui/ui/spinner.ts +368 -0
- package/src/tui/ui/toast.tsx +111 -0
- package/src/tui/util/clipboard.ts +217 -0
- package/src/tui/util/editor.ts +37 -0
- package/src/tui/util/model.ts +23 -0
- package/src/tui/util/provider-origin.ts +7 -0
- package/src/tui/util/revert-diff.ts +18 -0
- package/src/tui/util/scroll.ts +25 -0
- package/src/tui/util/selection.ts +65 -0
- package/src/tui/util/signal.ts +41 -0
- package/src/tui/util/sound.ts +156 -0
- package/src/tui/util/transcript.ts +112 -0
- package/src/tui/validate-session.ts +29 -0
- package/src/tui/win32.ts +130 -0
- package/src/tui/worker.ts +104 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { cmd } from "../cmd"
|
|
2
|
+
import { UI } from "@/cli/ui"
|
|
3
|
+
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
|
4
|
+
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
|
5
|
+
import { errorMessage } from "@/util/error"
|
|
6
|
+
import { validateSession } from "./validate-session"
|
|
7
|
+
import { ServerAuth } from "@/server/auth"
|
|
8
|
+
|
|
9
|
+
export const AttachCommand = cmd({
|
|
10
|
+
command: "attach <url>",
|
|
11
|
+
describe: "attach to a running opencode server",
|
|
12
|
+
builder: (yargs) =>
|
|
13
|
+
yargs
|
|
14
|
+
.positional("url", {
|
|
15
|
+
type: "string",
|
|
16
|
+
describe: "http://localhost:4096",
|
|
17
|
+
demandOption: true,
|
|
18
|
+
})
|
|
19
|
+
.option("dir", {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "directory to run in",
|
|
22
|
+
})
|
|
23
|
+
.option("continue", {
|
|
24
|
+
alias: ["c"],
|
|
25
|
+
describe: "continue the last session",
|
|
26
|
+
type: "boolean",
|
|
27
|
+
})
|
|
28
|
+
.option("session", {
|
|
29
|
+
alias: ["s"],
|
|
30
|
+
type: "string",
|
|
31
|
+
describe: "session id to continue",
|
|
32
|
+
})
|
|
33
|
+
.option("fork", {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
describe: "fork the session when continuing (use with --continue or --session)",
|
|
36
|
+
})
|
|
37
|
+
.option("password", {
|
|
38
|
+
alias: ["p"],
|
|
39
|
+
type: "string",
|
|
40
|
+
describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)",
|
|
41
|
+
})
|
|
42
|
+
.option("username", {
|
|
43
|
+
alias: ["u"],
|
|
44
|
+
type: "string",
|
|
45
|
+
describe: "basic auth username (defaults to OPENCODE_SERVER_USERNAME or 'opencode')",
|
|
46
|
+
}),
|
|
47
|
+
handler: async (args) => {
|
|
48
|
+
const unguard = win32InstallCtrlCGuard()
|
|
49
|
+
try {
|
|
50
|
+
win32DisableProcessedInput()
|
|
51
|
+
|
|
52
|
+
if (args.fork && !args.continue && !args.session) {
|
|
53
|
+
UI.error("--fork requires --continue or --session")
|
|
54
|
+
process.exitCode = 1
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const directory = (() => {
|
|
59
|
+
if (!args.dir) return undefined
|
|
60
|
+
try {
|
|
61
|
+
process.chdir(args.dir)
|
|
62
|
+
return process.cwd()
|
|
63
|
+
} catch {
|
|
64
|
+
// If the directory doesn't exist locally (remote attach), pass it through.
|
|
65
|
+
return args.dir
|
|
66
|
+
}
|
|
67
|
+
})()
|
|
68
|
+
const headers = ServerAuth.headers({ password: args.password, username: args.username })
|
|
69
|
+
const config = await TuiConfig.get()
|
|
70
|
+
const { tui } = await import("./app")
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await validateSession({
|
|
74
|
+
url: args.url,
|
|
75
|
+
sessionID: args.session,
|
|
76
|
+
directory,
|
|
77
|
+
headers,
|
|
78
|
+
})
|
|
79
|
+
} catch (error) {
|
|
80
|
+
UI.error(errorMessage(error))
|
|
81
|
+
process.exitCode = 1
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await tui({
|
|
86
|
+
url: args.url,
|
|
87
|
+
config,
|
|
88
|
+
args: {
|
|
89
|
+
continue: args.continue,
|
|
90
|
+
sessionID: args.session,
|
|
91
|
+
fork: args.fork,
|
|
92
|
+
},
|
|
93
|
+
directory,
|
|
94
|
+
headers,
|
|
95
|
+
})
|
|
96
|
+
} finally {
|
|
97
|
+
unguard?.()
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
})
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { OptimizedBuffer, RGBA, TextAttributes } from "@opentui/core"
|
|
2
|
+
import { go } from "@/cli/logo"
|
|
3
|
+
|
|
4
|
+
const PERIOD = 4600
|
|
5
|
+
const RINGS = 3
|
|
6
|
+
const WIDTH = 3.8
|
|
7
|
+
const TAIL = 9.5
|
|
8
|
+
const AMP = 0.55
|
|
9
|
+
const TAIL_AMP = 0.16
|
|
10
|
+
const BREATH_AMP = 0.05
|
|
11
|
+
const BREATH_SPEED = 0.0008
|
|
12
|
+
// Offset so the bg ring emits from the estimated GO center when the logo shimmer peaks.
|
|
13
|
+
const PHASE_OFFSET = 0.29
|
|
14
|
+
const LOGO_GAP = 1
|
|
15
|
+
const LOGO_TOP_BIAS = -1
|
|
16
|
+
const LOGO_LEFT_WIDTH = go.left[0]?.length ?? 0
|
|
17
|
+
const LOGO_LINES = go.left.map((line, index) => line + " ".repeat(LOGO_GAP) + go.right[index])
|
|
18
|
+
const LOGO_WIDTH = LOGO_LINES[0]?.length ?? 0
|
|
19
|
+
const LOGO_HEIGHT = LOGO_LINES.length
|
|
20
|
+
const SPACE = " ".codePointAt(0)!
|
|
21
|
+
const TOP_HALF = "▀".codePointAt(0)!
|
|
22
|
+
const FULL_BLOCK = "█".codePointAt(0)!
|
|
23
|
+
const RING_SCALE = 1 / RINGS
|
|
24
|
+
const TAIL_SCALE = 1 / TAIL
|
|
25
|
+
const LOGO_REACH = Math.hypot(LOGO_WIDTH, LOGO_HEIGHT * 2) + 3
|
|
26
|
+
|
|
27
|
+
const enum LogoCellKind {
|
|
28
|
+
Background,
|
|
29
|
+
Top,
|
|
30
|
+
ShadowTop,
|
|
31
|
+
Solid,
|
|
32
|
+
Char,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type LogoTemplateCell = {
|
|
36
|
+
x: number
|
|
37
|
+
y: number
|
|
38
|
+
kind: LogoCellKind
|
|
39
|
+
charCode: number
|
|
40
|
+
attributes: number
|
|
41
|
+
topDist: number
|
|
42
|
+
bottomDist: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const LOGO_TEMPLATE: LogoTemplateCell[] = LOGO_LINES.flatMap((line, y) =>
|
|
46
|
+
Array.from(line)
|
|
47
|
+
.map((char, x) => {
|
|
48
|
+
if (char === " ") return
|
|
49
|
+
const kind =
|
|
50
|
+
char === "_"
|
|
51
|
+
? LogoCellKind.Background
|
|
52
|
+
: char === "^"
|
|
53
|
+
? LogoCellKind.Top
|
|
54
|
+
: char === "~"
|
|
55
|
+
? LogoCellKind.ShadowTop
|
|
56
|
+
: char === "█"
|
|
57
|
+
? LogoCellKind.Solid
|
|
58
|
+
: LogoCellKind.Char
|
|
59
|
+
return {
|
|
60
|
+
x,
|
|
61
|
+
y,
|
|
62
|
+
kind,
|
|
63
|
+
charCode: char.codePointAt(0) ?? SPACE,
|
|
64
|
+
attributes: x > LOGO_LEFT_WIDTH ? TextAttributes.BOLD : 0,
|
|
65
|
+
topDist: Math.hypot(x + 0.5 - LOGO_WIDTH / 2, y * 2 - LOGO_HEIGHT),
|
|
66
|
+
bottomDist: Math.hypot(x + 0.5 - LOGO_WIDTH / 2, y * 2 + 1 - LOGO_HEIGHT),
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
.filter((cell): cell is LogoTemplateCell => !!cell),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
export type Rgb = [number, number, number]
|
|
73
|
+
|
|
74
|
+
export type GoUpsellArtRenderOptions = {
|
|
75
|
+
deltaTime?: number
|
|
76
|
+
rgb?: boolean
|
|
77
|
+
cache?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const CACHE_FRAME_COUNT = Math.round(PERIOD / (1000 / 30))
|
|
81
|
+
const CACHE_FRAMES_PER_RENDER = 1
|
|
82
|
+
|
|
83
|
+
export function toRgb(color: RGBA): Rgb {
|
|
84
|
+
const [r, g, b] = color.toInts()
|
|
85
|
+
return [r, g, b]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function clamp(n: number) {
|
|
89
|
+
return Math.max(0, Math.min(1, n))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function writeRgb(buffer: Uint16Array, offset: number, r: number, g: number, b: number, a = 255) {
|
|
93
|
+
buffer[offset] = r
|
|
94
|
+
buffer[offset + 1] = g
|
|
95
|
+
buffer[offset + 2] = b
|
|
96
|
+
buffer[offset + 3] = a
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function mixChannel(base: number, overlay: number, alpha: number) {
|
|
100
|
+
return Math.round(base + (overlay - base) * clamp(alpha))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function writeLogoTint(
|
|
104
|
+
buffer: Uint16Array,
|
|
105
|
+
offset: number,
|
|
106
|
+
base: Rgb,
|
|
107
|
+
primary: Rgb,
|
|
108
|
+
primaryMix: number,
|
|
109
|
+
peakMix: number,
|
|
110
|
+
) {
|
|
111
|
+
const p = clamp(primaryMix)
|
|
112
|
+
const q = clamp(peakMix)
|
|
113
|
+
const r = mixChannel(mixChannel(base[0], primary[0], p), 255, q)
|
|
114
|
+
const g = mixChannel(mixChannel(base[1], primary[1], p), 255, q)
|
|
115
|
+
const b = mixChannel(mixChannel(base[2], primary[2], p), 255, q)
|
|
116
|
+
writeRgb(buffer, offset, r, g, b)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function sameRgb(a: Rgb, b: Rgb) {
|
|
120
|
+
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export class GoUpsellArtPainter {
|
|
124
|
+
private panelRgb: Rgb = [0, 0, 0]
|
|
125
|
+
private primaryRgb: Rgb = [255, 255, 255]
|
|
126
|
+
private logoBaseRgb: Rgb = [180, 180, 180]
|
|
127
|
+
private elapsed = 0
|
|
128
|
+
private distances = new Float32Array(0)
|
|
129
|
+
private edgeFalloff = new Float32Array(0)
|
|
130
|
+
private geometryWidth = 0
|
|
131
|
+
private geometryHeight = 0
|
|
132
|
+
private reach = 1
|
|
133
|
+
private logoX = 0
|
|
134
|
+
private logoY = 0
|
|
135
|
+
private logoIndexes = new Int32Array(0)
|
|
136
|
+
private logoRgb: boolean | undefined
|
|
137
|
+
private pulsePeak = 0
|
|
138
|
+
private pulsePrimary = 0
|
|
139
|
+
private cacheDirty = true
|
|
140
|
+
private frameCache: Array<{ fg: Uint16Array; bg: Uint16Array }> = []
|
|
141
|
+
private cacheBuildIndex = 0
|
|
142
|
+
|
|
143
|
+
setBackgroundPanel(value: RGBA | Rgb | undefined) {
|
|
144
|
+
if (!value) return false
|
|
145
|
+
const next = value instanceof RGBA ? toRgb(value) : value
|
|
146
|
+
if (sameRgb(this.panelRgb, next)) return false
|
|
147
|
+
this.panelRgb = next
|
|
148
|
+
this.invalidateCache()
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
setLogoBase(value: RGBA | Rgb | undefined) {
|
|
153
|
+
if (!value) return false
|
|
154
|
+
const next = value instanceof RGBA ? toRgb(value) : value
|
|
155
|
+
if (sameRgb(this.logoBaseRgb, next)) return false
|
|
156
|
+
this.logoBaseRgb = next
|
|
157
|
+
this.invalidateCache()
|
|
158
|
+
return true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setPrimary(value: RGBA | Rgb | undefined) {
|
|
162
|
+
if (!value) return false
|
|
163
|
+
const next = value instanceof RGBA ? toRgb(value) : value
|
|
164
|
+
if (sameRgb(this.primaryRgb, next)) return false
|
|
165
|
+
this.primaryRgb = next
|
|
166
|
+
this.invalidateCache()
|
|
167
|
+
return true
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
render(frameBuffer: OptimizedBuffer, options: GoUpsellArtRenderOptions = {}) {
|
|
171
|
+
const rgb = options.rgb === true
|
|
172
|
+
this.elapsed = (this.elapsed + (options.deltaTime ?? 0)) % PERIOD
|
|
173
|
+
this.rebuildGeometry(frameBuffer, rgb)
|
|
174
|
+
if (options.cache !== false) {
|
|
175
|
+
this.drawCached(frameBuffer, rgb)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
this.drawBackground(frameBuffer, this.elapsed)
|
|
179
|
+
this.drawLogo(frameBuffer, this.elapsed, rgb)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private invalidateCache() {
|
|
183
|
+
this.cacheDirty = true
|
|
184
|
+
this.cacheBuildIndex = 0
|
|
185
|
+
this.frameCache = []
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private rebuildGeometry(frameBuffer: OptimizedBuffer, rgb: boolean) {
|
|
189
|
+
const width = frameBuffer.width
|
|
190
|
+
const height = frameBuffer.height
|
|
191
|
+
const geometryChanged = width !== this.geometryWidth || height !== this.geometryHeight
|
|
192
|
+
const logoTemplateChanged = this.logoRgb !== rgb
|
|
193
|
+
if (!geometryChanged && !logoTemplateChanged) return
|
|
194
|
+
|
|
195
|
+
if (geometryChanged) {
|
|
196
|
+
this.geometryWidth = width
|
|
197
|
+
this.geometryHeight = height
|
|
198
|
+
this.logoX = Math.max(0, Math.floor((width - LOGO_WIDTH) / 2))
|
|
199
|
+
this.logoY = Math.max(
|
|
200
|
+
0,
|
|
201
|
+
Math.min(Math.max(0, height - LOGO_HEIGHT), Math.round((height - LOGO_HEIGHT) / 2) + LOGO_TOP_BIAS),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
const centerX = this.logoX + LOGO_WIDTH / 2
|
|
205
|
+
const centerY = this.logoY + LOGO_HEIGHT / 2
|
|
206
|
+
this.reach = Math.hypot(Math.max(centerX, width - centerX), Math.max(centerY, height - centerY) * 2) + TAIL
|
|
207
|
+
this.distances = new Float32Array(width * height)
|
|
208
|
+
this.edgeFalloff = new Float32Array(width * height)
|
|
209
|
+
|
|
210
|
+
for (let y = 0; y < height; y++) {
|
|
211
|
+
for (let x = 0; x < width; x++) {
|
|
212
|
+
const index = y * width + x
|
|
213
|
+
const dist = Math.hypot(x + 0.5 - centerX, (y + 0.5 - centerY) * 2)
|
|
214
|
+
this.distances[index] = dist
|
|
215
|
+
this.edgeFalloff[index] = Math.max(0, 1 - (dist / (this.reach * 0.85)) ** 2)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.logoRgb = rgb
|
|
221
|
+
this.invalidateCache()
|
|
222
|
+
this.rebuildCellTemplate(frameBuffer, rgb)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private drawCached(frameBuffer: OptimizedBuffer, rgb: boolean) {
|
|
226
|
+
if (this.cacheDirty) this.startFrameCache(frameBuffer, rgb)
|
|
227
|
+
if (this.cacheBuildIndex < CACHE_FRAME_COUNT) {
|
|
228
|
+
this.buildFrameCache(frameBuffer, rgb)
|
|
229
|
+
this.drawBackground(frameBuffer, this.elapsed)
|
|
230
|
+
this.drawLogo(frameBuffer, this.elapsed, rgb)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const frame = this.frameCache[Math.floor((this.elapsed / PERIOD) * CACHE_FRAME_COUNT) % CACHE_FRAME_COUNT]
|
|
235
|
+
if (frame) {
|
|
236
|
+
frameBuffer.buffers.fg.set(frame.fg)
|
|
237
|
+
frameBuffer.buffers.bg.set(frame.bg)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private startFrameCache(frameBuffer: OptimizedBuffer, rgb: boolean) {
|
|
242
|
+
this.frameCache = []
|
|
243
|
+
this.cacheBuildIndex = 0
|
|
244
|
+
this.rebuildCellTemplate(frameBuffer, rgb)
|
|
245
|
+
this.cacheDirty = false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private buildFrameCache(frameBuffer: OptimizedBuffer, rgb: boolean) {
|
|
249
|
+
const end = Math.min(CACHE_FRAME_COUNT, this.cacheBuildIndex + CACHE_FRAMES_PER_RENDER)
|
|
250
|
+
for (; this.cacheBuildIndex < end; this.cacheBuildIndex++) {
|
|
251
|
+
const t = (this.cacheBuildIndex / CACHE_FRAME_COUNT) * PERIOD
|
|
252
|
+
this.drawBackground(frameBuffer, t)
|
|
253
|
+
this.drawLogo(frameBuffer, t, rgb)
|
|
254
|
+
this.frameCache.push({
|
|
255
|
+
fg: new Uint16Array(frameBuffer.buffers.fg),
|
|
256
|
+
bg: new Uint16Array(frameBuffer.buffers.bg),
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private rebuildCellTemplate(frameBuffer: OptimizedBuffer, rgb: boolean) {
|
|
262
|
+
const buffers = frameBuffer.buffers
|
|
263
|
+
buffers.char.fill(SPACE)
|
|
264
|
+
buffers.attributes.fill(0)
|
|
265
|
+
|
|
266
|
+
if (this.geometryWidth < LOGO_WIDTH || this.geometryHeight < LOGO_HEIGHT) {
|
|
267
|
+
this.logoIndexes = new Int32Array(0)
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.logoIndexes = new Int32Array(LOGO_TEMPLATE.length)
|
|
272
|
+
for (let i = 0; i < LOGO_TEMPLATE.length; i++) {
|
|
273
|
+
const cell = LOGO_TEMPLATE[i]!
|
|
274
|
+
const index = (this.logoY + cell.y) * this.geometryWidth + this.logoX + cell.x
|
|
275
|
+
this.logoIndexes[i] = index
|
|
276
|
+
buffers.attributes[index] = cell.attributes
|
|
277
|
+
buffers.char[index] =
|
|
278
|
+
cell.kind === LogoCellKind.Background
|
|
279
|
+
? SPACE
|
|
280
|
+
: cell.kind === LogoCellKind.Top || cell.kind === LogoCellKind.ShadowTop
|
|
281
|
+
? TOP_HALF
|
|
282
|
+
: cell.kind === LogoCellKind.Solid
|
|
283
|
+
? rgb
|
|
284
|
+
? TOP_HALF
|
|
285
|
+
: FULL_BLOCK
|
|
286
|
+
: cell.charCode
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private drawBackground(frameBuffer: OptimizedBuffer, t: number) {
|
|
291
|
+
const buffers = frameBuffer.buffers
|
|
292
|
+
const fg = buffers.fg
|
|
293
|
+
const bg = buffers.bg
|
|
294
|
+
const distances = this.distances
|
|
295
|
+
const edgeFalloff = this.edgeFalloff
|
|
296
|
+
const baseR = this.panelRgb[0]
|
|
297
|
+
const baseG = this.panelRgb[1]
|
|
298
|
+
const baseB = this.panelRgb[2]
|
|
299
|
+
const deltaR = this.primaryRgb[0] - baseR
|
|
300
|
+
const deltaG = this.primaryRgb[1] - baseG
|
|
301
|
+
const deltaB = this.primaryRgb[2] - baseB
|
|
302
|
+
const breath = (0.5 + 0.5 * Math.sin(t * BREATH_SPEED)) * BREATH_AMP
|
|
303
|
+
|
|
304
|
+
const phase0 = (t / PERIOD - PHASE_OFFSET + 1) % 1
|
|
305
|
+
const phase1 = (t / PERIOD + 1 / RINGS - PHASE_OFFSET + 1) % 1
|
|
306
|
+
const phase2 = (t / PERIOD + 2 / RINGS - PHASE_OFFSET + 1) % 1
|
|
307
|
+
const envelope0 = Math.sin(phase0 * Math.PI)
|
|
308
|
+
const envelope1 = Math.sin(phase1 * Math.PI)
|
|
309
|
+
const envelope2 = Math.sin(phase2 * Math.PI)
|
|
310
|
+
const eased0 = envelope0 * envelope0 * (3 - 2 * envelope0)
|
|
311
|
+
const eased1 = envelope1 * envelope1 * (3 - 2 * envelope1)
|
|
312
|
+
const eased2 = envelope2 * envelope2 * (3 - 2 * envelope2)
|
|
313
|
+
const head0 = phase0 * this.reach
|
|
314
|
+
const head1 = phase1 * this.reach
|
|
315
|
+
const head2 = phase2 * this.reach
|
|
316
|
+
|
|
317
|
+
for (let index = 0; index < distances.length; index++) {
|
|
318
|
+
const dist = distances[index]
|
|
319
|
+
const delta0 = dist - head0
|
|
320
|
+
const abs0 = delta0 < 0 ? -delta0 : delta0
|
|
321
|
+
const crest0 = abs0 < WIDTH ? 0.5 + 0.5 * Math.cos((delta0 / WIDTH) * Math.PI) : 0
|
|
322
|
+
const tail0 = delta0 < 0 && delta0 > -TAIL ? (1 + delta0 * TAIL_SCALE) ** 2.3 : 0
|
|
323
|
+
|
|
324
|
+
const delta1 = dist - head1
|
|
325
|
+
const abs1 = delta1 < 0 ? -delta1 : delta1
|
|
326
|
+
const crest1 = abs1 < WIDTH ? 0.5 + 0.5 * Math.cos((delta1 / WIDTH) * Math.PI) : 0
|
|
327
|
+
const tail1 = delta1 < 0 && delta1 > -TAIL ? (1 + delta1 * TAIL_SCALE) ** 2.3 : 0
|
|
328
|
+
|
|
329
|
+
const delta2 = dist - head2
|
|
330
|
+
const abs2 = delta2 < 0 ? -delta2 : delta2
|
|
331
|
+
const crest2 = abs2 < WIDTH ? 0.5 + 0.5 * Math.cos((delta2 / WIDTH) * Math.PI) : 0
|
|
332
|
+
const tail2 = delta2 < 0 && delta2 > -TAIL ? (1 + delta2 * TAIL_SCALE) ** 2.3 : 0
|
|
333
|
+
|
|
334
|
+
const level =
|
|
335
|
+
(crest0 * AMP + tail0 * TAIL_AMP) * eased0 +
|
|
336
|
+
(crest1 * AMP + tail1 * TAIL_AMP) * eased1 +
|
|
337
|
+
(crest2 * AMP + tail2 * TAIL_AMP) * eased2
|
|
338
|
+
const rawStrength = (level * RING_SCALE + breath) * edgeFalloff[index]
|
|
339
|
+
const strength = (rawStrength > 1 ? 1 : rawStrength) * 0.7
|
|
340
|
+
const offset = index * 4
|
|
341
|
+
const r = Math.round(baseR + deltaR * strength)
|
|
342
|
+
const g = Math.round(baseG + deltaG * strength)
|
|
343
|
+
const b = Math.round(baseB + deltaB * strength)
|
|
344
|
+
bg[offset] = fg[offset] = r
|
|
345
|
+
bg[offset + 1] = fg[offset + 1] = g
|
|
346
|
+
bg[offset + 2] = fg[offset + 2] = b
|
|
347
|
+
bg[offset + 3] = fg[offset + 3] = 255
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private setLogoPulse(dist: number, head0: number, eased0: number, head1: number, eased1: number) {
|
|
352
|
+
let peak = 0.04
|
|
353
|
+
let primary = 0
|
|
354
|
+
|
|
355
|
+
const delta0 = dist - head0
|
|
356
|
+
const core0 = Math.exp(-(Math.abs(delta0 / 1.2) ** 1.8))
|
|
357
|
+
const soft0 = Math.exp(-(Math.abs(delta0 / 7) ** 1.6))
|
|
358
|
+
const tail0 = delta0 < 0 && delta0 > -7 ? (1 + delta0 / 7) ** 2.6 : 0
|
|
359
|
+
peak += core0 * 0.65 * eased0
|
|
360
|
+
primary += (soft0 * 0.16 + tail0 * 0.22) * eased0
|
|
361
|
+
|
|
362
|
+
const delta1 = dist - head1
|
|
363
|
+
const core1 = Math.exp(-(Math.abs(delta1 / 1.2) ** 1.8))
|
|
364
|
+
const soft1 = Math.exp(-(Math.abs(delta1 / 7) ** 1.6))
|
|
365
|
+
const tail1 = delta1 < 0 && delta1 > -7 ? (1 + delta1 / 7) ** 2.6 : 0
|
|
366
|
+
peak += core1 * 0.65 * eased1
|
|
367
|
+
primary += (soft1 * 0.16 + tail1 * 0.22) * eased1
|
|
368
|
+
|
|
369
|
+
this.pulsePeak = peak > 1 ? 1 : peak
|
|
370
|
+
this.pulsePrimary = primary > 1 ? 1 : primary
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private drawLogo(frameBuffer: OptimizedBuffer, t: number, rgb: boolean) {
|
|
374
|
+
if (this.logoIndexes.length === 0) return
|
|
375
|
+
|
|
376
|
+
const buffers = frameBuffer.buffers
|
|
377
|
+
const fg = buffers.fg
|
|
378
|
+
const bg = buffers.bg
|
|
379
|
+
const shadow: Rgb = [
|
|
380
|
+
mixChannel(this.panelRgb[0], this.logoBaseRgb[0], 0.25),
|
|
381
|
+
mixChannel(this.panelRgb[1], this.logoBaseRgb[1], 0.25),
|
|
382
|
+
mixChannel(this.panelRgb[2], this.logoBaseRgb[2], 0.25),
|
|
383
|
+
]
|
|
384
|
+
const phase0 = (t / PERIOD) % 1
|
|
385
|
+
const phase1 = (t / PERIOD + 0.5) % 1
|
|
386
|
+
const envelope0 = Math.sin(phase0 * Math.PI)
|
|
387
|
+
const envelope1 = Math.sin(phase1 * Math.PI)
|
|
388
|
+
const eased0 = envelope0 * envelope0 * (3 - 2 * envelope0)
|
|
389
|
+
const eased1 = envelope1 * envelope1 * (3 - 2 * envelope1)
|
|
390
|
+
const head0 = phase0 * LOGO_REACH
|
|
391
|
+
const head1 = phase1 * LOGO_REACH
|
|
392
|
+
|
|
393
|
+
for (let i = 0; i < LOGO_TEMPLATE.length; i++) {
|
|
394
|
+
const cell = LOGO_TEMPLATE[i]!
|
|
395
|
+
const index = this.logoIndexes[i]!
|
|
396
|
+
const offset = index * 4
|
|
397
|
+
this.setLogoPulse(cell.topDist, head0, eased0, head1, eased1)
|
|
398
|
+
const topPeak = this.pulsePeak
|
|
399
|
+
const topPrimary = this.pulsePrimary
|
|
400
|
+
this.setLogoPulse(cell.bottomDist, head0, eased0, head1, eased1)
|
|
401
|
+
const bottomPeak = this.pulsePeak
|
|
402
|
+
const bottomPrimary = this.pulsePrimary
|
|
403
|
+
|
|
404
|
+
if (cell.kind === LogoCellKind.Background) {
|
|
405
|
+
writeLogoTint(bg, offset, shadow, this.primaryRgb, 0, Math.max(topPeak, bottomPeak) * 0.18)
|
|
406
|
+
continue
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (cell.kind === LogoCellKind.Top) {
|
|
410
|
+
writeLogoTint(fg, offset, this.logoBaseRgb, this.primaryRgb, topPrimary, topPeak)
|
|
411
|
+
writeLogoTint(bg, offset, shadow, this.primaryRgb, 0, bottomPeak * 0.18)
|
|
412
|
+
continue
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (cell.kind === LogoCellKind.ShadowTop) {
|
|
416
|
+
writeLogoTint(fg, offset, shadow, this.primaryRgb, 0, topPeak * 0.18)
|
|
417
|
+
continue
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (cell.kind === LogoCellKind.Solid && rgb) {
|
|
421
|
+
writeLogoTint(fg, offset, this.logoBaseRgb, this.primaryRgb, topPrimary, topPeak)
|
|
422
|
+
writeLogoTint(bg, offset, this.logoBaseRgb, this.primaryRgb, bottomPrimary, bottomPeak)
|
|
423
|
+
continue
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
writeLogoTint(
|
|
427
|
+
fg,
|
|
428
|
+
offset,
|
|
429
|
+
this.logoBaseRgb,
|
|
430
|
+
this.primaryRgb,
|
|
431
|
+
(topPrimary + bottomPrimary) / 2,
|
|
432
|
+
(topPeak + bottomPeak) / 2,
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FrameBufferRenderable,
|
|
3
|
+
RGBA,
|
|
4
|
+
type OptimizedBuffer,
|
|
5
|
+
type RenderContext,
|
|
6
|
+
type RenderableOptions,
|
|
7
|
+
} from "@opentui/core"
|
|
8
|
+
import { extend, useRenderer } from "@opentui/solid"
|
|
9
|
+
import { onCleanup, onMount } from "solid-js"
|
|
10
|
+
import { tint, useTheme } from "@tui/context/theme"
|
|
11
|
+
import { GoUpsellArtPainter } from "./bg-pulse-render"
|
|
12
|
+
|
|
13
|
+
type GoUpsellArtOptions = RenderableOptions<FrameBufferRenderable> & {
|
|
14
|
+
backgroundPanel?: RGBA
|
|
15
|
+
primary?: RGBA
|
|
16
|
+
logoBase?: RGBA
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class GoUpsellArtRenderable extends FrameBufferRenderable {
|
|
20
|
+
private painter = new GoUpsellArtPainter()
|
|
21
|
+
|
|
22
|
+
constructor(ctx: RenderContext, options: GoUpsellArtOptions = {}) {
|
|
23
|
+
const width = typeof options.width === "number" ? options.width : 1
|
|
24
|
+
const height = typeof options.height === "number" ? options.height : 1
|
|
25
|
+
super(ctx, {
|
|
26
|
+
...options,
|
|
27
|
+
width,
|
|
28
|
+
height,
|
|
29
|
+
live: options.live ?? true,
|
|
30
|
+
respectAlpha: false,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
if (options.width !== undefined && typeof options.width !== "number") this.width = options.width
|
|
34
|
+
if (options.height !== undefined && typeof options.height !== "number") this.height = options.height
|
|
35
|
+
this.painter.setBackgroundPanel(options.backgroundPanel)
|
|
36
|
+
this.painter.setPrimary(options.primary)
|
|
37
|
+
this.painter.setLogoBase(options.logoBase)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
set backgroundPanel(value: RGBA | undefined) {
|
|
41
|
+
if (this.painter.setBackgroundPanel(value)) this.requestRender()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
set logoBase(value: RGBA | undefined) {
|
|
45
|
+
if (this.painter.setLogoBase(value)) this.requestRender()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
set primary(value: RGBA | undefined) {
|
|
49
|
+
if (this.painter.setPrimary(value)) this.requestRender()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected override renderSelf(buffer: OptimizedBuffer, deltaTime = 0): void {
|
|
53
|
+
if (!this.visible || this.isDestroyed) return
|
|
54
|
+
|
|
55
|
+
this.painter.render(this.frameBuffer, {
|
|
56
|
+
deltaTime,
|
|
57
|
+
rgb: this._ctx.capabilities?.rgb === true,
|
|
58
|
+
})
|
|
59
|
+
super.renderSelf(buffer)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
declare module "@opentui/solid" {
|
|
64
|
+
interface OpenTUIComponents {
|
|
65
|
+
go_upsell_art: typeof GoUpsellArtRenderable
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
extend({ go_upsell_art: GoUpsellArtRenderable })
|
|
70
|
+
|
|
71
|
+
export function BgPulse() {
|
|
72
|
+
const { theme } = useTheme()
|
|
73
|
+
const renderer = useRenderer()
|
|
74
|
+
let targetFps = renderer.targetFps
|
|
75
|
+
let maxFps = renderer.maxFps
|
|
76
|
+
|
|
77
|
+
onMount(() => {
|
|
78
|
+
targetFps = renderer.targetFps
|
|
79
|
+
maxFps = renderer.maxFps
|
|
80
|
+
renderer.targetFps = 30
|
|
81
|
+
renderer.maxFps = 30
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
onCleanup(() => {
|
|
85
|
+
renderer.targetFps = targetFps
|
|
86
|
+
renderer.maxFps = maxFps
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<go_upsell_art
|
|
91
|
+
width="100%"
|
|
92
|
+
height="100%"
|
|
93
|
+
backgroundPanel={theme.backgroundPanel}
|
|
94
|
+
primary={theme.primary}
|
|
95
|
+
logoBase={tint(theme.background, theme.text, 0.62)}
|
|
96
|
+
live
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const EmptyBorder = {
|
|
2
|
+
topLeft: "",
|
|
3
|
+
bottomLeft: "",
|
|
4
|
+
vertical: "",
|
|
5
|
+
topRight: "",
|
|
6
|
+
bottomRight: "",
|
|
7
|
+
horizontal: " ",
|
|
8
|
+
bottomT: "",
|
|
9
|
+
topT: "",
|
|
10
|
+
cross: "",
|
|
11
|
+
leftT: "",
|
|
12
|
+
rightT: "",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const SplitBorder = {
|
|
16
|
+
border: ["left" as const, "right" as const],
|
|
17
|
+
customBorderChars: {
|
|
18
|
+
...EmptyBorder,
|
|
19
|
+
vertical: "┃",
|
|
20
|
+
},
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createMemo } from "solid-js"
|
|
2
|
+
import { useLocal } from "@tui/context/local"
|
|
3
|
+
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
4
|
+
import { useDialog } from "@tui/ui/dialog"
|
|
5
|
+
|
|
6
|
+
export function DialogAgent() {
|
|
7
|
+
const local = useLocal()
|
|
8
|
+
const dialog = useDialog()
|
|
9
|
+
|
|
10
|
+
const options = createMemo(() =>
|
|
11
|
+
local.agent.list().map((item) => {
|
|
12
|
+
return {
|
|
13
|
+
value: item.name,
|
|
14
|
+
title: item.name,
|
|
15
|
+
description: item.native ? "native" : item.description,
|
|
16
|
+
}
|
|
17
|
+
}),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<DialogSelect
|
|
22
|
+
title="Select agent"
|
|
23
|
+
current={local.agent.current()?.name}
|
|
24
|
+
options={options()}
|
|
25
|
+
onSelect={(option) => {
|
|
26
|
+
local.agent.set(option.value)
|
|
27
|
+
dialog.clear()
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}
|