import { useSync } from "@tui/context/sync" import { createMemo, For, Show, Switch, Match } from "solid-js" import { createStore } from "solid-js/store" import { useTheme } from "../../context/theme" import { Locale } from "@/util/locale" import path from "path" import type { AssistantMessage } from "@opencode-ai/sdk" import { Global } from "@/global" import { Installation } from "@/installation" import { useKeybind } from "../../context/keybind" import { useDirectory } from "../../context/directory" export function Sidebar(props: { sessionID: string }) { const sync = useSync() const { theme } = useTheme() const session = createMemo(() => sync.session.get(props.sessionID)!) const diff = createMemo(() => sync.data.session_diff[props.sessionID] ?? []) const todo = createMemo(() => sync.data.todo[props.sessionID] ?? []) const messages = createMemo(() => sync.data.message[props.sessionID] ?? []) const [expanded, setExpanded] = createStore({ mcp: true, diff: true, todo: true, lsp: true, }) // Sort MCP servers alphabetically for consistent display order const mcpEntries = createMemo(() => Object.entries(sync.data.mcp).sort(([a], [b]) => a.localeCompare(b))) const cost = createMemo(() => { const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(total) }) const context = createMemo(() => { const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage if (!last) return const total = last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write const model = sync.data.provider.find((x) => x.id === last.providerID)?.models[last.modelID] return { tokens: total.toLocaleString(), percentage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null, } }) const keybind = useKeybind() const directory = useDirectory() const hasProviders = createMemo(() => sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)), ) return ( {session().title} {session().share!.url} Context {context()?.tokens ?? 0} tokens {context()?.percentage ?? 0}% used {cost()} spent 0}> mcpEntries().length > 2 && setExpanded("mcp", !expanded.mcp)} > 2}> {expanded.mcp ? "▼" : "▶"} MCP {([key, item]) => ( • {key}{" "} Connected {(val) => {val().error}} Disabled in configuration )} sync.data.lsp.length > 2 && setExpanded("lsp", !expanded.lsp)} > 2}> {expanded.lsp ? "▼" : "▶"} LSP LSPs will activate as files are read {(item) => ( • {item.id} {item.root} )} 0 && todo().some((t) => t.status !== "completed")}> todo().length > 2 && setExpanded("todo", !expanded.todo)} > 2}> {expanded.todo ? "▼" : "▶"} Todo {(todo) => ( [{todo.status === "completed" ? "✓" : " "}] {todo.content} )} 0}> diff().length > 2 && setExpanded("diff", !expanded.diff)} > 2}> {expanded.diff ? "▼" : "▶"} Modified Files {(item) => { const file = createMemo(() => { const splits = item.file.split(path.sep).filter(Boolean) const last = splits.at(-1)! const rest = splits.slice(0, -1).join(path.sep) if (!rest) return last return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last }) return ( {file()} +{item.additions} -{item.deletions} ) }} ⬖ Getting started OpenCode includes free models so you can start immediately. Connect from 75+ providers to use other models, including Claude, GPT, Gemini etc Connect provider /connect {directory()} • Open Code {" "} {Installation.VERSION} ) }