nanobot-voice-interface/frontend/src/App.tsx
kacper 4dfb7ca3cc
Some checks failed
CI / Backend Checks (push) Failing after 36s
CI / Frontend Checks (push) Failing after 40s
feat: unify card runtime and event-driven web ui
2026-04-06 15:42:53 -04:00

160 lines
4.9 KiB
TypeScript

import { useState } from "preact/hooks";
import {
AgentWorkspace,
FeedWorkspace,
SwipeWorkspace,
useAppPresentation,
useSessionDrawerEdgeSwipe,
} from "./appShell/presentation";
import { VoiceStatus } from "./components/Controls";
import { SessionDrawer } from "./components/SessionDrawer";
import { WorkbenchOverlay } from "./components/WorkbenchOverlay";
import { useWebRTC } from "./hooks/useWebRTC";
import type { ThemeName, ThemeOption } from "./theme/themes";
import { useThemePreference } from "./theme/useThemePreference";
function AgentPanel({
app,
rtc,
sessionDrawerOpen,
setSessionDrawerOpen,
sessionDrawerEdgeGesture,
activeTheme,
themeOptions,
onSelectTheme,
}: {
app: ReturnType<typeof useAppPresentation>;
rtc: ReturnType<typeof useWebRTC>;
sessionDrawerOpen: boolean;
setSessionDrawerOpen(open: boolean): void;
sessionDrawerEdgeGesture: ReturnType<typeof useSessionDrawerEdgeSwipe>;
activeTheme: ThemeName;
themeOptions: ThemeOption[];
onSelectTheme(themeName: ThemeName): void;
}) {
return (
<AgentWorkspace
active={app.view === "agent"}
selectedCard={app.selectedCard}
selectedCardSelection={app.selectedCardSelection}
selectedCardContextLabel={app.selectedCardContextLabel}
onClearSelectedCardContext={app.clearSelectedCardContext}
textOnly={rtc.textOnly}
onToggleTextOnly={app.handleToggleTextOnly}
sessionDrawerEdge={
!sessionDrawerOpen ? (
<div
id="session-drawer-edge"
data-no-swipe="1"
onTouchStart={sessionDrawerEdgeGesture.onTouchStart}
onTouchMove={sessionDrawerEdgeGesture.onTouchMove}
onTouchEnd={sessionDrawerEdgeGesture.onTouchEnd}
onTouchCancel={sessionDrawerEdgeGesture.onTouchCancel}
/>
) : null
}
logLines={rtc.logLines}
connected={rtc.connected}
onSendMessage={app.handleSendMessage}
effectiveAgentState={app.effectiveAgentState}
textStreaming={rtc.textStreaming}
connecting={rtc.connecting}
audioLevel={app.audioLevel}
sessionDrawer={
<SessionDrawer
open={sessionDrawerOpen}
progress={sessionDrawerEdgeGesture.progress}
dragging={sessionDrawerEdgeGesture.progress > 0 && !sessionDrawerOpen}
sessions={rtc.sessions}
activeSessionId={rtc.activeSessionId}
busy={rtc.sessionLoading || rtc.textStreaming}
onClose={() => setSessionDrawerOpen(false)}
onCreate={async () => {
await rtc.createSession();
setSessionDrawerOpen(false);
}}
onSelect={async (chatId) => {
await rtc.switchSession(chatId);
setSessionDrawerOpen(false);
}}
onRename={async (chatId, title) => {
await rtc.renameSession(chatId, title);
}}
onDelete={async (chatId) => {
await rtc.deleteSession(chatId);
setSessionDrawerOpen(false);
}}
activeTheme={activeTheme}
themeOptions={themeOptions}
onSelectTheme={onSelectTheme}
/>
}
workbenchOverlay={
<WorkbenchOverlay
items={rtc.workbenchItems}
onDismiss={rtc.dismissWorkbenchItem}
onPromote={rtc.promoteWorkbenchItem}
/>
}
/>
);
}
function FeedPanel({
app,
rtc,
}: {
app: ReturnType<typeof useAppPresentation>;
rtc: ReturnType<typeof useWebRTC>;
}) {
return (
<FeedWorkspace
cards={rtc.cards}
onDismiss={rtc.dismissCard}
onChoice={app.handleCardChoice}
onAskCard={app.handleAskCard}
/>
);
}
export function App() {
const rtc = useWebRTC();
const app = useAppPresentation(rtc);
const theme = useThemePreference();
const [sessionDrawerOpen, setSessionDrawerOpen] = useState(false);
const sessionDrawerEdgeGesture = useSessionDrawerEdgeSwipe({
enabled: app.view === "agent" && !sessionDrawerOpen,
onOpen: () => setSessionDrawerOpen(true),
});
return (
<>
<SwipeWorkspace
view={app.view}
trackStyle={app.trackStyle}
onSwipeStart={app.onSwipeStart}
onSwipeMove={app.onSwipeMove}
onSwipeEnd={app.onSwipeEnd}
onSwipeCancel={app.onSwipeCancel}
onTouchStart={app.onTouchStart}
onTouchMove={app.onTouchMove}
onTouchEnd={app.onTouchEnd}
onTouchCancel={app.onTouchCancel}
agentWorkspace={
<AgentPanel
app={app}
rtc={rtc}
sessionDrawerOpen={sessionDrawerOpen}
setSessionDrawerOpen={setSessionDrawerOpen}
sessionDrawerEdgeGesture={sessionDrawerEdgeGesture}
activeTheme={theme.themeName}
themeOptions={theme.themeOptions}
onSelectTheme={theme.setThemeName}
/>
}
feedWorkspace={<FeedPanel app={app} rtc={rtc} />}
/>
<VoiceStatus text={rtc.voiceStatus} visible={rtc.statusVisible} />
</>
);
}