AskAIWidget.tsx111 lines · main
1import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useRef } from 'react'
2import { Button, ExpandingTextArea } from 'ui'
3
4interface AskAIWidgetProps {
5 value: string
6 onChange: (value: string) => void
7 onSubmit: (prompt: string) => void
8 onAccept?: () => void
9 onReject?: () => void
10 onCancel?: () => void
11 isDiffVisible: boolean
12 isLoading?: boolean
13}
14
15export const AskAIWidget = ({
16 value,
17 onChange,
18 onSubmit,
19 onAccept,
20 onReject,
21 onCancel,
22 isDiffVisible,
23 isLoading = false,
24}: AskAIWidgetProps) => {
25 const textAreaRef = useRef<HTMLTextAreaElement>(null)
26
27 useEffect(() => {
28 setTimeout(() => {
29 textAreaRef.current?.focus()
30 textAreaRef.current?.setSelectionRange(value.length, value.length)
31 }, 100)
32 }, [])
33
34 const handleSubmit = useCallback(() => {
35 if (value.trim() && !isLoading) {
36 onSubmit(value)
37 }
38 }, [value, isLoading, onSubmit])
39
40 const handleChange = useCallback(
41 (e: ChangeEvent<HTMLTextAreaElement>) => {
42 onChange(e.target.value)
43 },
44 [onChange]
45 )
46
47 const handleKeyDown = useCallback(
48 (e: KeyboardEvent<HTMLTextAreaElement>) => {
49 if (e.key === 'Enter' && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
50 e.preventDefault()
51 handleSubmit()
52 }
53 },
54 [handleSubmit]
55 )
56
57 return (
58 <div className="overflow-hidden rounded-md p-0 bg-popover border border-foreground/20 focus-within:border-foreground/30 shadow-xl text-sm max-w-xl">
59 <ExpandingTextArea
60 ref={textAreaRef}
61 className="bg-transparent border-0 outline-0 ring-0 ring-offset-0 focus:outline-0 focus:ring-0 focus:ring-offset-0 focus-visible:outline-0 focus-visible:ring-0 focus-visible:ring-offset-0 focus-within:outline-0 focus-within:ring-0 focus-within:ring-offset-0 shadow-none rounded-none gap-4 text-xs md:text-xs py-2 pl-3 leading-[20px]!"
62 placeholder={isDiffVisible ? 'Make an edit...' : 'Edit via the Assistant...'}
63 value={value}
64 onChange={handleChange}
65 onKeyDown={handleKeyDown}
66 disabled={isLoading}
67 />
68 {isDiffVisible ? (
69 <div className="flex justify-start p-0 border-t">
70 <Button
71 type="text"
72 onClick={onAccept}
73 className="text-xs h-auto py-1 rounded-none px-3 border-r-border"
74 disabled={isLoading}
75 >
76 Accept <span className="text-xs text-foreground-light">⌘ + Enter</span>
77 </Button>
78 <Button
79 onClick={onReject}
80 type="text"
81 className="text-xs h-auto py-1 rounded-none px-3 border-r-border"
82 disabled={isLoading}
83 >
84 Reject <span className="text-xs text-foreground-light">Esc</span>
85 </Button>
86 </div>
87 ) : (
88 <div className="flex justify-start p-0 border-t">
89 <Button
90 type="text"
91 onClick={handleSubmit}
92 loading={isLoading}
93 className="text-xs h-auto py-1 rounded-none px-3 border-r-border"
94 disabled={isLoading}
95 >
96 {isLoading ? 'Generating...' : 'Generate'}{' '}
97 {!isLoading && <span className="text-xs text-foreground-light">Enter</span>}
98 </Button>
99 <Button
100 onClick={onCancel}
101 type="text"
102 className="text-xs h-auto py-1 rounded-none px-3 border-r-border"
103 disabled={isLoading}
104 >
105 Cancel <span className="text-xs text-foreground-light">Esc</span>
106 </Button>
107 </div>
108 )}
109 </div>
110 )
111}