Spaces:
Build error
Build error
import React from "react"; | |
import { useNavigation } from "react-router"; | |
import { useDispatch, useSelector } from "react-redux"; | |
import { RootState } from "#/store"; | |
import { addFile, removeFile } from "#/state/initial-query-slice"; | |
import { SuggestionBubble } from "#/components/features/suggestions/suggestion-bubble"; | |
import { SUGGESTIONS } from "#/utils/suggestions"; | |
import { convertImageToBase64 } from "#/utils/convert-image-to-base-64"; | |
import { ChatInput } from "#/components/features/chat/chat-input"; | |
import { getRandomKey } from "#/utils/get-random-key"; | |
import { cn } from "#/utils/utils"; | |
import { AttachImageLabel } from "../features/images/attach-image-label"; | |
import { ImageCarousel } from "../features/images/image-carousel"; | |
import { UploadImageInput } from "../features/images/upload-image-input"; | |
import { useCreateConversation } from "#/hooks/mutation/use-create-conversation"; | |
import { LoadingSpinner } from "./loading-spinner"; | |
interface TaskFormProps { | |
ref: React.RefObject<HTMLFormElement | null>; | |
} | |
export function TaskForm({ ref }: TaskFormProps) { | |
const dispatch = useDispatch(); | |
const navigation = useNavigation(); | |
const { files } = useSelector((state: RootState) => state.initialQuery); | |
const [text, setText] = React.useState(""); | |
const [suggestion, setSuggestion] = React.useState(() => { | |
const key = getRandomKey(SUGGESTIONS["non-repo"]); | |
return { key, value: SUGGESTIONS["non-repo"][key] }; | |
}); | |
const [inputIsFocused, setInputIsFocused] = React.useState(false); | |
const { mutate: createConversation, isPending } = useCreateConversation(); | |
const onRefreshSuggestion = () => { | |
const suggestions = SUGGESTIONS["non-repo"]; | |
// remove current suggestion to avoid refreshing to the same suggestion | |
const suggestionCopy = { ...suggestions }; | |
delete suggestionCopy[suggestion.key]; | |
const key = getRandomKey(suggestionCopy); | |
setSuggestion({ key, value: suggestions[key] }); | |
}; | |
const onClickSuggestion = () => { | |
setText(suggestion.value); | |
}; | |
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { | |
event.preventDefault(); | |
const formData = new FormData(event.currentTarget); | |
const q = formData.get("q")?.toString(); | |
createConversation({ q }); | |
}; | |
return ( | |
<div className="flex flex-col gap-1 w-full"> | |
<form | |
ref={ref} | |
onSubmit={handleSubmit} | |
className="flex flex-col items-center gap-2" | |
> | |
<SuggestionBubble | |
suggestion={suggestion} | |
onClick={onClickSuggestion} | |
onRefresh={onRefreshSuggestion} | |
/> | |
<div | |
className={cn( | |
"border border-neutral-600 px-4 rounded-lg text-[17px] leading-5 w-full transition-colors duration-200", | |
inputIsFocused ? "bg-neutral-600" : "bg-tertiary", | |
"hover:border-neutral-500 focus-within:border-neutral-500", | |
)} | |
> | |
{isPending ? ( | |
<div className="flex justify-center py-[17px]"> | |
<LoadingSpinner size="small" /> | |
</div> | |
) : ( | |
<ChatInput | |
name="q" | |
onSubmit={() => { | |
if (typeof ref !== "function") ref?.current?.requestSubmit(); | |
}} | |
onChange={(message) => setText(message)} | |
onFocus={() => setInputIsFocused(true)} | |
onBlur={() => setInputIsFocused(false)} | |
onImagePaste={async (imageFiles) => { | |
const promises = imageFiles.map(convertImageToBase64); | |
const base64Images = await Promise.all(promises); | |
base64Images.forEach((base64) => { | |
dispatch(addFile(base64)); | |
}); | |
}} | |
value={text} | |
maxRows={15} | |
showButton={!!text} | |
className="text-[17px] leading-5 py-[17px]" | |
buttonClassName="pb-[17px]" | |
disabled={navigation.state === "submitting"} | |
/> | |
)} | |
</div> | |
</form> | |
<UploadImageInput | |
onUpload={async (uploadedFiles) => { | |
const promises = uploadedFiles.map(convertImageToBase64); | |
const base64Images = await Promise.all(promises); | |
base64Images.forEach((base64) => { | |
dispatch(addFile(base64)); | |
}); | |
}} | |
label={<AttachImageLabel />} | |
/> | |
{files.length > 0 && ( | |
<ImageCarousel | |
size="large" | |
images={files} | |
onRemove={(index) => dispatch(removeFile(index))} | |
/> | |
)} | |
</div> | |
); | |
} | |