mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-07 03:00:29 +00:00
feat(web): add a sticky steer bar at the bottom of run views
Adds a single-row text input + Send button that pins to the viewport bottom on every run-detail tab as a placeholder for the future steering composer (will replace the modal). Renders mutually exclusive with InterviewDock: the dock takes precedence on blocked runs with pending questions, otherwise the steer bar shows. Reuses the existing --fabro-interview-dock-clearance variable so consumers that already pad for the dock pick up the steer bar's clearance too. Send is intentionally a no-op for now until the steering API is swapped over from the modal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1b9920e9e5
commit
427a24d145
2 changed files with 64 additions and 2 deletions
58
apps/fabro-web/app/components/steer-bar.tsx
Normal file
58
apps/fabro-web/app/components/steer-bar.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { useState, type FormEvent, type KeyboardEvent } from "react";
|
||||
|
||||
export interface SteerBarProps {
|
||||
runId: string;
|
||||
}
|
||||
|
||||
export function SteerBar({ runId: _runId }: SteerBarProps) {
|
||||
const [text, setText] = useState("");
|
||||
const trimmed = text.trim();
|
||||
const canSubmit = trimmed.length > 0;
|
||||
|
||||
function handleSubmit(e: FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!canSubmit) return;
|
||||
setText("");
|
||||
}
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent<HTMLTextAreaElement>) {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
if (canSubmit) {
|
||||
setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none fixed inset-x-0 bottom-0 z-30">
|
||||
<div className="bg-linear-to-t from-page via-page/80 to-transparent pt-10">
|
||||
<div className="pointer-events-auto mx-auto max-w-5xl px-4 pb-4 sm:px-6 lg:px-8">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
aria-label="Steer running agent"
|
||||
className="flex items-end gap-2 rounded-2xl bg-panel p-2 shadow-[0_-12px_40px_-8px_rgba(0,0,0,0.5)] ring-1 ring-line-strong"
|
||||
>
|
||||
<textarea
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Steer the agent…"
|
||||
rows={1}
|
||||
maxLength={8192}
|
||||
aria-label="Steering message"
|
||||
className="flex-1 resize-none bg-transparent px-3 py-2 text-sm text-fg outline-hidden placeholder:text-fg-muted"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!canSubmit}
|
||||
className="inline-flex shrink-0 items-center justify-center rounded-lg bg-teal-500 px-4 py-2 text-sm font-medium text-on-primary transition-colors hover:bg-teal-300 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-500 disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-teal-500"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import { Link, Outlet, useLocation, useMatches } from "react-router";
|
|||
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
|
||||
|
||||
import { InterviewDock } from "../components/interview-dock";
|
||||
import { SteerBar } from "../components/steer-bar";
|
||||
import { SteerComposer } from "../components/steer-composer";
|
||||
import { ErrorState } from "../components/state";
|
||||
import { useToast } from "../components/toast";
|
||||
|
|
@ -212,8 +213,9 @@ export default function RunDetail({ params }: { params: { id: string } }) {
|
|||
const archivePending = archiveMutation.isMutating;
|
||||
const unarchivePending = unarchiveMutation.isMutating;
|
||||
const hasPendingQuestions = isBlocked && pendingQuestions.length > 0;
|
||||
const rootStyle = fullHeight && hasPendingQuestions
|
||||
? ({ "--fabro-interview-dock-clearance": "18rem" } as CSSProperties)
|
||||
const dockClearance = hasPendingQuestions ? "18rem" : "5rem";
|
||||
const rootStyle = fullHeight
|
||||
? ({ "--fabro-interview-dock-clearance": dockClearance } as CSSProperties)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
|
|
@ -346,6 +348,8 @@ export default function RunDetail({ params }: { params: { id: string } }) {
|
|||
onClose={() => setSteerOpen(false)}
|
||||
/>
|
||||
|
||||
{!hasPendingQuestions && <SteerBar runId={params.id} />}
|
||||
|
||||
{hasPendingQuestions && (
|
||||
fullHeight ? (
|
||||
<InterviewDock runId={params.id} questions={pendingQuestions} />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue