-
Notifications
You must be signed in to change notification settings - Fork 0
[FIX] Hide ProgressBar when ingest finishes #132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9e7e40d
19d2456
b24d980
8225726
c310f43
90755fd
554205a
58a42a9
03329c6
c885806
7a77751
8b8a3e4
a6bae9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,8 @@ import { createHash } from "crypto"; | |||||||||||||||||||
| import { homedir } from "os"; | ||||||||||||||||||||
| import { join } from "path"; | ||||||||||||||||||||
| import type { PluginInput } from "@opencode-ai/plugin"; | ||||||||||||||||||||
| import { brainBus, type BrainStatusEvent } from "./event-bus"; | ||||||||||||||||||||
| import { BusClient } from "@four-bytes/opencode-plugin-lib"; | ||||||||||||||||||||
| import type { BrainStatusEvent } from "./event-bus"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export type StatusState = "busy" | "success" | "warning" | "error" | "ready"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
@@ -23,22 +24,41 @@ export interface StatusOpts { | |||||||||||||||||||
| const _state = { current: {} as Record<string, unknown> }; | ||||||||||||||||||||
| _state.current = { status: "init", statusText: "", version: "" }; | ||||||||||||||||||||
| let _version = ""; | ||||||||||||||||||||
| let _sessionId = ""; | ||||||||||||||||||||
| let _channel = "brain/status"; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let _client: PluginInput["client"] | null = null; | ||||||||||||||||||||
| let _server: ReturnType<typeof Bun.serve> | null = null; | ||||||||||||||||||||
| let _port = 0; | ||||||||||||||||||||
| let _busPromise: Promise<BusClient> | null = null; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** Initialize with client for toast support */ | ||||||||||||||||||||
| export function initVersion(v: string): void { | ||||||||||||||||||||
| _version = v; | ||||||||||||||||||||
| write({ status: "init", statusText: "initializing..." }); | ||||||||||||||||||||
| write({ status: "init", statusText: "initializing…" }); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export function setSessionId(id: string): void { | ||||||||||||||||||||
| if (id === _sessionId) return; | ||||||||||||||||||||
| _sessionId = id; | ||||||||||||||||||||
| _channel = `brain/${id}`; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export function initStatus(client: PluginInput["client"], directory: string): void { | ||||||||||||||||||||
| _client = client; | ||||||||||||||||||||
| startStatusServer(directory); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| function getBus(): Promise<BusClient> { | ||||||||||||||||||||
| if (!_busPromise) { | ||||||||||||||||||||
| _busPromise = BusClient.connect().catch((err) => { | ||||||||||||||||||||
| console.warn("[brain] BusClient connect failed:", (err as Error).message); | ||||||||||||||||||||
| throw err; | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
Comment on lines
+54
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: getBus permanently caches a rejected BusClient connection. One transient connect failure disables all future bus publishes until process restart. Prompt for AI agents
Suggested change
|
||||||||||||||||||||
| } | ||||||||||||||||||||
| return _busPromise; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export function startStatusServer(directory: string): void { | ||||||||||||||||||||
| if (_server) return; | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
@@ -84,7 +104,14 @@ export function stopStatusServer(): void { | |||||||||||||||||||
|
|
||||||||||||||||||||
| function write(data: Record<string, unknown>): void { | ||||||||||||||||||||
| _state.current = { ..._state.current, ...data }; | ||||||||||||||||||||
| brainBus.emit("status", { ..._state.current, version: _version } as BrainStatusEvent); | ||||||||||||||||||||
| const payload = { ..._state.current, version: _version } as BrainStatusEvent; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Real-time push via plugin bus (HTTP fallback still serves status endpoint) | ||||||||||||||||||||
| getBus() | ||||||||||||||||||||
| .then((bus) => bus.publish(_channel, payload)) | ||||||||||||||||||||
| .catch((err) => { | ||||||||||||||||||||
| console.warn("[brain] Bus publish failed:", (err as Error).message); | ||||||||||||||||||||
| }); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,15 +3,17 @@ | |
| import { createSignal, onMount, onCleanup } from "solid-js"; | ||
| import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"; | ||
| import type { RGBA } from "@opentui/core"; | ||
| import { brainBus, type BrainStatusEvent } from "./event-bus"; | ||
| import { BusTui } from "@four-bytes/opencode-plugin-lib/tui"; | ||
| import { ProgressBar } from "@four-bytes/opencode-plugin-lib/tui-components"; | ||
| import type { BrainStatusEvent } from "./event-bus"; | ||
| import { Spinner } from "./spinner"; | ||
| import { createHash } from "crypto"; | ||
| import { homedir } from "os"; | ||
| import { join } from "path"; | ||
|
|
||
| function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi }) { | ||
| function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi; sessionId?: string }) { | ||
| const [indicator, setIndicator] = createSignal("•"); | ||
| const [status, setStatus] = createSignal("connecting..."); | ||
| const [status, setStatus] = createSignal("connecting…"); | ||
| const [version, setVersion] = createSignal(""); | ||
| const [current, setCurrent] = createSignal(0); | ||
| const [total, setTotal] = createSignal(0); | ||
|
|
@@ -37,15 +39,19 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi }) { | |
| setFg(theme().error); | ||
| } else if (data.status === "init") { | ||
| setBusy(true); | ||
| setStatus(data.statusText ?? "initializing..."); | ||
| setStatus(data.statusText ?? "initializing…"); | ||
| setCurrent(0); | ||
| setTotal(0); | ||
| setFg(theme().warning); | ||
| } else if (data.status === "busy") { | ||
| setBusy(true); | ||
| setCurrent(data.current ?? 0); | ||
| setTotal(data.total ?? 0); | ||
| setStatus(data.statusText ?? "working"); | ||
| setStatus(data.statusText ?? "working…"); | ||
| setFg(pulse % 2 === 0 ? theme().warning : theme().accent); | ||
| } else { | ||
| setCurrent(0); | ||
| setTotal(0); | ||
| setBusy(false); | ||
| setIndicator("•"); | ||
| setStatus("ready"); | ||
|
|
@@ -60,9 +66,30 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi }) { | |
| }; | ||
|
|
||
| onMount(() => { | ||
| const unsub = brainBus.on("status", handleStatus); | ||
| let bus: BusTui | null = null; | ||
| let unsub: (() => void) | null = null; | ||
| let timer: ReturnType<typeof setInterval> | null = null; | ||
|
|
||
| // Resolve port from discovery file, then poll HTTP endpoint | ||
| onCleanup(() => { | ||
| unsub?.(); | ||
| bus?.close(); | ||
| if (timer) clearInterval(timer); | ||
| }); | ||
|
|
||
| // Real-time WebSocket subscription via plugin bus | ||
| BusTui.connect() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Async bus subscription can leak when component unmounts before Prompt for AI agents |
||
| .then((b) => { | ||
| bus = b; | ||
| const channel = `brain/${props.sessionId || "unknown"}`; | ||
| unsub = b.subscribe(channel, (envelope) => { | ||
| handleStatus(envelope.payload as BrainStatusEvent); | ||
| }); | ||
| }) | ||
| .catch((err) => { | ||
| console.warn("[brain TUI] BusTui connect failed:", (err as Error).message); | ||
| }); | ||
|
|
||
| // HTTP fallback for when bus is unavailable (cross-process) | ||
| let statusUrl = ""; | ||
| const hash = createHash("md5").update(props.api.state.path.directory).digest("hex").slice(0, 12); | ||
| const portFile = join(homedir(), ".cache", "opencode", "brain", `status-port-${hash}.json`); | ||
|
|
@@ -92,15 +119,20 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi }) { | |
| }; | ||
|
|
||
| poll(); | ||
| const timer = setInterval(poll, 200); | ||
| onCleanup(() => { unsub(); clearInterval(timer); }); | ||
| timer = setInterval(poll, 200); | ||
| }); | ||
|
|
||
| const StatusRow = () => ( | ||
| <box flexDirection="row"> | ||
| <text fg={theme().textMuted}>🧠 {version()} </text> | ||
| {busy() ? <Spinner fg={fg()} /> : <text fg={connecting() ? theme().error : fg()}>{indicator()}</text>} | ||
| <text fg={connecting() ? theme().error : theme().textMuted}> {connecting() ? "connecting..." : status()}</text> | ||
| <text fg={connecting() ? theme().error : theme().textMuted}> {connecting() ? "connecting…" : status()}</text> | ||
| {current() > 0 && total() > 0 && ( | ||
| <text> </text> | ||
| )} | ||
| {current() > 0 && total() > 0 && ( | ||
| <ProgressBar current={current()} total={total()} showLabel={true} fillBg="#aaa" fillFg="#000" /> | ||
| )} | ||
| </box> | ||
| ); | ||
|
|
||
|
|
@@ -123,7 +155,13 @@ function BrainStatusBar(props: { centered?: boolean; api: TuiPluginApi }) { | |
| </box> | ||
| <box flexDirection="row"> | ||
| {busy() ? <Spinner fg={fg()} /> : <text fg={connecting() ? theme().error : fg()}>{indicator()}</text>} | ||
| <text fg={connecting() ? theme().error : theme().textMuted}> {connecting() ? "connecting..." : status()}</text> | ||
| <text fg={connecting() ? theme().error : theme().textMuted}> {connecting() ? "connecting…" : status()}</text> | ||
| {current() > 0 && total() > 0 && ( | ||
| <text> </text> | ||
| )} | ||
| {current() > 0 && total() > 0 && ( | ||
| <ProgressBar current={current()} total={total()} showLabel={true} fillBg="#aaa" fillFg="#000" /> | ||
| )} | ||
| </box> | ||
| </box> | ||
| )} | ||
|
|
@@ -137,7 +175,7 @@ const tui: TuiPlugin = (api) => { | |
| api.slots.register({ | ||
| order: 60, // below deepseek-meter (55) | ||
| slots: { | ||
| sidebar_content: () => <BrainStatusBar api={api} />, | ||
| sidebar_content: (_ctx: any, props: any) => <BrainStatusBar api={api} sessionId={props.session_id} />, | ||
| home_bottom: () => <BrainStatusBar api={api} centered />, | ||
| }, | ||
| }); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Global status channel is overwritten per latest chat message, so concurrent sessions can receive each other’s status updates. This causes incorrect progress/status display across sessions.
Prompt for AI agents