suppoer multiple line input using textarea instead
This commit is contained in:
parent
45abf10424
commit
ef3d89f33e
@ -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'
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
21
uikit/src/textarea/index.tsx
Normal file
21
uikit/src/textarea/index.tsx
Normal 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 }
|
||||||
6
uikit/src/textarea/styles.scss
Normal file
6
uikit/src/textarea/styles.scss
Normal 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;
|
||||||
|
}
|
||||||
@ -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 }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user