AssistantChatForm.tsx128 lines · main
1'use client'
2
3import { useBreakpoint } from 'common'
4import { Loader2 } from 'lucide-react'
5import React, { ChangeEvent, memo, useRef } from 'react'
6import { ExpandingTextArea } from 'ui'
7import { cn } from 'ui/src/lib/utils'
8
9export interface FormProps extends React.FormHTMLAttributes<HTMLFormElement> {
10 /* The ref for the textarea, optional. Exposed for the CommandsPopover to attach events. */
11 textAreaRef?: React.RefObject<HTMLTextAreaElement | null>
12 /* The loading state of the form */
13 loading: boolean
14 /* The disabled state of the form */
15 disabled?: boolean
16 /* The value of the textarea */
17 value?: string
18 /* The function to handle the value change */
19 onValueChange: (value: ChangeEvent<HTMLTextAreaElement>) => void
20 /* Used to stop onSubmit event when Command popover is open. Use with AssistantCommandsPopover */
21 commandsOpen?: boolean
22 /* Used to close Command popover when onSubmit event happens. Use with AssistantCommandsPopover */
23 setCommandsOpen?: (value: boolean) => void
24 /* The icon to display on the left of the textarea */
25 icon?: React.ReactNode
26 /* The function to handle the form submission */
27 onSubmit: React.FormHTMLAttributes<HTMLFormElement>['onSubmit']
28 /* The placeholder of the textarea */
29 placeholder?: string
30}
31
32const AssistantChatFormComponent = React.forwardRef<HTMLFormElement, FormProps>(
33 (
34 {
35 loading = false,
36 disabled = false,
37 value = '',
38 textAreaRef,
39 commandsOpen = false,
40 icon = null,
41 onValueChange,
42 setCommandsOpen,
43 onSubmit,
44 placeholder,
45 ...props
46 },
47 ref
48 ) => {
49 const formRef = useRef<HTMLFormElement>(null)
50 const submitRef = useRef<HTMLButtonElement>(null)
51 const isMobile = useBreakpoint('md')
52
53 /**
54 * This function is used to handle the "Enter" key press
55 */
56 const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
57 // Check if the pressed key is "Enter" (key code 13) without the "Shift" key
58 // also checks if the commands popover is open
59 if (event.key === 'Enter' && event.keyCode === 13 && !event.shiftKey && !commandsOpen) {
60 event.preventDefault()
61 if (submitRef.current) submitRef.current.click()
62 }
63
64 // handles closing the commands popover if open
65 if (event.key === 'Enter' && commandsOpen) {
66 if (setCommandsOpen) setCommandsOpen(false)
67 }
68 }
69
70 return (
71 <form
72 id="assistant-chat"
73 ref={formRef}
74 {...props}
75 onSubmit={onSubmit}
76 className={cn('relative ', props.className)}
77 >
78 <ExpandingTextArea
79 ref={textAreaRef}
80 autoFocus={isMobile}
81 disabled={disabled}
82 className="text-base md:text-sm pr-10 rounded-sm max-h-64"
83 placeholder={placeholder}
84 spellCheck={false}
85 rows={3}
86 value={value}
87 onChange={(event) => onValueChange(event)}
88 onKeyDown={handleKeyDown}
89 />
90 <div className="absolute right-1.5 top-1.5 flex gap-3 items-center">
91 {loading && (
92 <Loader2 size={22} className="animate-spin w-7 h-7 text-muted" strokeWidth={1} />
93 )}
94
95 <button
96 ref={submitRef}
97 type="submit"
98 className={cn(
99 'transition-all',
100 'flex items-center justify-center w-7 h-7 border border-control rounded-full mr-0.5 p-1.5 background-alternative',
101 !value ? 'text-muted opacity-50' : 'text-default opacity-100',
102 loading && 'hidden'
103 )}
104 >
105 <svg
106 width="16"
107 height="16"
108 viewBox="0 0 16 16"
109 fill="none"
110 xmlns="http://www.w3.org/2000/svg"
111 >
112 <path
113 fillRule="evenodd"
114 clipRule="evenodd"
115 d="M13.5 3V2.25H15V3V10C15 10.5523 14.5522 11 14 11H3.56062L5.53029 12.9697L6.06062 13.5L4.99996 14.5607L4.46963 14.0303L1.39641 10.9571C1.00588 10.5666 1.00588 9.93342 1.39641 9.54289L4.46963 6.46967L4.99996 5.93934L6.06062 7L5.53029 7.53033L3.56062 9.5H13.5V3Z"
116 fill="currentColor"
117 />
118 </svg>
119 </button>
120 </div>
121 </form>
122 )
123 }
124)
125
126AssistantChatFormComponent.displayName = 'AssistantChatFormComponent'
127
128export const AssistantChatForm = memo(AssistantChatFormComponent)