feat(sidebar): add expandable sections for sidebar (#4132)

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
OpeOginni
2025-11-12 23:15:17 +01:00
committed by GitHub
parent 90f05eb9c2
commit 4ab4baf3a4

View File

@@ -1,5 +1,5 @@
import { useSync } from "@tui/context/sync" import { useSync } from "@tui/context/sync"
import { createMemo, For, Show, Switch, Match } from "solid-js" import { createMemo, For, Show, Switch, Match, createSignal } from "solid-js"
import { useTheme } from "../../context/theme" import { useTheme } from "../../context/theme"
import { Locale } from "@/util/locale" import { Locale } from "@/util/locale"
import path from "path" import path from "path"
@@ -13,6 +13,11 @@ export function Sidebar(props: { sessionID: string }) {
const todo = createMemo(() => sync.data.todo[props.sessionID] ?? []) const todo = createMemo(() => sync.data.todo[props.sessionID] ?? [])
const messages = createMemo(() => sync.data.message[props.sessionID] ?? []) const messages = createMemo(() => sync.data.message[props.sessionID] ?? [])
const [mcpExpanded, setMcpExpanded] = createSignal(true)
const [diffExpanded, setDiffExpanded] = createSignal(true)
const [todoExpanded, setTodoExpanded] = createSignal(true)
const [lspExpanded, setLspExpanded] = createSignal(true)
const cost = createMemo(() => { const cost = createMemo(() => {
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
return new Intl.NumberFormat("en-US", { return new Intl.NumberFormat("en-US", {
@@ -55,110 +60,130 @@ export function Sidebar(props: { sessionID: string }) {
</box> </box>
<Show when={Object.keys(sync.data.mcp).length > 0}> <Show when={Object.keys(sync.data.mcp).length > 0}>
<box> <box>
<text fg={theme.text}> <box flexDirection="row" gap={1} onMouseDown={() => setMcpExpanded(!mcpExpanded())}>
<b>MCP</b> <text fg={theme.text}>{mcpExpanded() ? "▼" : "▶"}</text>
</text> <text fg={theme.text}>
<For each={Object.entries(sync.data.mcp)}> <b>MCP</b>
{([key, item]) => ( </text>
<box flexDirection="row" gap={1}> </box>
<text <Show when={mcpExpanded()}>
flexShrink={0} <For each={Object.entries(sync.data.mcp)}>
style={{ {([key, item]) => (
fg: { <box flexDirection="row" gap={1}>
connected: theme.success, <text
failed: theme.error, flexShrink={0}
disabled: theme.textMuted, style={{
}[item.status], fg: {
}} connected: theme.success,
> failed: theme.error,
disabled: theme.textMuted,
</text> }[item.status],
<text fg={theme.text} wrapMode="word"> }}
{key}{" "} >
<span style={{ fg: theme.textMuted }}>
<Switch> </text>
<Match when={item.status === "connected"}>Connected</Match> <text fg={theme.text} wrapMode="word">
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match> {key}{" "}
<Match when={item.status === "disabled"}>Disabled in configuration</Match> <span style={{ fg: theme.textMuted }}>
</Switch> <Switch>
</span> <Match when={item.status === "connected"}>Connected</Match>
</text> <Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
</box> <Match when={item.status === "disabled"}>Disabled in configuration</Match>
)} </Switch>
</For> </span>
</text>
</box>
)}
</For>
</Show>
</box> </box>
</Show> </Show>
<Show when={sync.data.lsp.length > 0}> <Show when={sync.data.lsp.length > 0}>
<box> <box>
<text fg={theme.text}> <box flexDirection="row" gap={1} onMouseDown={() => setLspExpanded(!lspExpanded())}>
<b>LSP</b> <text fg={theme.text}>{lspExpanded() ? "▼" : "▶"}</text>
</text> <text fg={theme.text}>
<For each={sync.data.lsp}> <b>LSP</b>
{(item) => ( </text>
<box flexDirection="row" gap={1}> </box>
<text <Show when={lspExpanded()}>
flexShrink={0} <For each={sync.data.lsp}>
style={{ {(item) => (
fg: { <box flexDirection="row" gap={1}>
connected: theme.success, <text
error: theme.error, flexShrink={0}
}[item.status], style={{
}} fg: {
> connected: theme.success,
error: theme.error,
</text> }[item.status],
<text fg={theme.textMuted}> }}
{item.id} {item.root} >
</text>
</box> </text>
)} <text fg={theme.textMuted}>
</For> {item.id} {item.root}
</text>
</box>
)}
</For>
</Show>
</box> </box>
</Show> </Show>
<Show when={todo().length > 0}> <Show when={todo().length > 0}>
<box> <box>
<text fg={theme.text}> <box flexDirection="row" gap={1} onMouseDown={() => setTodoExpanded(!todoExpanded())}>
<b>Todo</b> <text fg={theme.text}>{todoExpanded() ? "▼" : "▶"}</text>
</text> <text fg={theme.text}>
<For each={todo()}> <b>Todo</b>
{(todo) => ( </text>
<text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}> </box>
[{todo.status === "completed" ? "✓" : " "}] {todo.content} <Show when={todoExpanded()}>
</text> <For each={todo()}>
)} {(todo) => (
</For> <text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}>
[{todo.status === "completed" ? "✓" : " "}] {todo.content}
</text>
)}
</For>
</Show>
</box> </box>
</Show> </Show>
<Show when={diff().length > 0}> <Show when={diff().length > 0}>
<box> <box>
<text fg={theme.text}> <box flexDirection="row" gap={1} onMouseDown={() => setDiffExpanded(!diffExpanded())}>
<b>Modified Files</b> <text fg={theme.text}>{diffExpanded() ? "▼" : "▶"}</text>
</text> <text fg={theme.text}>
<For each={diff() || []}> <b>Modified Files</b>
{(item) => { </text>
const file = createMemo(() => { </box>
const splits = item.file.split(path.sep).filter(Boolean) <Show when={diffExpanded()}>
const last = splits.at(-1)! <For each={diff() || []}>
const rest = splits.slice(0, -1).join(path.sep) {(item) => {
return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last const file = createMemo(() => {
}) const splits = item.file.split(path.sep).filter(Boolean)
return ( const last = splits.at(-1)!
<box flexDirection="row" gap={1} justifyContent="space-between"> const rest = splits.slice(0, -1).join(path.sep)
<text fg={theme.textMuted} wrapMode="char"> return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last
{file()} })
</text> return (
<box flexDirection="row" gap={1} flexShrink={0}> <box flexDirection="row" gap={1} justifyContent="space-between">
<Show when={item.additions}> <text fg={theme.textMuted} wrapMode="char">
<text fg={theme.diffAdded}>+{item.additions}</text> {file()}
</Show> </text>
<Show when={item.deletions}> <box flexDirection="row" gap={1} flexShrink={0}>
<text fg={theme.diffRemoved}>-{item.deletions}</text> <Show when={item.additions}>
</Show> <text fg={theme.diffAdded}>+{item.additions}</text>
</Show>
<Show when={item.deletions}>
<text fg={theme.diffRemoved}>-{item.deletions}</text>
</Show>
</box>
</box> </box>
</box> )
) }}
}} </For>
</For> </Show>
</box> </box>
</Show> </Show>
</box> </box>