123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- import { View, ScrollView, Video } from "@tarojs/components";
- import NavBarNormal from "@/components/NavBarNormal/index";
- import PageCustom from "@/components/page-custom/index";
- import Taro, { useDidShow, useRouter, useUnload } from "@tarojs/taro";
- import ChatMessage from "@/components/chat-message";
- import InputBar from "./components/input-bar";
- import { useEffect, useState, useRef, useMemo } from "react";
- import { useTextChat } from "@/store/textChat";
- import { TRobotMessage, TMessage, EContentType, EChatRole } from "@/types/bot";
- import ChatGreeting from "./components/ChatGreeting";
- import IconArrowLeftWhite24 from "@/components/icon/IconArrowLeftWhite24";
- import PersonalCard from "./components/personal-card";
- import { useAgentStore } from "@/store/agentStore";
- import { useLoadMoreInfinite, createKey } from "@/utils/loadMoreInfinite";
- import { getMessageHistories } from "@/service/bot";
- import RecommendQuestions from './components/RecommendQuestions'
- import {useKeyboard} from './components/keyboard'
- import { saveMessageToServer } from "./components/input-bar/message";
- import { generateUUID, getLoginId } from '@/utils/index'
- export default function Index() {
- const router = useRouter();
-
- const { agentId, isVisitor } = router.params;
- if (!agentId) {
- return <View>没有相应的智能体</View>;
- }
-
- const { fetchAgent, fetchAgentProfile, } = useAgentStore()
-
- const agent = useAgentStore((state) => {
- if(isVisitor === 'true'){
- return state.agentProfile
- }
- return state.agent
- });
- const scrollViewRef = useRef<any>(null);
- const messageList = useTextChat((state) => state.list);
-
- const {keyboardHeight, marginTopOffset, triggerHeightUpdate,} = useKeyboard(scrollViewRef, '#messageList', '#scrollView')
-
-
-
- const { destroy, setScrollTop, genSessionId, sessionId, setAutoScroll } = useTextChat();
- const scrollTop = useTextChat((state) => state.scrollTop);
- // const autoScroll = useTextChat((state) => state.autoScroll);
-
- const fetcher = async ([_url,{ nextId, pageSize}]) => {
- const _nextId = nextId ? decodeURIComponent(nextId) : nextId;
- const res = await getMessageHistories({
- agentId,
- startId: _nextId,
- pageSize,
- });
- return res.data;
- };
- const { list, loadMore, pageIndex, mutate } = useLoadMoreInfinite<TMessage[]|TRobotMessage[]>(
- createKey(`messeagehistories${isVisitor}${agentId}`),
- fetcher,
- );
- const parsedList = list.map((item: TMessage|TRobotMessage) => {
- if(item.contentType == EContentType.AiseekQA){
- try{
- const contentJson = JSON.parse(item.content as string)
- item.content = contentJson.answer.text
- // 把消息详情放入统一 body 中
- item.body = {...item, content: contentJson, contentType: EContentType.AiseekQA}
- }catch(e){
- // console.error(e)
- }
- }
- return item
- })
- const allMessages = useMemo(()=> [...[...parsedList].reverse(), ...messageList], [parsedList, messageList])
- const messagesLength = useMemo(() => allMessages.length, [allMessages.length]);
- const prevLengthRef = useRef(messagesLength);
- const [showWelcome, setShowWelcome] = useState(!list.length);
- // 加载更多
- const onScrollToUpper = () => {
- console.log("onscroll");
- loadMore();
- };
- const handleTouchMove = ()=> {
- console.log('set auto scroll false')
- setAutoScroll(false)
- }
- useDidShow(()=> {
- mutate(undefined,{revalidate: true})
- })
- useEffect(() => {
- if(agentId){
- if(isVisitor){
- fetchAgentProfile(agentId)
- }else{
- fetchAgent(agentId);
- }
- }
- }, [agentId, isVisitor]);
- // 是否显示欢迎 ui
- useEffect(() => {
- setShowWelcome(!messageList.length && !list.length);
- }, [list, messageList]);
- // 将 greeting 开场白存入聊天记录里
- // useEffect(()=> {
- // console.log(showWelcome, agent)
- // if(!showWelcome){
- // return;
- // }
- // const loginId = getLoginId();
- // if (!loginId || !agent?.agentId || !agent?.greeting || !sessionId) {
- // return;
- // }
- // saveMessageToServer({
- // loginId,
- // messages: [{
- // content: agent.greeting,
- // contentType: EContentType.TextPlain,
- // role: EChatRole.Assistant,
- // saveStatus: 2,
- // isStreaming: false,
- // msgUk: generateUUID(),
- // }],
- // agentId: agent.agentId,
- // sessionId,
- // })
- // }, [showWelcome, sessionId])
- // 首次进入界面滚动到底
- useEffect(() => {
- if (pageIndex === 1) {
- setTimeout(() => {
- setScrollTop();
- }, 300);
- }
- }, [pageIndex]);
- // 首次进入聊天生成 session id
- useEffect(() => {
- genSessionId();
- }, []);
- // 监听消息列表变化,触发键盘高度重新计算
- useEffect(() => {
- // 只在长度真正变化时才触发
- if (prevLengthRef.current !== messagesLength) {
- prevLengthRef.current = messagesLength;
-
- // 使用 setTimeout 确保 DOM 更新完成后再计算高度
- const timer = setTimeout(() => {
- triggerHeightUpdate();
- }, 100);
-
- return () => clearTimeout(timer);
- }
- }, [messagesLength, triggerHeightUpdate]);
- useUnload(() => {
- destroy();
- });
- const renderNavLeft = () => {
- return (
- <View
- className="flex items-center gap-8"
- onClick={() => Taro.navigateBack()}
- >
- <IconArrowLeftWhite24 />
- <PersonalCard agent={agent} size="mini" />
- </View>
- );
- };
-
- // 大背景可以是视频,也可以是图片
- const getBgContent = ()=> {
- if(!agent?.avatarUrl || !!!agent?.enabledChatBg){
- return ''
- }
- return agent?.avatarUrl
- }
- useEffect(()=> {
- Taro.setNavigationBarColor({
- frontColor: '#ffffff',
- backgroundColor: 'transparent'
- })
- return ()=> {
- Taro.setNavigationBarColor({
- frontColor: '#000000',
- backgroundColor: 'transparent'
- })
- }
- })
-
-
- return (
- <PageCustom fullPage style={{ overflow: "hidden" }} styleBg={getBgContent()}>
- <NavBarNormal blur leftColumn={renderNavLeft}>
- {/* <>{`${scrollTop}`}--{autoScroll ? 'true': 'false'}</> */}
- </NavBarNormal>
- <View
- className="flex flex-col w-full h-full relative z-10 flex-1"
- style={{ top: `${marginTopOffset}px` }}
- >
- <ScrollView
- ref={scrollViewRef}
- scrollY
- id="scrollView"
- style={{
- flex: 1,
- height: "1px", // 高度自适应
- }}
- scrollTop={scrollTop}
- scrollWithAnimation
- onScrollToUpper={onScrollToUpper}
- onScrollToLower={()=> setAutoScroll(true)}
- >
- <View id="messageList" className="flex flex-col gap-16 px-18" onTouchMove={handleTouchMove}>
- {showWelcome && <ChatGreeting agent={agent} />}
- {/* 复制 histories 再 reverse 否则会影响 state */}
- {allMessages.map((message) => {
- const reasoningContent = (message as any).reasoningContent || '';
- return (
- <ChatMessage
- key={message.msgUk}
- textReasoning={reasoningContent}
- agent={agent}
- role={message.role}
- text={message.content}
- message={message}
- ></ChatMessage>
- );
- })}
- </View>
- <View className="pb-40 pt-8">
- {(agent) && <RecommendQuestions agent={agent} />}
- </View>
- </ScrollView>
- <View className="w-full h-54">
- {/* 输入框高度占位块,todo: 改成动态获取输入框块高度 */}
- </View>
- <View
- className="bottom-bar px-16 pt-12 z-50"
- style={{
- bottom: `${keyboardHeight}px`,
- }}
- >
- <View className="bg-[#F5FAFF]">
- {agent && (
- <InputBar
- agent={agent}
- histories={list}
- setShowWelcome={setShowWelcome}
- ></InputBar>
- )}
- </View>
- </View>
- </View>
- </PageCustom>
- );
- }
|