diff --git a/src/components/ChatPane.tsx b/src/components/ChatPane.tsx
index 2eaecb9..d50e74d 100644
--- a/src/components/ChatPane.tsx
+++ b/src/components/ChatPane.tsx
@@ -222,6 +222,7 @@ export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, b
{cfg.backend ?? session.backend}
{truncate(cfg.model ?? session.model, 24)}
+ {session.cwd}
{cfg.agent && cfg.agent !== 'default' && (
{cfg.agent}
)}
diff --git a/src/components/PromptBar.tsx b/src/components/PromptBar.tsx
index 75844c5..b0f58d0 100644
--- a/src/components/PromptBar.tsx
+++ b/src/components/PromptBar.tsx
@@ -10,7 +10,6 @@ export function PromptBar({ running, onSend, onStop }: Props) {
const [value, setValue] = useState('')
const ref = useRef(null)
- useEffect(() => { ref.current?.focus() })
const submit = () => {
const text = value.trim()
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 4fdc73c..27d7438 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -1,7 +1,6 @@
import { useState } from 'preact/hooks'
import type { IdxEntry } from '../lib/api'
import type { Theme } from '../lib/theme'
-import { truncate } from '../lib/chat'
interface Props {
sessions: IdxEntry[]
@@ -18,12 +17,44 @@ interface Props {
function stateClass(state: string) {
if (state === 'idle') return 'idle'
if (state === 'stopped') return 'stopped'
- return 'running' // thinking, calling: , etc.
+ return 'running'
+}
+
+interface SessionNode {
+ session: IdxEntry | null
+ children: Map
+}
+
+function buildTree(sessions: IdxEntry[]): SessionNode {
+ const root: SessionNode = { session: null, children: new Map() }
+ for (const s of sessions) {
+ const parts = s.id.split('__')
+ let node = root
+ for (let i = 0; i < parts.length - 1; i++) {
+ const key = parts.slice(0, i + 1).join('__')
+ if (!node.children.has(key)) {
+ node.children.set(key, { session: null, children: new Map() })
+ }
+ node = node.children.get(key)!
+ }
+ if (node.children.has(s.id)) {
+ node.children.get(s.id)!.session = s
+ } else {
+ node.children.set(s.id, { session: s, children: new Map() })
+ }
+ }
+ return root
+}
+
+function leafName(id: string): string {
+ const parts = id.split('__')
+ return parts[parts.length - 1]
}
export function Sidebar({ sessions, selectedId, onSelect, onNew, onKill, onRename, themes, currentTheme, onThemeChange }: Props) {
const [editingId, setEditingId] = useState(null)
const [editValue, setEditValue] = useState('')
+ const [collapsed, setCollapsed] = useState>(new Set())
const startRename = (e: MouseEvent, id: string) => {
e.stopPropagation()
@@ -36,6 +67,70 @@ export function Sidebar({ sessions, selectedId, onSelect, onNew, onKill, onRenam
setEditingId(null)
if (name && name !== oldId) onRename(oldId, name)
}
+
+ const toggle = (id: string, e: MouseEvent) => {
+ e.stopPropagation()
+ setCollapsed(prev => {
+ const next = new Set(prev)
+ if (next.has(id)) next.delete(id); else next.add(id)
+ return next
+ })
+ }
+
+ const tree = buildTree(sessions)
+
+ function renderNode(node: SessionNode, depth: number): any {
+ const items: any[] = []
+ for (const [id, child] of node.children) {
+ const s = child.session
+ const hasChildren = child.children.size > 0
+ const isCollapsed = collapsed.has(id)
+
+ if (s) {
+ items.push(
+
+
onSelect(s.id === selectedId ? null : s.id)}
+ >
+ {hasChildren && (
+ toggle(id, e)}>▾
+ )}
+
+ {editingId === s.id ? (
+ setEditValue((e.target as HTMLInputElement).value)}
+ onBlur={() => commitRename(s.id)}
+ onKeyDown={e => { if (e.key === 'Enter') commitRename(s.id); if (e.key === 'Escape') setEditingId(null) }}
+ onClick={e => e.stopPropagation()}
+ autoFocus
+ />
+ ) : (
+ startRename(e, s.id)}>{leafName(s.id)}
+ )}
+ {s.state}
+
+
+ {hasChildren && !isCollapsed && renderNode(child, depth + 1)}
+
+ )
+ } else if (hasChildren) {
+ items.push(
+
+ {renderNode(child, depth + 1)}
+
+ )
+ }
+ }
+ return items
+ }
+
return (