mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-26 17:06:23 +02:00
Enhance chat input and sidebar components with runId support and improved auto-focus behavior
This commit is contained in:
parent
f462c558f1
commit
4d2fc01f88
3 changed files with 33 additions and 11 deletions
|
|
@ -282,6 +282,7 @@ interface ChatInputInnerProps {
|
|||
isProcessing: boolean
|
||||
presetMessage?: string
|
||||
onPresetMessageConsumed?: () => void
|
||||
runId?: string | null
|
||||
}
|
||||
|
||||
function ChatInputInner({
|
||||
|
|
@ -289,6 +290,7 @@ function ChatInputInner({
|
|||
isProcessing,
|
||||
presetMessage,
|
||||
onPresetMessageConsumed,
|
||||
runId,
|
||||
}: ChatInputInnerProps) {
|
||||
const controller = usePromptInputController()
|
||||
const message = controller.textInput.value
|
||||
|
|
@ -320,9 +322,9 @@ function ChatInputInner({
|
|||
<div className="flex items-center gap-2 bg-background border border-border rounded-3xl shadow-xl px-4 py-2.5">
|
||||
<PromptInputTextarea
|
||||
placeholder="Type your message..."
|
||||
disabled={isProcessing}
|
||||
onKeyDown={handleKeyDown}
|
||||
autoFocus
|
||||
focusTrigger={runId}
|
||||
className="min-h-6 py-0 border-0 shadow-none focus-visible:ring-0 rounded-none"
|
||||
/>
|
||||
<Button
|
||||
|
|
@ -351,6 +353,7 @@ interface ChatInputWithMentionsProps {
|
|||
isProcessing: boolean
|
||||
presetMessage?: string
|
||||
onPresetMessageConsumed?: () => void
|
||||
runId?: string | null
|
||||
}
|
||||
|
||||
function ChatInputWithMentions({
|
||||
|
|
@ -361,6 +364,7 @@ function ChatInputWithMentions({
|
|||
isProcessing,
|
||||
presetMessage,
|
||||
onPresetMessageConsumed,
|
||||
runId,
|
||||
}: ChatInputWithMentionsProps) {
|
||||
return (
|
||||
<PromptInputProvider knowledgeFiles={knowledgeFiles} recentFiles={recentFiles} visibleFiles={visibleFiles}>
|
||||
|
|
@ -369,6 +373,7 @@ function ChatInputWithMentions({
|
|||
isProcessing={isProcessing}
|
||||
presetMessage={presetMessage}
|
||||
onPresetMessageConsumed={onPresetMessageConsumed}
|
||||
runId={runId}
|
||||
/>
|
||||
</PromptInputProvider>
|
||||
)
|
||||
|
|
@ -405,6 +410,7 @@ function App() {
|
|||
const [currentReasoning, setCurrentReasoning] = useState<string>('')
|
||||
const [, setModelUsage] = useState<LanguageModelUsage | null>(null)
|
||||
const [runId, setRunId] = useState<string | null>(null)
|
||||
const runIdRef = useRef<string | null>(null)
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
const [agentId] = useState<string>('copilot')
|
||||
const [presetMessage, setPresetMessage] = useState<string | undefined>(undefined)
|
||||
|
|
@ -427,6 +433,11 @@ function App() {
|
|||
// Onboarding state
|
||||
const [showOnboarding, setShowOnboarding] = useState(false)
|
||||
|
||||
// Keep runIdRef in sync with runId state (for use in event handlers to avoid stale closures)
|
||||
useEffect(() => {
|
||||
runIdRef.current = runId
|
||||
}, [runId])
|
||||
|
||||
// Load directory tree
|
||||
const loadDirectory = useCallback(async () => {
|
||||
try {
|
||||
|
|
@ -723,15 +734,17 @@ function App() {
|
|||
}, [])
|
||||
|
||||
// Listen to run events
|
||||
// Listen to run events - use ref to avoid stale closure issues
|
||||
useEffect(() => {
|
||||
const cleanup = window.ipc.on('runs:events', ((event: unknown) => {
|
||||
handleRunEvent(event as RunEventType)
|
||||
}) as (event: null) => void)
|
||||
return cleanup
|
||||
}, [runId])
|
||||
}, [])
|
||||
|
||||
const handleRunEvent = (event: RunEventType) => {
|
||||
if (event.runId !== runId) return
|
||||
// Use ref to get current runId to avoid stale closure issues
|
||||
if (event.runId !== runIdRef.current) return
|
||||
|
||||
console.log('Run event:', event.type, event)
|
||||
|
||||
|
|
@ -1044,6 +1057,7 @@ function App() {
|
|||
setRunId(null)
|
||||
setMessage('')
|
||||
setModelUsage(null)
|
||||
setIsProcessing(false)
|
||||
setPendingPermissionRequests(new Map())
|
||||
setPendingAskHumanRequests(new Map())
|
||||
setAllPermissionRequests(new Map())
|
||||
|
|
@ -1716,6 +1730,7 @@ function App() {
|
|||
isProcessing={isProcessing}
|
||||
presetMessage={presetMessage}
|
||||
onPresetMessageConsumed={() => setPresetMessage(undefined)}
|
||||
runId={runId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -906,6 +906,7 @@ export type PromptInputTextareaProps = ComponentProps<
|
|||
typeof InputGroupTextarea
|
||||
> & {
|
||||
autoFocus?: boolean;
|
||||
focusTrigger?: unknown; // When this value changes, focus the textarea
|
||||
};
|
||||
|
||||
export const PromptInputTextarea = ({
|
||||
|
|
@ -914,6 +915,7 @@ export const PromptInputTextarea = ({
|
|||
placeholder = "What would you like to know?",
|
||||
onKeyDown: externalOnKeyDown,
|
||||
autoFocus = false,
|
||||
focusTrigger,
|
||||
...props
|
||||
}: PromptInputTextareaProps) => {
|
||||
const controller = useOptionalPromptInputController();
|
||||
|
|
@ -924,16 +926,16 @@ export const PromptInputTextarea = ({
|
|||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// Auto-focus the textarea when requested
|
||||
// Auto-focus the textarea when requested or when focusTrigger changes
|
||||
useEffect(() => {
|
||||
if (autoFocus) {
|
||||
if (autoFocus || focusTrigger !== undefined) {
|
||||
// Small delay to ensure the element is fully mounted and visible
|
||||
const timer = setTimeout(() => {
|
||||
textareaRef.current?.focus();
|
||||
}, 50);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [autoFocus]);
|
||||
}, [autoFocus, focusTrigger]);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const highlightRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
|
|
|||
|
|
@ -260,10 +260,16 @@ export function ChatSidebar({
|
|||
document.addEventListener('mouseup', handleMouseUp)
|
||||
}, [width])
|
||||
|
||||
// Auto-focus textarea when sidebar opens
|
||||
// Auto-focus textarea when sidebar opens or when conversation is cleared (new chat)
|
||||
useEffect(() => {
|
||||
textareaRef.current?.focus()
|
||||
}, [])
|
||||
// Focus when conversation is empty (new chat started)
|
||||
if (conversation.length === 0) {
|
||||
const timer = setTimeout(() => {
|
||||
textareaRef.current?.focus()
|
||||
}, 50)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [conversation.length])
|
||||
|
||||
// Auto-populate with @currentfile when switching knowledge files
|
||||
useEffect(() => {
|
||||
|
|
@ -584,9 +590,8 @@ export function ChatSidebar({
|
|||
onKeyDown={handleKeyDown}
|
||||
onScroll={syncHighlightScroll}
|
||||
placeholder="Ask anything..."
|
||||
disabled={isProcessing}
|
||||
rows={1}
|
||||
className="relative z-10 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground disabled:opacity-50 resize-none max-h-32 min-h-6"
|
||||
className="relative z-10 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground resize-none max-h-32 min-h-6"
|
||||
style={{ fieldSizing: 'content' } as React.CSSProperties}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue