-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathChatMessageInput.tsx
More file actions
97 lines (88 loc) · 2.86 KB
/
ChatMessageInput.tsx
File metadata and controls
97 lines (88 loc) · 2.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import cx from 'classnames'
import { FC, memo, ReactNode, useCallback, useEffect, useRef, useState } from 'react'
import { GoBook } from 'react-icons/go'
import { trackEvent } from '~app/plausible'
import { Prompt } from '~services/prompts'
import Button from '../Button'
import PromptLibraryDialog from '../PromptLibrary/Dialog'
import TextInput from './TextInput'
export interface Message {
text: string
role: Prompt['role']
}
interface Props {
mode: 'full' | 'compact'
onSubmit: (message: Message) => void
className?: string
disabled?: boolean
placeholder?: string
actionButton?: ReactNode | null
autoFocus?: boolean
}
const ChatMessageInput: FC<Props> = (props) => {
const [value, setValue] = useState('')
const formRef = useRef<HTMLFormElement>(null)
const inputRef = useRef<HTMLTextAreaElement>(null)
const [isPromptLibraryDialogOpen, setIsPromptLibraryDialogOpen] = useState(false)
const role = useRef<Prompt['role']>('user')
useEffect(() => {
if (!props.disabled && props.autoFocus) {
inputRef.current?.focus()
}
}, [props.autoFocus, props.disabled])
const onFormSubmit = useCallback(
(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (value.trim()) {
props.onSubmit({ text: value, role: role.current })
}
setValue('')
},
[props, value],
)
const insertPromptAtCursor = useCallback(
(prompt: Prompt) => {
const cursorPosition = inputRef.current?.selectionStart || 0
const textBeforeCursor = value.slice(0, cursorPosition)
const textAfterCursor = value.slice(cursorPosition)
setValue(`${textBeforeCursor}${prompt.prompt}${textAfterCursor}`)
role.current = prompt.role
setIsPromptLibraryDialogOpen(false)
inputRef.current?.focus()
},
[value],
)
const openPromptLibrary = useCallback(() => {
setIsPromptLibraryDialogOpen(true)
trackEvent('open_prompt_library')
}, [])
return (
<form className={cx('flex flex-row items-center gap-3', props.className)} onSubmit={onFormSubmit} ref={formRef}>
{props.mode === 'full' && (
<>
<GoBook size={22} color="#707070" className="cursor-pointer" onClick={openPromptLibrary} />
{isPromptLibraryDialogOpen && (
<PromptLibraryDialog
isOpen={true}
onClose={() => setIsPromptLibraryDialogOpen(false)}
insertPrompt={insertPromptAtCursor}
/>
)}
</>
)}
<TextInput
ref={inputRef}
formref={formRef}
name="input"
disabled={props.disabled}
placeholder={props.placeholder}
value={value}
onValueChange={setValue}
/>
{props.actionButton || (
<Button text="-" className="invisible" size={props.mode === 'full' ? 'normal' : 'small'} />
)}
</form>
)
}
export default memo(ChatMessageInput)