128 lines
3.4 KiB
TypeScript
128 lines
3.4 KiB
TypeScript
import { JSONValue } from "ai";
|
|
import { useState } from "react";
|
|
import { Button } from "../button";
|
|
import { DocumentPreview } from "../document-preview";
|
|
import FileUploader from "../file-uploader";
|
|
import { Input } from "../input";
|
|
import UploadImagePreview from "../upload-image-preview";
|
|
import { ChatHandler } from "./chat.interface";
|
|
import { useFile } from "./hooks/use-file";
|
|
import { LlamaCloudSelector } from "./widgets/LlamaCloudSelector";
|
|
|
|
const ALLOWED_EXTENSIONS = ["png", "jpg", "jpeg", "csv", "pdf", "txt", "docx"];
|
|
|
|
export default function ChatInput(
|
|
props: Pick<
|
|
ChatHandler,
|
|
| "isLoading"
|
|
| "input"
|
|
| "onFileUpload"
|
|
| "onFileError"
|
|
| "handleSubmit"
|
|
| "handleInputChange"
|
|
| "messages"
|
|
| "setInput"
|
|
| "append"
|
|
> & {
|
|
requestParams?: any;
|
|
},
|
|
) {
|
|
const {
|
|
imageUrl,
|
|
setImageUrl,
|
|
uploadFile,
|
|
files,
|
|
removeDoc,
|
|
reset,
|
|
getAnnotations,
|
|
} = useFile();
|
|
const [requestData, setRequestData] = useState<any>();
|
|
|
|
// default submit function does not handle including annotations in the message
|
|
// so we need to use append function to submit new message with annotations
|
|
const handleSubmitWithAnnotations = (
|
|
e: React.FormEvent<HTMLFormElement>,
|
|
annotations: JSONValue[] | undefined,
|
|
) => {
|
|
e.preventDefault();
|
|
props.append!(
|
|
{
|
|
content: props.input,
|
|
role: "user",
|
|
createdAt: new Date(),
|
|
annotations,
|
|
},
|
|
{ data: requestData },
|
|
);
|
|
props.setInput!("");
|
|
};
|
|
|
|
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
const annotations = getAnnotations();
|
|
if (annotations.length) {
|
|
handleSubmitWithAnnotations(e, annotations);
|
|
return reset();
|
|
}
|
|
props.handleSubmit(e, { data: requestData });
|
|
};
|
|
|
|
const handleUploadFile = async (file: File) => {
|
|
if (imageUrl || files.length > 0) {
|
|
alert("同一时刻只能上传一个文件。");
|
|
return;
|
|
}
|
|
try {
|
|
await uploadFile(file, props.requestParams);
|
|
props.onFileUpload?.(file);
|
|
} catch (error: any) {
|
|
props.onFileError?.(error.message);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form
|
|
onSubmit={onSubmit}
|
|
className="rounded-xl bg-white p-4 shadow-xl space-y-4 shrink-0"
|
|
>
|
|
{imageUrl && (
|
|
<UploadImagePreview url={imageUrl} onRemove={() => setImageUrl(null)} />
|
|
)}
|
|
{files.length > 0 && (
|
|
<div className="flex gap-4 w-full overflow-auto py-2">
|
|
{files.map((file) => (
|
|
<DocumentPreview
|
|
key={file.id}
|
|
file={file}
|
|
onRemove={() => removeDoc(file)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
<div className="flex w-full items-start justify-between gap-4 ">
|
|
<Input
|
|
autoFocus
|
|
name="message"
|
|
placeholder="请输入消息"
|
|
className="flex-1"
|
|
value={props.input}
|
|
onChange={props.handleInputChange}
|
|
/>
|
|
<FileUploader
|
|
onFileUpload={handleUploadFile}
|
|
onFileError={props.onFileError}
|
|
config={{
|
|
allowedExtensions: ALLOWED_EXTENSIONS,
|
|
disabled: props.isLoading,
|
|
}}
|
|
/>
|
|
{process.env.NEXT_PUBLIC_USE_LLAMACLOUD === "true" && (
|
|
<LlamaCloudSelector setRequestData={setRequestData} />
|
|
)}
|
|
<Button type="submit" disabled={props.isLoading || !props.input.trim()}>
|
|
发送
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|