diff --git a/packages/app/pr/screenshots/minimap-rail-1.png b/packages/app/pr/screenshots/minimap-rail-1.png new file mode 100644 index 000000000..01c5c70eb Binary files /dev/null and b/packages/app/pr/screenshots/minimap-rail-1.png differ diff --git a/packages/app/pr/screenshots/minimap-rail-2.png b/packages/app/pr/screenshots/minimap-rail-2.png new file mode 100644 index 000000000..38b08bc37 Binary files /dev/null and b/packages/app/pr/screenshots/minimap-rail-2.png differ diff --git a/packages/app/src/app/app.tsx b/packages/app/src/app/app.tsx index bfd0f38ea..55ff078ab 100644 --- a/packages/app/src/app/app.tsx +++ b/packages/app/src/app/app.tsx @@ -2002,6 +2002,7 @@ export default function App() { listAgents={listAgents} setSessionAgent={setSessionAgent} saveSession={saveSessionExport} + sessionStatusById={activeSessionStatusById()} onTryNotionPrompt={() => { setPrompt("setup my crm"); setTryNotionPromptVisible(false); diff --git a/packages/app/src/app/components/flyout-item.tsx b/packages/app/src/app/components/flyout-item.tsx new file mode 100644 index 000000000..c8855c0b1 --- /dev/null +++ b/packages/app/src/app/components/flyout-item.tsx @@ -0,0 +1,50 @@ +import { Show, createSignal, onMount } from "solid-js"; +import { Check, FileText, Folder } from "lucide-solid"; + +export type FlyoutProps = { + item: { + id: string; + rect: { top: number; left: number; width: number; height: number }; + targetRect: { top: number; left: number; width: number; height: number }; + label: string; + icon: "file" | "check" | "folder"; + }; +}; + +export default function FlyoutItem(props: FlyoutProps) { + const [active, setActive] = createSignal(false); + onMount(() => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + setActive(true); + }); + }); + }); + + return ( +
+ + + + + + + + + + {props.item.label} +
+ ); +} diff --git a/packages/app/src/app/components/session/composer.tsx b/packages/app/src/app/components/session/composer.tsx new file mode 100644 index 000000000..a357d9eb3 --- /dev/null +++ b/packages/app/src/app/components/session/composer.tsx @@ -0,0 +1,209 @@ +import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js"; +import { ArrowRight, Zap } from "lucide-solid"; + +export type CommandItem = { + id: string; + description: string; +}; + +export type ComposerProps = { + prompt: string; + setPrompt: (value: string) => void; + busy: boolean; + onSend: () => void; + commandMatches: CommandItem[]; + onRunCommand: (commandId: string) => void; + selectedModelLabel: string; + onModelClick: () => void; + showNotionBanner: boolean; + onNotionBannerClick: () => void; + toast: string | null; +}; + +export default function Composer(props: ComposerProps) { + let textareaRef: HTMLTextAreaElement | undefined; + const [commandIndex, setCommandIndex] = createSignal(0); + + const commandMenuOpen = createMemo(() => { + return props.prompt.startsWith("/") && !props.busy; + }); + + const syncHeight = () => { + if (!textareaRef) return; + textareaRef.style.height = "auto"; + const nextHeight = Math.min(textareaRef.scrollHeight, 160); + textareaRef.style.height = `${nextHeight}px`; + textareaRef.style.overflowY = textareaRef.scrollHeight > 160 ? "auto" : "hidden"; + }; + + createEffect(() => { + props.prompt; + syncHeight(); + }); + + createEffect(() => { + if (commandMenuOpen()) { + setCommandIndex(0); + } + }); + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter" && event.shiftKey) return; + if (event.isComposing && event.key !== "Enter") return; + + if (commandMenuOpen()) { + const matches = props.commandMatches; + if (event.key === "Enter") { + event.preventDefault(); + const active = matches[commandIndex()] ?? matches[0]; + if (active) { + props.onRunCommand(active.id); + } + return; + } + if (event.key === "ArrowDown") { + event.preventDefault(); + setCommandIndex((i) => Math.min(i + 1, matches.length - 1)); + return; + } + if (event.key === "ArrowUp") { + event.preventDefault(); + setCommandIndex((i) => Math.max(i - 1, 0)); + return; + } + if (event.key === "Escape") { + event.preventDefault(); + props.setPrompt(""); + return; + } + if (event.key === "Tab") { + event.preventDefault(); + // maybe autocomplete? + const active = matches[commandIndex()] ?? matches[0]; + if (active) { + props.onRunCommand(active.id); + } + return; + } + } + + if (event.key === "Enter") { + event.preventDefault(); + props.onSend(); + } + }; + + createEffect(() => { + const handler = () => { + textareaRef?.focus(); + }; + window.addEventListener("openwork:focusPrompt", handler); + onCleanup(() => window.removeEventListener("openwork:focusPrompt", handler)); + }); + + return ( +
+
+
+ +
+
+
+ Commands +
+
+ No commands found.
+ } + > + + {(command, idx) => ( + + )} + + +
+
+
+ + + + +
+ + + + +
+ +
+ {props.toast} +
+
+ +
+