Working messages display

This commit is contained in:
Shantur Rathore
2025-10-22 22:10:51 +01:00
commit fa77b4e82e
53 changed files with 9336 additions and 0 deletions

247
src/App.tsx Normal file
View File

@@ -0,0 +1,247 @@
import { Component, onMount, Show, createMemo, createEffect } from "solid-js"
import type { Session } from "./types/session"
import EmptyState from "./components/empty-state"
import SessionPicker from "./components/session-picker"
import InstanceTabs from "./components/instance-tabs"
import SessionTabs from "./components/session-tabs"
import MessageStream from "./components/message-stream"
import {
hasInstances,
isSelectingFolder,
setIsSelectingFolder,
setHasInstances,
sessionPickerInstance,
hideSessionPicker,
showSessionPicker,
} from "./stores/ui"
import {
createInstance,
instances,
updateInstance,
activeInstanceId,
setActiveInstanceId,
stopInstance,
getActiveInstance,
} from "./stores/instances"
import {
getSessions,
activeSessionId,
setActiveSession,
setActiveParentSession,
clearActiveParentSession,
createSession,
deleteSession,
getSessionFamily,
activeParentSessionId,
getParentSessions,
loadMessages,
} from "./stores/sessions"
import { setupTabKeyboardShortcuts } from "./lib/keyboard"
const SessionMessages: Component<{
sessionId: string
activeSessions: Map<string, Session>
instanceId: string
}> = (props) => {
const session = () => props.activeSessions.get(props.sessionId)
createEffect(() => {
const currentSession = session()
if (currentSession) {
loadMessages(props.instanceId, currentSession.id).catch(console.error)
}
})
return (
<Show
when={session()}
fallback={
<div class="flex items-center justify-center h-full">
<div class="text-center text-gray-500">Session not found</div>
</div>
}
>
{(s) => <MessageStream sessionId={s().id} messages={s().messages || []} messagesInfo={s().messagesInfo} />}
</Show>
)
}
const App: Component = () => {
const activeInstance = createMemo(() => getActiveInstance())
const activeSessions = createMemo(() => {
const instance = activeInstance()
if (!instance) return new Map()
const instanceId = instance.id
const parentId = activeParentSessionId().get(instanceId)
if (!parentId) return new Map()
const sessionFamily = getSessionFamily(instanceId, parentId)
return new Map(sessionFamily.map((s) => [s.id, s]))
})
const activeSessionIdForInstance = createMemo(() => {
const instance = activeInstance()
if (!instance) return null
return activeSessionId().get(instance.id) || null
})
async function handleSelectFolder() {
setIsSelectingFolder(true)
try {
const folder = await window.electronAPI.selectFolder()
if (!folder) {
return
}
const instanceId = await createInstance(folder)
setHasInstances(true)
console.log("Created instance:", instanceId, "Port:", instances().get(instanceId)?.port)
} catch (error) {
console.error("Failed to create instance:", error)
} finally {
setIsSelectingFolder(false)
}
}
async function handleCloseInstance(instanceId: string) {
if (confirm("Stop OpenCode instance? This will stop the server.")) {
await stopInstance(instanceId)
if (instances().size === 0) {
setHasInstances(false)
}
}
}
async function handleNewSession(instanceId: string) {
try {
const session = await createSession(instanceId)
setActiveParentSession(instanceId, session.id)
} catch (error) {
console.error("Failed to create session:", error)
}
}
async function handleCloseSession(instanceId: string, sessionId: string) {
const sessions = getSessions(instanceId)
const session = sessions.find((s) => s.id === sessionId)
const isParent = session?.parentId === null
if (!isParent) {
return
}
clearActiveParentSession(instanceId)
showSessionPicker(instanceId)
}
onMount(() => {
setupTabKeyboardShortcuts(handleSelectFolder, handleNewSession, handleCloseSession)
window.electronAPI.onNewInstance(() => {
handleSelectFolder()
})
window.electronAPI.onInstanceStarted(({ id, port, pid }) => {
console.log("Instance started:", { id, port, pid })
updateInstance(id, { port, pid, status: "ready" })
})
window.electronAPI.onInstanceError(({ id, error }) => {
console.error("Instance error:", { id, error })
updateInstance(id, { status: "error", error })
})
window.electronAPI.onInstanceStopped(({ id }) => {
console.log("Instance stopped:", id)
updateInstance(id, { status: "stopped" })
})
})
return (
<div class="h-screen w-screen flex flex-col">
<Show
when={!hasInstances()}
fallback={
<>
<InstanceTabs
instances={instances()}
activeInstanceId={activeInstanceId()}
onSelect={setActiveInstanceId}
onClose={handleCloseInstance}
onNew={handleSelectFolder}
/>
<Show when={activeInstance()}>
{(instance) => (
<>
<Show
when={activeSessions().size > 0}
fallback={
<div class="flex-1 flex items-center justify-center">
<div class="text-center text-gray-500">
<p class="mb-2">No parent session selected</p>
<p class="text-sm">Select or create a parent session to begin</p>
</div>
</div>
}
>
<SessionTabs
instanceId={instance().id}
sessions={activeSessions()}
activeSessionId={activeSessionIdForInstance()}
onSelect={(id) => setActiveSession(instance().id, id)}
onClose={(id) => handleCloseSession(instance().id, id)}
onNew={() => handleNewSession(instance().id)}
/>
<div class="content-area flex-1 overflow-hidden flex flex-col">
<Show
when={activeSessionIdForInstance() === "logs"}
fallback={
<Show
when={activeSessionIdForInstance()}
fallback={
<div class="flex items-center justify-center h-full">
<div class="text-center text-gray-500">
<p class="mb-2">No session selected</p>
<p class="text-sm">Select a session to view messages</p>
</div>
</div>
}
>
<SessionMessages
sessionId={activeSessionIdForInstance()!}
activeSessions={activeSessions()}
instanceId={activeInstance()!.id}
/>
</Show>
}
>
<div class="p-4 text-gray-600">
<p class="font-semibold mb-2">Server Logs</p>
<p class="text-sm">Log viewer will be implemented in Task 013</p>
</div>
</Show>
</div>
</Show>
</>
)}
</Show>
</>
}
>
<EmptyState onSelectFolder={handleSelectFolder} isLoading={isSelectingFolder()} />
</Show>
<Show when={sessionPickerInstance()}>
{(instanceId) => <SessionPicker instanceId={instanceId()} open={true} onClose={hideSessionPicker} />}
</Show>
</div>
)
}
export default App