diff --git a/index.html b/index.html
index 4808f0b..d508699 100644
--- a/index.html
+++ b/index.html
@@ -7,6 +7,7 @@
+
diff --git a/src/app.tsx b/src/app.tsx
index e353947..9674e59 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'preact/hooks'
import { Sidebar } from './components/Sidebar'
import { ChatPane } from './components/ChatPane'
import { NewSession } from './components/NewSession'
+import { getTheme, getThemes, loadThemeName, saveThemeName, applyTheme } from './lib/theme'
import {
listSessions,
getSessionCfg,
@@ -10,6 +11,7 @@ import {
sendCtl,
createSession,
killSession,
+ browse9p,
} from './lib/api'
import type { IdxEntry } from './lib/api'
import { parseChat } from './lib/chat'
@@ -21,6 +23,42 @@ export function App() {
const [cfg, setCfg] = useState | null>(null)
const [messages, setMessages] = useState([])
const [showNew, setShowNew] = useState(false)
+ const [themeName, setThemeName] = useState(loadThemeName)
+ const [browsePath, setBrowsePath] = useState(null)
+ const [browseData, setBrowseData] = useState(null)
+
+ // Apply theme on mount and when changed.
+ useEffect(() => { applyTheme(getTheme(themeName)) }, [themeName])
+
+ const handleSetTheme = useCallback((name: string) => {
+ saveThemeName(name)
+ setThemeName(name)
+ }, [])
+
+ const handleBrowse = useCallback(async (path: string) => {
+ setSelectedId(null)
+ setBrowsePath(path)
+ if (path === '/mnt/') {
+ // Synthetic root listing
+ setBrowseData([
+ { name: 's', is_dir: true },
+ { name: 'a', is_dir: true },
+ { name: 'm', is_dir: true },
+ { name: 't', is_dir: true },
+ { name: 'sk', is_dir: true },
+ ])
+ } else {
+ const apiPath = path.startsWith('/mnt/') ? '/' + path.slice(5) : path
+ const data = await browse9p(apiPath)
+ setBrowseData(data)
+ }
+ }, [])
+
+ const handleSelect = useCallback((id: string | null) => {
+ setSelectedId(id)
+ setBrowsePath(null)
+ setBrowseData(null)
+ }, [])
// Session list — poll every 5s.
useEffect(() => {
@@ -89,9 +127,12 @@ export function App() {
setShowNew(true)}
onKill={handleKill}
+ themes={getThemes()}
+ currentTheme={themeName}
+ onThemeChange={handleSetTheme}
/>
s.id === selectedId) ?? null}
@@ -99,6 +140,10 @@ export function App() {
messages={messages}
onSend={handleSend}
onStop={handleStop}
+ browsePath={browsePath}
+ browseData={browseData}
+ onBrowse={handleBrowse}
+ onSelectSession={handleSelect}
/>
{showNew && (
setShowNew(false)} onCreate={handleCreate} />
diff --git a/src/components/ChatPane.tsx b/src/components/ChatPane.tsx
index 30c734e..f73eb48 100644
--- a/src/components/ChatPane.tsx
+++ b/src/components/ChatPane.tsx
@@ -13,6 +13,10 @@ interface Props {
messages: ChatMessage[]
onSend: (text: string) => void
onStop: () => void
+ browsePath: string | null
+ browseData: any
+ onBrowse: (path: string) => void
+ onSelectSession: (id: string) => void
}
function stateClass(state: string) {
@@ -21,7 +25,7 @@ function stateClass(state: string) {
return 'running'
}
-export function ChatPane({ session, cfg, messages, onSend, onStop }: Props) {
+export function ChatPane({ session, cfg, messages, onSend, onStop, browsePath, browseData, onBrowse, onSelectSession }: Props) {
const listRef = useRef(null)
const [atBottom, setAtBottom] = useState(true)
@@ -42,7 +46,11 @@ export function ChatPane({ session, cfg, messages, onSend, onStop }: Props) {
if (!session || !cfg) {
return (
-
+ {browsePath && browsePath !== '/mnt/' ? (
+
+ ) : (
+
+ )}
)
}
@@ -54,6 +62,7 @@ export function ChatPane({ session, cfg, messages, onSend, onStop }: Props) {