nandi/oripublic Fork 0
b6cd929ab1425a4ef6073ea9c189917cca02cf3c
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

App.tsx · 86 lines · 2.6 KBTypeScript Blame HistoryRaw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
 * Root component: header with connection/session status and the workspace
 * toggle, the chat column (thread, plan, permissions, composer) and the
 * workspace panel. The WebSocket is injected so tests (and future
 * embeddings) can supply a fake.
 *
 * @example
 * <App socket={connectSocket({ url: wsUrl(), dispatch })} />
 */

import { ChatThread } from "./components/ChatThread";
import { PermissionPrompt } from "./components/PermissionPrompt";
import { PlanView } from "./components/PlanView";
import { PromptInput } from "./components/PromptInput";
import { Workspace } from "./components/Workspace";
import { useChatState } from "./store";
import { toggleTheme, useTheme } from "./theme";
import { toggleWorkspace, useWorkspace } from "./workspace";
import type { OriSocket } from "./ws";

export function App({ socket }: { socket: OriSocket }) {
	const state = useChatState();
	const workspaceOpen = useWorkspace((workspace) => workspace.open);
	const theme = useTheme((state) => state.theme);
	const nextTheme = theme === "dark" ? "light" : "dark";

	return (
		<div className="app">
			<header className="topbar">
				<h1 className="topbar-title">Ori</h1>
				<span className={`status status-${state.connection}`}>
					{state.connection}
					{state.sessionId ? ` · ${state.sessionId}` : ""}
				</span>
				<button
					type="button"
					className="topbar-theme"
					onClick={toggleTheme}
					aria-label={`Switch to ${nextTheme} theme`}
					title={`Switch to ${nextTheme} theme`}
				>
					{theme === "dark" ? "☀" : "☾"}
				</button>
				<button
					type="button"
					className={`topbar-workspace${workspaceOpen ? " topbar-workspace-active" : ""}`}
					onClick={toggleWorkspace}
				>
					 Workspace
				</button>
			</header>
			<div className={`app-body${workspaceOpen ? " with-workspace" : ""}`}>
				<div className="chat-column">
					<ChatThread thread={state.thread} turnActive={state.turnActive} />
					<PlanView entries={state.plan} />
					{state.permissions.map((request) => (
						<PermissionPrompt
							key={request.requestId}
							request={request}
							onRespond={(requestId, optionId) =>
								socket.send({
									type: "permission_response",
									requestId,
									optionId,
								})
							}
						/>
					))}
					<PromptInput
						turnActive={state.turnActive}
						commands={state.commands}
						onSend={(text, attachments) =>
							socket.send(
								attachments.length > 0
									? { type: "prompt", text, attachments }
									: { type: "prompt", text },
							)
						}
						onCancel={() => socket.send({ type: "cancel" })}
					/>
				</div>
				<Workspace />
			</div>
		</div>
	);
}