nandi/oripublic Fork 0
55d4c9e2f7a9027b52846558166f42e50851523a
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

PromptInput.tsx · 183 lines · 4.6 KBTypeScript Blame HistoryRaw
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago1/**
2 * PromptInput is the message composer: Enter sends (Shift+Enter for a new
3 * line); while a turn runs the send button becomes a stop button, matching
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday4 * the behaviour of Zed's agent panel. Typing "@" opens the workspace file
5 * selector (the pick is sent as an ACP resource_link attachment) and a
6 * leading "/" opens the skill / command selector.
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago7 *
8 * @example
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday9 * <PromptInput turnActive={false} commands={[]} onSend={(t, files) => …} onCancel={() => {}} />
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago10 */
11
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday12import { type KeyboardEvent, useEffect, useRef, useState } from "react";
13import {
14 type CompletionItem,
15 activeAttachments,
16 applyCompletion,
17} from "../mentions";
18import type { AcpAvailableCommand, PromptAttachment } from "../protocol";
19import { useCompletion } from "../useCompletion";
20import { CompletionPopup } from "./CompletionPopup";
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago21
22interface PromptInputProps {
23 turnActive: boolean;
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday24 /** Slash commands announced by the agent, merged into the "/" popup. */
25 commands: AcpAvailableCommand[];
26 onSend: (text: string, attachments: PromptAttachment[]) => void;
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago27 onCancel: () => void;
28}
29
30export function PromptInput({
31 turnActive,
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday32 commands,
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago33 onSend,
34 onCancel,
35}: PromptInputProps) {
36 const [text, setText] = useState("");
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday37 const [caret, setCaret] = useState(0);
38 const [attachments, setAttachments] = useState<PromptAttachment[]>([]);
39 const textareaRef = useRef<HTMLTextAreaElement>(null);
40 const pendingCaret = useRef<number | null>(null);
41 const completion = useCompletion({ text, caret, commands });
42
43 // After a completion the caret must land right after the insertion; the
44 // pending value is consumed on the render that follows the text update.
45 useEffect(() => {
46 if (pendingCaret.current !== null && textareaRef.current) {
47 textareaRef.current.setSelectionRange(
48 pendingCaret.current,
49 pendingCaret.current,
50 );
51 pendingCaret.current = null;
52 }
53 });
54
55 const syncCaret = () => {
56 setCaret(textareaRef.current?.selectionStart ?? text.length);
57 };
58
59 const pick = (item: CompletionItem) => {
60 if (!completion.trigger) {
61 return;
62 }
63 const next = applyCompletion(text, completion.trigger, item.insert);
64 setText(next.text);
65 setCaret(next.caret);
66 pendingCaret.current = next.caret;
67 if (item.attachment) {
68 setAttachments((current) => [
69 ...current,
70 item.attachment as PromptAttachment,
71 ]);
72 }
73 textareaRef.current?.focus();
74 };
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago75
76 const submit = () => {
77 const trimmed = text.trim();
78 if (trimmed === "" || turnActive) {
79 return;
80 }
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday81 onSend(trimmed, activeAttachments(trimmed, attachments));
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago82 setText("");
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday83 setCaret(0);
84 setAttachments([]);
85 };
86
87 const onKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
88 if (completion.open && handleCompletionKey(event, completion, pick)) {
89 event.preventDefault();
90 return;
91 }
92 if (event.key === "Enter" && !event.shiftKey) {
93 event.preventDefault();
94 submit();
95 }
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago96 };
97
98 return (
99 <form
100 className="composer"
101 onSubmit={(event) => {
102 event.preventDefault();
103 submit();
104 }}
105 >
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday106 <div className="composer-field">
107 {completion.open && (
108 <CompletionPopup
109 items={completion.items}
110 active={completion.active}
111 onHover={completion.setActive}
112 onPick={pick}
113 />
114 )}
115 <textarea
116 ref={textareaRef}
117 className="composer-input"
118 placeholder="Message the agent… (@ file, / skill)"
119 aria-label="prompt"
120 aria-autocomplete="list"
121 aria-expanded={completion.open}
122 aria-activedescendant={
123 completion.open ? `completion-${completion.active}` : undefined
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago124 }
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday125 value={text}
126 rows={3}
127 onChange={(event) => {
128 setText(event.target.value);
129 setCaret(event.target.selectionStart);
130 }}
131 onKeyDown={onKeyDown}
132 onKeyUp={syncCaret}
133 onClick={syncCaret}
134 />
135 </div>
✨ Introduce new feature(s): ACP web client — Go backend (agent, bridge, httpserver, mockagent) + React SPA (Zed-like agent panel), tests, quality gate PASS 2434cc7 k33g 2d ago136 {turnActive ? (
137 <button type="button" className="composer-stop" onClick={onCancel}>
138 Stop
139 </button>
140 ) : (
141 <button
142 type="submit"
143 className="composer-send"
144 disabled={text.trim() === ""}
145 >
146 Send
147 </button>
148 )}
149 </form>
150 );
151}
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday152
153/**
154 * handleCompletionKey applies the popup's keyboard contract and tells
155 * whether the key was consumed: arrows move, Enter/Tab pick, Escape closes.
156 */
157function handleCompletionKey(
158 event: KeyboardEvent<HTMLTextAreaElement>,
159 completion: ReturnType<typeof useCompletion>,
160 pick: (item: CompletionItem) => void,
161): boolean {
162 switch (event.key) {
163 case "ArrowDown":
164 completion.moveActive(1);
165 return true;
166 case "ArrowUp":
167 completion.moveActive(-1);
168 return true;
169 case "Enter":
170 case "Tab": {
171 const item = completion.items[completion.active];
172 if (item) {
173 pick(item);
174 }
175 return true;
176 }
177 case "Escape":
178 completion.dismiss();
179 return true;
180 default:
181 return false;
182 }
183}