Commit 7437b1d0 by Liu

feat: update chat answers

parent 8ca45a16
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
"@types/node": "^22.0.2", "@types/node": "^22.0.2",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"baseline-browser-mapping": "^2.8.31",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.9", "eslint-plugin-react-refresh": "^0.4.9",
...@@ -7811,6 +7812,16 @@ ...@@ -7811,6 +7812,16 @@
"node": "^4.5.0 || >= 5.9" "node": "^4.5.0 || >= 5.9"
} }
}, },
"node_modules/baseline-browser-mapping": {
"version": "2.8.31",
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz",
"integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/big.js": { "node_modules/big.js": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz", "resolved": "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz",
......
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
"@types/node": "^22.0.2", "@types/node": "^22.0.2",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"baseline-browser-mapping": "^2.8.31",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.9", "eslint-plugin-react-refresh": "^0.4.9",
......
...@@ -15,7 +15,7 @@ import { FilePreviewModal } from '@/components/FilePreviewModal' ...@@ -15,7 +15,7 @@ import { FilePreviewModal } from '@/components/FilePreviewModal'
interface ChatAnswerAttachmentProps { interface ChatAnswerAttachmentProps {
answer: Answer answer: Answer
isLastAnswer?: boolean isLastAnswer?: boolean
onSubmitQuestion?: (question: string, productCode?: string) => void onSubmitQuestion?: (question: string, productCode?: string, toolId?: string) => void
} }
export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({
......
...@@ -16,7 +16,7 @@ interface ChatAnswerBoxProps { ...@@ -16,7 +16,7 @@ interface ChatAnswerBoxProps {
showIndex: number showIndex: number
isLastAnswer: boolean isLastAnswer: boolean
index: number index: number
onSubmitQuestion: (question: string, productCode?: string) => void onSubmitQuestion: (question: string, productCode?: string, toolId?: string) => void
} }
export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, isLastAnswer, onSubmitQuestion }) => { export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, isLastAnswer, onSubmitQuestion }) => {
...@@ -51,9 +51,10 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -51,9 +51,10 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
return ( return (
<div> <div>
{innerRecord.answerList.map((item, index) => { {innerRecord.answerList.map((item, index) => {
const itemKey = item.recordId || item.messageId || item.answerId || `${innerRecord.conversationId}-${showIndex}`
return ( return (
index === showIndex && ( index === showIndex && (
<div className="chatItemBotContainer w-full" key={`${item.recordId}-${index}`}> <div className="chatItemBotContainer w-full" key={itemKey}>
<div className="flex"> <div className="flex">
<Avatar <Avatar
className="sm:mr-[20px] hidden sm:block flex-shrink-0" className="sm:mr-[20px] hidden sm:block flex-shrink-0"
......
...@@ -14,7 +14,7 @@ interface ChatAnswerParserProps { ...@@ -14,7 +14,7 @@ interface ChatAnswerParserProps {
isLastAnswer: boolean isLastAnswer: boolean
onTyping: () => void onTyping: () => void
onComplate: () => void onComplate: () => void
onSubmitQuestion: (question: string, productCode?: string) => void onSubmitQuestion: (question: string, productCode?: string, toolId?: string) => void
} }
function CheckIcon({ ...props }) { function CheckIcon({ ...props }) {
......
...@@ -3,15 +3,22 @@ import { Button, Skeleton } from '@heroui/react' ...@@ -3,15 +3,22 @@ import { Button, Skeleton } from '@heroui/react'
import type { Answer } from '@/types/chat' import type { Answer } from '@/types/chat'
import { fetchQueryRecommendQuestion } from '@/api/chat' import { fetchQueryRecommendQuestion } from '@/api/chat'
import SendIcon from '@/assets/svg/sendBlack.svg?react' import SendIcon from '@/assets/svg/sendBlack.svg?react'
import { useAppSelector } from '@/store/hook'
interface ChatAnswerRecommendProps { interface ChatAnswerRecommendProps {
answer: Answer answer: Answer
onSubmitQuestion: (question: string) => void onSubmitQuestion: (question: string, productCode?: string, toolId?: string) => void
} }
export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer, onSubmitQuestion }) => { export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer, onSubmitQuestion }) => {
let isGet = false let isGet = false
const [questionList, setQuestionList] = useState<string[]>([]) const [questionList, setQuestionList] = useState<string[]>([])
const [loading, setLoading] = useState<boolean>(false) const [loading, setLoading] = useState<boolean>(false)
const currentToolId = useAppSelector(state => state.conversation.currentToolId)
const handleRecommendClick = (question: string) => {
const toolIdParam = currentToolId || ''
onSubmitQuestion(question, undefined, toolIdParam)
}
const getAnswerRecommend = async () => { const getAnswerRecommend = async () => {
setLoading(true) setLoading(true)
const res = await fetchQueryRecommendQuestion(answer.conversationId || '', answer.recordId || '') const res = await fetchQueryRecommendQuestion(answer.conversationId || '', answer.recordId || '')
...@@ -32,8 +39,8 @@ export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer ...@@ -32,8 +39,8 @@ export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer
{!loading && questionList.length !== 0 && questionList.length > 0 && ( {!loading && questionList.length !== 0 && questionList.length > 0 && (
<div className="flex flex-col gap-[8px]"> <div className="flex flex-col gap-[8px]">
{ {
questionList.map((item, index) => ( questionList.map(item => (
<Button onPress={() => onSubmitQuestion(item)} key={index} color="primary" variant="light" className="text-left bg-[#fff] w-fit max-w-full text-[#333] rounded-[8px] data-[hover=true]:bg-[#F6F6F8] data-[hover=true]:text-[#333]"> <Button onPress={() => handleRecommendClick(item)} key={`${answer.recordId}-${item}`} color="primary" variant="light" className="text-left bg-[#fff] w-fit max-w-full text-[#333] rounded-[8px] data-[hover=true]:bg-[#F6F6F8] data-[hover=true]:text-[#333]">
<div className="w-full sm:w-full text-nowrap text-ellipsis overflow-hidden"> <div className="w-full sm:w-full text-nowrap text-ellipsis overflow-hidden">
{item} {item}
</div> </div>
......
...@@ -7,7 +7,7 @@ import { MarkdownDetail } from '@/components/MarkdownDetail' ...@@ -7,7 +7,7 @@ import { MarkdownDetail } from '@/components/MarkdownDetail'
interface ChatAnswerShowerProps { interface ChatAnswerShowerProps {
answer: Answer answer: Answer
isLastAnswer: boolean isLastAnswer: boolean
onSubmitQuestion: (question: string) => void onSubmitQuestion: (question: string, productCode?: string, toolId?: string) => void
} }
export const ChatAnswerShower: React.FC<ChatAnswerShowerProps> = ({ answer, isLastAnswer, onSubmitQuestion }) => { export const ChatAnswerShower: React.FC<ChatAnswerShowerProps> = ({ answer, isLastAnswer, onSubmitQuestion }) => {
......
...@@ -5,7 +5,6 @@ import { Outlet, useLocation } from 'react-router-dom' ...@@ -5,7 +5,6 @@ import { Outlet, useLocation } from 'react-router-dom'
import { useLocalStorageState } from 'ahooks' import { useLocalStorageState } from 'ahooks'
import styles from './Home.module.less' import styles from './Home.module.less'
import { QuestionList } from './components/QuestionList' import { QuestionList } from './components/QuestionList'
import HomeIcon1 from '@/assets/homeIcon1.png'
import HomeIcon2 from '@/assets/homeIcon2.png' import HomeIcon2 from '@/assets/homeIcon2.png'
import SmartIce from '@/assets/smart-ice.png' import SmartIce from '@/assets/smart-ice.png'
import { clearCurrentToolId, createConversation, fetchConversations, setCurrentToolId } from '@/store/conversationSlice' import { clearCurrentToolId, createConversation, fetchConversations, setCurrentToolId } from '@/store/conversationSlice'
...@@ -48,10 +47,7 @@ export const Home: React.FC = () => { ...@@ -48,10 +47,7 @@ export const Home: React.FC = () => {
const location = useLocation() const location = useLocation()
const hasFetched = useRef(false) const hasFetched = useRef(false)
// 使用 useState // 使用 useState
const [productQuestions, setProductQuestions] = useState<any>({ content: [] })
const [otherQuestions, setOtherQuestions] = useState<any>({ content: [] }) const [otherQuestions, setOtherQuestions] = useState<any>({ content: [] })
const [isToolBtnActive, setIsToolBtnActive] = useState<boolean>(false)
const [shouldChangeStyle, setShouldChangeStyle] = useState<boolean>(false)
// 保存原始的configType为07的数据 // 保存原始的configType为07的数据
const [originalOtherQuestions, setOriginalOtherQuestions] = useState<any>({ content: [] }) const [originalOtherQuestions, setOriginalOtherQuestions] = useState<any>({ content: [] })
...@@ -59,6 +55,32 @@ export const Home: React.FC = () => { ...@@ -59,6 +55,32 @@ export const Home: React.FC = () => {
defaultValue: '', defaultValue: '',
}) })
const fetchGeneralQuestions = useCallback(
async (fallbackData?: any) => {
try {
const res = await fetchEfficiencyQuestionList({ toolId: '' })
if (res && res.data && res.data.questions) {
setOtherQuestions((prev: any) => ({
...prev,
content: res.data.questions || [],
}))
return
}
}
catch (error) {
console.error('获取通用问题失败:', error)
}
// 如果接口无数据或失败,显示空态但保留基础信息
const baseData = fallbackData || originalOtherQuestions
setOtherQuestions((prev: any) => ({
...prev,
...baseData,
content: [],
}))
},
[originalOtherQuestions],
)
/** 获取qa记录 */ /** 获取qa记录 */
const getQuestionList = useCallback(async () => { const getQuestionList = useCallback(async () => {
setIsLoading(true) setIsLoading(true)
...@@ -70,14 +92,11 @@ export const Home: React.FC = () => { ...@@ -70,14 +92,11 @@ export const Home: React.FC = () => {
if (res && res.data) { if (res && res.data) {
for (let index = 0; index < res.data.length; index++) { for (let index = 0; index < res.data.length; index++) {
const element = res.data[index] const element = res.data[index]
if (element.configType === '06') {
element.content = JSON.parse(element.content)
setProductQuestions(element)
}
if (element.configType === '07') { if (element.configType === '07') {
element.content = JSON.parse(element.content) element.content = JSON.parse(element.content)
setOtherQuestions(element) setOtherQuestions(element)
setOriginalOtherQuestions(element) // 保存原始数据 setOriginalOtherQuestions(element) // 保存原始数据
await fetchGeneralQuestions(element)
} }
} }
} }
...@@ -90,7 +109,7 @@ finally { ...@@ -90,7 +109,7 @@ finally {
setIsLoading(false) setIsLoading(false)
setIsDataLoaded(true) setIsDataLoaded(true)
} }
}, []) }, [fetchGeneralQuestions])
const initConversation = () => { const initConversation = () => {
const fromCollect = location.state?.fromCollect const fromCollect = location.state?.fromCollect
...@@ -138,11 +157,12 @@ finally { ...@@ -138,11 +157,12 @@ finally {
} }
} }
else if (isToolBtn) { else if (isToolBtn) {
// 通用模式:恢复刷新时的状态,包括左侧内容也要恢复到初始状态 // 通用模式:调用接口获取通用问题,toolId 传空字符串
setOtherQuestions(originalOtherQuestions) setIsDataLoaded(false)
setIsDataLoaded(true) // 恢复原始数据时标记为已加载 await fetchGeneralQuestions()
setIsDataLoaded(true)
} }
}, [originalOtherQuestions]) }, [originalOtherQuestions, fetchGeneralQuestions])
// 进入页面时,如果路由中有 toolId 且不为空,执行工具点击逻辑 // 进入页面时,如果路由中有 toolId 且不为空,执行工具点击逻辑
useEffect(() => { useEffect(() => {
...@@ -152,8 +172,6 @@ finally { ...@@ -152,8 +172,6 @@ finally {
if (toolIdFromUrl && toolIdFromUrl !== '') { if (toolIdFromUrl && toolIdFromUrl !== '') {
// 有 toolId,执行工具点击逻辑 // 有 toolId,执行工具点击逻辑
dispatch(setCurrentToolId(toolIdFromUrl)) dispatch(setCurrentToolId(toolIdFromUrl))
setShouldChangeStyle(true)
setIsToolBtnActive(false)
_handleToolClick(false, toolIdFromUrl) _handleToolClick(false, toolIdFromUrl)
} }
} }
...@@ -162,33 +180,16 @@ finally { ...@@ -162,33 +180,16 @@ finally {
// 监听工具按钮点击事件 // 监听工具按钮点击事件
useEffect(() => { useEffect(() => {
const handleToolClickEvent = (event: CustomEvent) => { const handleToolClickEvent = (event: CustomEvent) => {
const { isToolBtn, toolId, shouldChangeStyle: shouldChangeStyleParam } = event.detail const { isToolBtn, toolId } = event.detail
// eslint-disable-next-line no-console
console.log('🔧 [Home] 工具按钮点击事件触发:', {
isToolBtn,
toolId,
shouldChangeStyle: shouldChangeStyleParam,
当前isToolBtnActive: isToolBtnActive,
当前shouldChangeStyle: shouldChangeStyle,
})
setIsToolBtnActive(isToolBtn)
// 更新样式控制状态
if (shouldChangeStyleParam !== undefined) {
setShouldChangeStyle(shouldChangeStyleParam)
}
// 保存当前选择的 toolId 到 Redux // 保存当前选择的 toolId 到 Redux
if (!isToolBtn && toolId) { if (!isToolBtn && toolId) {
// 提质增效模式,保存 toolId // 提质增效模式,保存 toolId
dispatch(setCurrentToolId(toolId)) dispatch(setCurrentToolId(toolId))
// eslint-disable-next-line no-console
console.log('✅ [Home] 已保存 toolId 到 Redux:', toolId)
} }
else { else {
// 通用模式,清除 toolId // 通用模式,清除 toolId
dispatch(clearCurrentToolId()) dispatch(clearCurrentToolId())
// eslint-disable-next-line no-console
console.log('🔄 [Home] 已清除 Redux 中的 toolId')
} }
_handleToolClick(isToolBtn, toolId) _handleToolClick(isToolBtn, toolId)
...@@ -197,7 +198,7 @@ finally { ...@@ -197,7 +198,7 @@ finally {
return () => { return () => {
window.removeEventListener('toolButtonClick', handleToolClickEvent as EventListener) window.removeEventListener('toolButtonClick', handleToolClickEvent as EventListener)
} }
}, [_handleToolClick, isToolBtnActive, shouldChangeStyle, dispatch]) }, [_handleToolClick, dispatch])
const login = useCallback(async () => { const login = useCallback(async () => {
// 防止重复调用 // 防止重复调用
...@@ -275,41 +276,30 @@ finally { ...@@ -275,41 +276,30 @@ finally {
<div className="flex-1 items-center pt-[24px] sm:pt-[32px] scrollbar-hide"> <div className="flex-1 items-center pt-[24px] sm:pt-[32px] scrollbar-hide">
<div className="w-full"> <div className="w-full">
<div className="flex justify-center gap-[20px]"> <div className="flex justify-center gap-[20px]">
{/* 左侧区域 - 产品问答和您可以试着问我 */} {/* 左侧区域 - 常见问题 */}
<div <div
className="flex flex-col gap-[20px] items-center overflow-y-auto scrollbar-hide" className="flex flex-col gap-[20px] items-center overflow-y-auto scrollbar-hide"
style={{ height: shouldChangeStyle ? 'calc(-64px + 100vh)' : '500px', background: shouldChangeStyle ? 'linear-gradient(180deg, #F0F8FF 0%, #FFFFFF 50%, #FFFFFF 100%)' : '', borderRadius: '24px' }} style={{
height: 'calc(-64px + 100vh)',
background: 'linear-gradient(180deg, #F0F8FF 0%, #FFFFFF 50%, #FFFFFF 100%)',
borderRadius: '24px',
}}
> >
{!shouldChangeStyle && (
<motion.div className="w-full sm:w-auto" {...getAnimationProps(2)}>
<QuestionList
questions={productQuestions.content}
dotColor="#D4CCFF"
background="linear-gradient(180deg, #EBE6FF 0%, #FFFFFF 50%, #FFFFFF 100%)"
title={productQuestions.configName}
iconImg={HomeIcon1}
isToolBtn={shouldChangeStyle}
isLoaded={isDataLoaded}
/>
</motion.div>
)}
<motion.div className="w-full sm:w-auto" {...getAnimationProps(3)}> <motion.div className="w-full sm:w-auto" {...getAnimationProps(3)}>
<QuestionList <QuestionList
questions={otherQuestions.content} questions={otherQuestions.content}
dotColor="#CBECFF" dotColor="#CBECFF"
background="linear-gradient(180deg, #F0F8FF 0%, #FFFFFF 50%, #FFFFFF 100%)" background="linear-gradient(180deg, #F0F8FF 0%, #FFFFFF 50%, #FFFFFF 100%)"
height={shouldChangeStyle ? '288px' : 'auto'} height="288px"
title={otherQuestions.configName} title={otherQuestions.configName}
iconImg={HomeIcon2} iconImg={HomeIcon2}
isToolBtn={shouldChangeStyle} isToolBtn
isLoaded={isDataLoaded} isLoaded={isDataLoaded}
/> />
</motion.div> </motion.div>
{shouldChangeStyle && ( <div>
<div> <img src={SmartIce} alt="Smart Ice" className="w-[260px] h-[218px] mt-[-12px] object-contain" />
<img src={SmartIce} alt="Smart Ice" className="w-[260px] h-[218px] mt-[-12px] object-contain" /> </div>
</div>
)}
</div> </div>
{/* 右侧区域 */} {/* 右侧区域 */}
<div className="hidden sm:flex flex-1 h-full"> <div className="hidden sm:flex flex-1 h-full">
......
...@@ -139,7 +139,7 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ ...@@ -139,7 +139,7 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({
</div> </div>
</div> </div>
{showRefresh {showRefresh
? ( ? (
<div onClick={handleRefresh} className="flex-shrink-0 ml-[8px] flex items-center gap-[4px]"> <div onClick={handleRefresh} className="flex-shrink-0 ml-[8px] flex items-center gap-[4px]">
<div className="cursor-pointer"> <div className="cursor-pointer">
<motion.div <motion.div
...@@ -155,18 +155,18 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ ...@@ -155,18 +155,18 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({
</div> </div>
<div className="text-[12px] text-[#29B6FD] cursor-pointer">换一换</div> <div className="text-[12px] text-[#29B6FD] cursor-pointer">换一换</div>
</div> </div>
) )
: null} : null}
</h3> </h3>
{isLoaded && questions && questions.length === 0 {isLoaded && questions && questions.length === 0
? ( ? (
<div className="mt-[34px] flex flex-col items-center justify-center h-[200px]"> <div className="mt-[34px] flex flex-col items-center justify-center h-[200px]">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Image src={emptyIcon} alt="空数据" className="w-[72px] h-[72px]" /> <Image src={emptyIcon} alt="空数据" className="w-[72px] h-[72px]" />
<div className="mt-[16px] text-[14px] text-[#27353C]">问题正在路上...</div> <div className="mt-[16px] text-[14px] text-[#27353C]">问题正在路上...</div>
</div> </div>
</div> </div>
) )
: ( : (
<motion.ul <motion.ul
key={displayedItems.join(',')} key={displayedItems.join(',')}
...@@ -193,14 +193,17 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ ...@@ -193,14 +193,17 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({
className="text-left bg-[#F8FBFF] w-full text-[#333] rounded-[23px] data-[hover=true]:bg-[#F0F8FF] data-[hover=true]:text-primary h-8" className="text-left bg-[#F8FBFF] w-full text-[#333] rounded-[23px] data-[hover=true]:bg-[#F0F8FF] data-[hover=true]:text-primary h-8"
> >
<div className="w-full text-nowrap text-ellipsis overflow-hidden"> <div className="w-full text-nowrap text-ellipsis overflow-hidden">
<span className="w-[6px] h-[6px] rounded-full inline-block mr-[6px]" style={{ backgroundColor: dotColor }}></span> <span
className="w-[6px] h-[6px] rounded-full inline-block mr-[6px]"
style={{ backgroundColor: dotColor }}
/>
<span className="ml-[8px]">{item}</span> <span className="ml-[8px]">{item}</span>
</div> </div>
</Button> </Button>
</motion.li> </motion.li>
))} ))}
</motion.ul> </motion.ul>
)} )}
</div> </div>
) )
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment