|
"use client" |
|
|
|
import * as React from "react" |
|
import { cn } from "@/lib/utils" |
|
import { LanguageSelect, GraphSelect } from "@/components/Chat/ChatCfgSelect" |
|
import PdfSelect from "@/components/Chat/PdfSelect" |
|
import { useAppDispatch, useAppSelector, isRagGraph, isLanguageSupported } from "@/common" |
|
import { setRtmConnected, addChatItem } from "@/store/reducers/global" |
|
import MessageList from "@/components/Chat/MessageList" |
|
import { Button } from "@/components/ui/button" |
|
import { Send } from "lucide-react" |
|
import { rtmManager } from "@/manager/rtm" |
|
import { type IRTMTextItem, EMessageDataType, EMessageType, ERTMTextType } from "@/types" |
|
|
|
let hasInit: boolean = false |
|
|
|
export default function ChatCard(props: { className?: string }) { |
|
const { className } = props |
|
const [inputValue, setInputValue] = React.useState("") |
|
|
|
const graphName = useAppSelector((state) => state.global.graphName) |
|
const rtmConnected = useAppSelector((state) => state.global.rtmConnected) |
|
const options = useAppSelector((state) => state.global.options) |
|
const agentConnected = useAppSelector((state) => state.global.agentConnected) |
|
const dispatch = useAppDispatch() |
|
|
|
const disableInputMemo = React.useMemo(() => { |
|
return ( |
|
!options.channel || |
|
!options.userId || |
|
!options.appId || |
|
!options.token || |
|
!rtmConnected || |
|
!agentConnected |
|
) |
|
}, [ |
|
options.channel, |
|
options.userId, |
|
options.appId, |
|
options.token, |
|
rtmConnected, |
|
agentConnected, |
|
]) |
|
|
|
React.useEffect(() => { |
|
if ( |
|
!options.channel || |
|
!options.userId || |
|
!options.appId || |
|
!options.token |
|
) { |
|
return |
|
} |
|
if (hasInit) { |
|
return |
|
} |
|
|
|
init() |
|
|
|
return () => { |
|
|
|
|
|
|
|
} |
|
}, [options.channel, options.userId, options.appId, options.token]) |
|
|
|
const init = async () => { |
|
console.log("[rtm] init") |
|
await rtmManager.init({ |
|
channel: options.channel, |
|
userId: options.userId, |
|
appId: options.appId, |
|
token: options.token, |
|
}) |
|
dispatch(setRtmConnected(true)) |
|
rtmManager.on("rtmMessage", onTextChanged) |
|
hasInit = true |
|
} |
|
const destory = async () => { |
|
console.log("[rtm] destory") |
|
rtmManager.off("rtmMessage", onTextChanged) |
|
await rtmManager.destroy() |
|
dispatch(setRtmConnected(false)) |
|
hasInit = false |
|
} |
|
|
|
const onTextChanged = (text: IRTMTextItem) => { |
|
console.log("[rtm] onTextChanged", text) |
|
if (text.type == ERTMTextType.TRANSCRIBE) { |
|
|
|
dispatch( |
|
addChatItem({ |
|
userId: options.userId, |
|
text: text.text, |
|
type: `${text.stream_id}` === "0" ? EMessageType.AGENT : EMessageType.USER, |
|
data_type: EMessageDataType.TEXT, |
|
isFinal: text.is_final, |
|
time: text.ts, |
|
}), |
|
) |
|
} |
|
if (text.type == ERTMTextType.INPUT_TEXT) { |
|
dispatch( |
|
addChatItem({ |
|
userId: options.userId, |
|
text: text.text, |
|
type: EMessageType.USER, |
|
data_type: EMessageDataType.TEXT, |
|
isFinal: true, |
|
time: text.ts, |
|
}), |
|
) |
|
} |
|
} |
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
|
setInputValue(e.target.value) |
|
} |
|
|
|
const handleInputSubmit = (e: React.FormEvent<HTMLFormElement>) => { |
|
e.preventDefault() |
|
if (!inputValue || disableInputMemo) { |
|
return |
|
} |
|
rtmManager.sendText(inputValue) |
|
setInputValue("") |
|
} |
|
|
|
return ( |
|
<> |
|
{/* Chat Card */} |
|
<div className={cn("flex-grow", className)}> |
|
<div className="flex h-full w-full flex-col p-4"> |
|
{/* Action Bar */} |
|
<div className="flex w-full flex-wrap items-center justify-end gap-x-4 gap-y-2"> |
|
<GraphSelect /> |
|
{ |
|
isLanguageSupported(graphName) ? |
|
<LanguageSelect /> : |
|
null |
|
} |
|
{isRagGraph(graphName) && <PdfSelect />} |
|
</div> |
|
{/* Chat messages would go here */} |
|
<MessageList /> |
|
<div |
|
className={cn("border-t pt-4", { |
|
["hidden"]: !graphName.includes("rtm"), // TODO: TMP use rtm key word |
|
})} |
|
> |
|
<form |
|
onSubmit={handleInputSubmit} |
|
className="flex items-center space-x-2" |
|
> |
|
<input |
|
type="text" |
|
disabled={disableInputMemo} |
|
placeholder="Type a message..." |
|
value={inputValue} |
|
onChange={handleInputChange} |
|
className={cn( |
|
"flex-grow rounded-md border bg-background p-1.5 focus:outline-none focus:ring-1 focus:ring-ring", |
|
{ |
|
["cursor-not-allowed"]: disableInputMemo, |
|
}, |
|
)} |
|
/> |
|
<Button |
|
type="submit" |
|
disabled={disableInputMemo || inputValue.length == 0} |
|
size="icon" |
|
variant="outline" |
|
className={cn("bg-transparent", { |
|
["opacity-50"]: disableInputMemo || inputValue.length == 0, |
|
["cursor-not-allowed"]: disableInputMemo, |
|
})} |
|
> |
|
<Send className="h-4 w-4" /> |
|
<span className="sr-only">Send message</span> |
|
</Button> |
|
</form> |
|
</div> |
|
</div> |
|
</div> |
|
</> |
|
) |
|
} |
|
|