suppoer multiple line input using textarea instead

This commit is contained in:
Faisal Amir 2023-11-11 17:22:54 +07:00
parent 45abf10424
commit ef3d89f33e
6 changed files with 65 additions and 16 deletions

View File

@ -9,3 +9,4 @@ export * from './badge'
export * from './tooltip' export * from './tooltip'
export * from './modal' export * from './modal'
export * from './command' export * from './command'
export * from './textarea'

View File

@ -13,6 +13,7 @@
@import './tooltip/styles.scss'; @import './tooltip/styles.scss';
@import './modal/styles.scss'; @import './modal/styles.scss';
@import './command/styles.scss'; @import './command/styles.scss';
@import './textarea/styles.scss';
.animate-spin { .animate-spin {
animation: spin 1s linear infinite; animation: spin 1s linear infinite;

View File

@ -0,0 +1,21 @@
import * as React from 'react'
import { twMerge } from 'tailwind-merge'
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={twMerge('textarea-input', className)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = 'Textarea'
export { Textarea }

View File

@ -0,0 +1,6 @@
.textarea-input {
@apply border-border placeholder:text-muted-foreground flex w-full rounded-md border bg-transparent px-3 py-2 transition-colors;
@apply disabled:cursor-not-allowed disabled:opacity-50;
@apply focus-visible:ring-secondary focus-visible:outline-none focus-visible:ring-1;
@apply file:border-0 file:bg-transparent file:font-medium;
}

View File

@ -80,8 +80,11 @@ const SimpleTextMessage: React.FC<Props> = ({
<BubbleLoader /> <BubbleLoader />
) : ( ) : (
<> <>
<span <div
className={'message text-[15px] font-normal leading-relaxed'} className={twMerge(
'message flex flex-grow flex-col gap-y-2 text-[15px] font-normal leading-relaxed',
isUser && 'whitespace-pre-wrap break-words'
)}
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
dangerouslySetInnerHTML={{ __html: parsedText }} dangerouslySetInnerHTML={{ __html: parsedText }}
/> />

View File

@ -1,7 +1,7 @@
import { Fragment, useEffect } from 'react' import { Fragment, useEffect, useRef } from 'react'
import { Model } from '@janhq/core/lib/types' import { Model } from '@janhq/core/lib/types'
import { Input, Button, Badge } from '@janhq/uikit' import { Button, Badge, Textarea } from '@janhq/uikit'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { Trash2Icon } from 'lucide-react' import { Trash2Icon } from 'lucide-react'
@ -54,13 +54,15 @@ const ChatScreen = () => {
const conversations = useAtomValue(userConversationsAtom) const conversations = useAtomValue(userConversationsAtom)
const isEnableChat = (currentConvo && activeModel) || conversations.length > 0 const isEnableChat = (currentConvo && activeModel) || conversations.length > 0
const textareaRef = useRef<HTMLTextAreaElement>(null)
useEffect(() => { useEffect(() => {
getUserConversations() getUserConversations()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const handleMessageChange = (value: string) => { const handleMessageChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setCurrentPrompt(value) setCurrentPrompt(e.target.value)
} }
const handleSendMessage = async () => { const handleSendMessage = async () => {
@ -71,6 +73,7 @@ const ChatScreen = () => {
await requestCreateConvo(activeModel as Model) await requestCreateConvo(activeModel as Model)
} }
} }
useEffect(() => { useEffect(() => {
if (isWaitingToSend && activeConversationId) { if (isWaitingToSend && activeConversationId) {
setIsWaitingToSend(false) setIsWaitingToSend(false)
@ -79,12 +82,24 @@ const ChatScreen = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [waitingToSendMessage, activeConversationId]) }, [waitingToSendMessage, activeConversationId])
const handleKeyDown = async ( useEffect(() => {
event: React.KeyboardEvent<HTMLInputElement> if (textareaRef.current !== null) {
) => { const scrollHeight = textareaRef.current.scrollHeight
if (event.key === 'Enter') { if (currentPrompt.length === 0) {
if (!event.shiftKey) { textareaRef.current.style.height = '40px'
event.preventDefault() } else {
textareaRef.current.style.height = `${
scrollHeight < 40 ? 40 : scrollHeight
}px`
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentPrompt])
const handleKeyDown = async (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
if (!e.shiftKey) {
e.preventDefault()
handleSendMessage() handleSendMessage()
} }
} }
@ -162,9 +177,9 @@ const ChatScreen = () => {
</div> </div>
)} )}
<div className="mx-auto flex w-full flex-shrink-0 items-center justify-center space-x-4 p-4 lg:w-3/4"> <div className="mx-auto flex w-full flex-shrink-0 items-center justify-center space-x-4 p-4 lg:w-3/4">
<Input <Textarea
type="text" className="min-h-10 h-10 max-h-16 resize-none pr-20"
className="h-10" ref={textareaRef}
onKeyDown={(e) => handleKeyDown(e)} onKeyDown={(e) => handleKeyDown(e)}
placeholder="Type your message ..." placeholder="Type your message ..."
disabled={ disabled={
@ -173,7 +188,9 @@ const ChatScreen = () => {
activeModel._id !== currentConvo?.modelId activeModel._id !== currentConvo?.modelId
} }
value={currentPrompt} value={currentPrompt}
onChange={(e) => handleMessageChange(e.target.value)} onChange={(e) => {
handleMessageChange(e)
}}
/> />
<Button <Button
size="lg" size="lg"