diff --git a/web/app/(shareLayout)/chat/[token]/page.tsx b/web/app/(shareLayout)/chat/[token]/page.tsx index 56b2e0da7d..640c40378f 100644 --- a/web/app/(shareLayout)/chat/[token]/page.tsx +++ b/web/app/(shareLayout)/chat/[token]/page.tsx @@ -1,11 +1,8 @@ 'use client' -import type { FC } from 'react' import React from 'react' - -import type { IMainProps } from '@/app/components/share/chat' import ChatWithHistoryWrap from '@/app/components/base/chat/chat-with-history' -const Chat: FC = () => { +const Chat = () => { return ( ) diff --git a/web/app/(shareLayout)/chatbot/[token]/page.tsx b/web/app/(shareLayout)/chatbot/[token]/page.tsx index b78680c503..1db5c7e458 100644 --- a/web/app/(shareLayout)/chatbot/[token]/page.tsx +++ b/web/app/(shareLayout)/chatbot/[token]/page.tsx @@ -1,14 +1,12 @@ 'use client' -import type { FC } from 'react' import React, { useEffect } from 'react' import cn from 'classnames' -import type { IMainProps } from '@/app/components/share/chat' import EmbeddedChatbot from '@/app/components/base/chat/embedded-chatbot' import Loading from '@/app/components/base/loading' import { fetchSystemFeatures } from '@/service/share' import LogoSite from '@/app/components/base/logo/logo-site' -const Chatbot: FC = () => { +const Chatbot = () => { const [isSSOEnforced, setIsSSOEnforced] = React.useState(true) const [loading, setLoading] = React.useState(true) diff --git a/web/app/(shareLayout)/completion/[token]/page.tsx b/web/app/(shareLayout)/completion/[token]/page.tsx index 28bbfa68da..e8bc9d79f5 100644 --- a/web/app/(shareLayout)/completion/[token]/page.tsx +++ b/web/app/(shareLayout)/completion/[token]/page.tsx @@ -1,13 +1,10 @@ -import type { FC } from 'react' import React from 'react' - -import type { IMainProps } from '@/app/components/share/chat' import Main from '@/app/components/share/text-generation' -const TextGeneration: FC = () => { +const Completion = () => { return (
) } -export default React.memo(TextGeneration) +export default React.memo(Completion) diff --git a/web/app/(shareLayout)/workflow/[token]/page.tsx b/web/app/(shareLayout)/workflow/[token]/page.tsx index c1d7fa13a5..e93bc8c1af 100644 --- a/web/app/(shareLayout)/workflow/[token]/page.tsx +++ b/web/app/(shareLayout)/workflow/[token]/page.tsx @@ -1,13 +1,11 @@ -import type { FC } from 'react' import React from 'react' -import type { IMainProps } from '@/app/components/share/text-generation' import Main from '@/app/components/share/text-generation' -const TextGeneration: FC = () => { +const Workflow = () => { return (
) } -export default React.memo(TextGeneration) +export default React.memo(Workflow) diff --git a/web/app/components/app/chat/answer/index.tsx b/web/app/components/app/chat/answer/index.tsx deleted file mode 100644 index 1ba033911a..0000000000 --- a/web/app/components/app/chat/answer/index.tsx +++ /dev/null @@ -1,428 +0,0 @@ -'use client' -import type { FC, ReactNode } from 'react' -import React, { useEffect, useMemo, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { UserCircleIcon } from '@heroicons/react/24/solid' -import cn from 'classnames' -import type { CitationItem, DisplayScene, FeedbackFunc, Feedbacktype, IChatItem } from '../type' -import OperationBtn from '../operation' -import LoadingAnim from '../loading-anim' -import { RatingIcon } from '../icon-component' -import s from '../style.module.css' -import MoreInfo from '../more-info' -import CopyBtn from '../copy-btn' -import Thought from '../thought' -import Citation from '../citation' -import AudioBtn from '@/app/components/base/audio-btn' -import { randomString } from '@/utils' -import type { MessageRating } from '@/models/log' -import Tooltip from '@/app/components/base/tooltip' -import { Markdown } from '@/app/components/base/markdown' -import type { DataSet } from '@/models/datasets' -import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn' -import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal' -import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item' -import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' -import type { Emoji } from '@/app/components/tools/types' -import type { VisionFile } from '@/types/app' -import ImageGallery from '@/app/components/base/image-gallery' -import Log from '@/app/components/app/chat/log' - -const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => { - return
- {children} -
-} -export type IAnswerProps = { - item: IChatItem - index: number - feedbackDisabled: boolean - isHideFeedbackEdit: boolean - onQueryChange: (query: string) => void - onFeedback?: FeedbackFunc - displayScene: DisplayScene - isResponding?: boolean - answerIcon?: ReactNode - citation?: CitationItem[] - dataSets?: DataSet[] - isShowCitation?: boolean - isShowCitationHitInfo?: boolean - isShowTextToSpeech?: boolean - // Annotation props - supportAnnotation?: boolean - appId?: string - question: string - onAnnotationEdited?: (question: string, answer: string, index: number) => void - onAnnotationAdded?: (annotationId: string, authorName: string, question: string, answer: string, index: number) => void - onAnnotationRemoved?: (index: number) => void - allToolIcons?: Record - isShowPromptLog?: boolean -} -// The component needs to maintain its own state to control whether to display input component -const Answer: FC = ({ - item, - index, - onQueryChange, - feedbackDisabled = false, - isHideFeedbackEdit = false, - onFeedback, - displayScene = 'web', - isResponding, - answerIcon, - citation, - isShowCitation, - isShowCitationHitInfo = false, - isShowTextToSpeech, - supportAnnotation, - appId, - question, - onAnnotationEdited, - onAnnotationAdded, - onAnnotationRemoved, - allToolIcons, - isShowPromptLog, -}) => { - const { id, content, more, feedback, adminFeedback, annotation, agent_thoughts } = item - const isAgentMode = !!agent_thoughts && agent_thoughts.length > 0 - const hasAnnotation = useMemo(() => !!annotation, [annotation]) - // const [annotation, setAnnotation] = useState(initAnnotation) - // const [inputValue, setInputValue] = useState(initAnnotation?.content ?? '') - const [localAdminFeedback, setLocalAdminFeedback] = useState(adminFeedback) - // const { userProfile } = useContext(AppContext) - const { t } = useTranslation() - - const [isShowReplyModal, setIsShowReplyModal] = useState(false) - - /** - * Render feedback results (distinguish between users and administrators) - * User reviews cannot be cancelled in Console - * @param rating feedback result - * @param isUserFeedback Whether it is user's feedback - * @param isWebScene Whether it is web scene - * @returns comp - */ - const renderFeedbackRating = (rating: MessageRating | undefined, isUserFeedback = true, isWebScene = true) => { - if (!rating) - return null - - const isLike = rating === 'like' - const ratingIconClassname = isLike ? 'text-primary-600 bg-primary-100 hover:bg-primary-200' : 'text-red-600 bg-red-100 hover:bg-red-200' - const UserSymbol = - // The tooltip is always displayed, but the content is different for different scenarios. - return ( - -
{ - const res = await onFeedback?.(id, { rating: null }) - if (res && !isWebScene) - setLocalAdminFeedback({ rating: null }) - }, - } - : {})} - > -
- -
- {!isWebScene && isUserFeedback && UserSymbol} -
-
- ) - } - - /** - * Different scenarios have different operation items. - * @param isWebScene Whether it is web scene - * @returns comp - */ - const renderItemOperation = (isWebScene = true) => { - const userOperation = () => { - return feedback?.rating - ? null - :
- - {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'like' }) })} - - - {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'dislike' }) })} - -
- } - - const adminOperation = () => { - return
- {!localAdminFeedback?.rating && <> - - {OperationBtn({ - innerContent: , - onClick: async () => { - const res = await onFeedback?.(id, { rating: 'like' }) - if (res) - setLocalAdminFeedback({ rating: 'like' }) - }, - })} - - - {OperationBtn({ - innerContent: , - onClick: async () => { - const res = await onFeedback?.(id, { rating: 'dislike' }) - if (res) - setLocalAdminFeedback({ rating: 'dislike' }) - }, - })} - - } -
- } - - return ( -
- {isWebScene ? userOperation() : adminOperation()} -
- ) - } - - const getImgs = (list?: VisionFile[]) => { - if (!list) - return [] - return list.filter(file => file.type === 'image' && file.belongs_to === 'assistant') - } - - const agentModeAnswer = ( -
- {agent_thoughts?.map((item, index) => ( -
- {item.thought && ( - - )} - {/* {item.tool} */} - {/* perhaps not use tool */} - {!!item.tool && ( - - )} - - {getImgs(item.message_files).length > 0 && ( - item.url)} /> - )} -
- ))} -
- ) - - const [containerWidth, setContainerWidth] = useState(0) - const [contentWidth, setContentWidth] = useState(0) - const containerRef = useRef(null) - const contentRef = useRef(null) - - const getContainerWidth = () => { - if (containerRef.current) - setContainerWidth(containerRef.current?.clientWidth + 24) - } - const getContentWidth = () => { - if (contentRef.current) - setContentWidth(contentRef.current?.clientWidth) - } - - useEffect(() => { - getContainerWidth() - }, []) - - useEffect(() => { - if (!isResponding) - getContentWidth() - }, [isResponding]) - - const operationWidth = useMemo(() => { - let width = 0 - if (!item.isOpeningStatement) - width += 28 - if (!item.isOpeningStatement && isShowPromptLog) - width += 102 + 8 - if (!item.isOpeningStatement && isShowTextToSpeech) - width += 33 - if (!item.isOpeningStatement && supportAnnotation) - width += 96 + 8 - if (!feedbackDisabled && !item.feedbackDisabled) - width += 60 + 8 - if (!feedbackDisabled && localAdminFeedback?.rating && !item.isOpeningStatement) - width += 60 + 8 - if (!feedbackDisabled && feedback?.rating && !item.isOpeningStatement) - width += 28 + 8 - return width - }, [item.isOpeningStatement, item.feedbackDisabled, isShowPromptLog, isShowTextToSpeech, supportAnnotation, feedbackDisabled, localAdminFeedback?.rating, feedback?.rating]) - - const positionRight = useMemo(() => operationWidth < containerWidth - contentWidth - 4, [operationWidth, containerWidth, contentWidth]) - - return ( - // data-id for debug the item message is right -
-
- { - answerIcon || ( -
- {isResponding - &&
- -
- } -
- ) - } -
-
-
-
- {(isResponding && (isAgentMode ? (!content && (agent_thoughts || []).filter(item => !!item.thought || !!item.tool).length === 0) : !content)) - ? ( -
- -
- ) - : ( -
- {annotation?.logAnnotation && ( -
-
- {isAgentMode - ? (
{agentModeAnswer}
) - : ( - - )} -
- -
- )} -
- {annotation?.logAnnotation - ? ( - - ) - : (isAgentMode - ? agentModeAnswer - : ( - - ))} -
- {(hasAnnotation && !annotation?.logAnnotation) && ( - - )} - {item.isOpeningStatement && item.suggestedQuestions && item.suggestedQuestions.filter(q => !!q && q.trim()).length > 0 && ( -
- {item.suggestedQuestions.filter(q => !!q && q.trim()).map((question, index) => ( -
onQueryChange(question)} - > - {question} -
), - )} -
- )} -
- )} - { - !!citation?.length && isShowCitation && !isResponding && ( - - ) - } -
- {hasAnnotation && ( -
-
- -
-
- )} -
- {!item.isOpeningStatement && ( - - )} - {((isShowPromptLog && !isResponding) || (!item.isOpeningStatement && isShowTextToSpeech)) && ( -
- {isShowPromptLog && !isResponding && ( - - )} - {!item.isOpeningStatement && isShowTextToSpeech && ( - <> -
- - - )} -
- )} - {(!item.isOpeningStatement && supportAnnotation) && ( - onAnnotationAdded?.(id, authorName, question, content, index)} - onEdit={() => setIsShowReplyModal(true)} - onRemoved={() => onAnnotationRemoved!(index)} - /> - )} - - setIsShowReplyModal(false)} - query={question} - answer={content} - onEdited={(editedQuery, editedAnswer) => onAnnotationEdited!(editedQuery, editedAnswer, index)} - onAdded={(annotationId, authorName, editedQuery, editedAnswer) => onAnnotationAdded!(annotationId, authorName, editedQuery, editedAnswer, index)} - appId={appId!} - messageId={id} - annotationId={annotation?.id || ''} - createdAt={annotation?.created_at} - onRemove={() => { }} - /> - - {!feedbackDisabled && !item.feedbackDisabled && renderItemOperation(displayScene !== 'console')} - {/* Admin feedback is displayed only in the background. */} - {!feedbackDisabled && renderFeedbackRating(localAdminFeedback?.rating, false, false)} - {/* User feedback must be displayed */} - {!feedbackDisabled && renderFeedbackRating(feedback?.rating, !isHideFeedbackEdit, displayScene !== 'console')} -
-
- {more && } -
-
-
-
- ) -} -export default React.memo(Answer) diff --git a/web/app/components/app/chat/icon-component/index.tsx b/web/app/components/app/chat/icon-component/index.tsx deleted file mode 100644 index c35fb77855..0000000000 --- a/web/app/components/app/chat/icon-component/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import type { FC, SVGProps } from 'react' -import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' - -export const stopIcon = ( - - - -) - -export const OpeningStatementIcon = ({ className }: SVGProps) => ( - - - -) - -export const RatingIcon: FC<{ isLike: boolean }> = ({ isLike }) => { - return isLike ? : -} - -export const EditIcon = ({ className }: SVGProps) => { - return - - -} - -export const EditIconSolid = ({ className }: SVGProps) => { - return - - - -} - -export const TryToAskIcon = ( - - - -) - -export const ReplayIcon = ({ className }: SVGProps) => ( - - - -) diff --git a/web/app/components/app/chat/icons/answer.svg b/web/app/components/app/chat/icons/answer.svg deleted file mode 100644 index e983039306..0000000000 --- a/web/app/components/app/chat/icons/answer.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/app/components/app/chat/icons/default-avatar.jpg b/web/app/components/app/chat/icons/default-avatar.jpg deleted file mode 100644 index 396d5dd291..0000000000 Binary files a/web/app/components/app/chat/icons/default-avatar.jpg and /dev/null differ diff --git a/web/app/components/app/chat/icons/edit.svg b/web/app/components/app/chat/icons/edit.svg deleted file mode 100644 index a922970b6c..0000000000 --- a/web/app/components/app/chat/icons/edit.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/web/app/components/app/chat/icons/question.svg b/web/app/components/app/chat/icons/question.svg deleted file mode 100644 index 39904f52a5..0000000000 --- a/web/app/components/app/chat/icons/question.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/app/components/app/chat/icons/robot.svg b/web/app/components/app/chat/icons/robot.svg deleted file mode 100644 index a50c8886d1..0000000000 --- a/web/app/components/app/chat/icons/robot.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/web/app/components/app/chat/icons/send-active.svg b/web/app/components/app/chat/icons/send-active.svg deleted file mode 100644 index 03d4734bc6..0000000000 --- a/web/app/components/app/chat/icons/send-active.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/app/components/app/chat/icons/send.svg b/web/app/components/app/chat/icons/send.svg deleted file mode 100644 index e977ef95bb..0000000000 --- a/web/app/components/app/chat/icons/send.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/app/components/app/chat/icons/typing.svg b/web/app/components/app/chat/icons/typing.svg deleted file mode 100644 index 7b28f0ef78..0000000000 --- a/web/app/components/app/chat/icons/typing.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/web/app/components/app/chat/icons/user.svg b/web/app/components/app/chat/icons/user.svg deleted file mode 100644 index 556aaf7bfb..0000000000 --- a/web/app/components/app/chat/icons/user.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/web/app/components/app/chat/index.tsx b/web/app/components/app/chat/index.tsx deleted file mode 100644 index d861ddb2de..0000000000 --- a/web/app/components/app/chat/index.tsx +++ /dev/null @@ -1,455 +0,0 @@ -'use client' -import type { FC, ReactNode } from 'react' -import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' -import Textarea from 'rc-textarea' -import { useContext } from 'use-context-selector' -import cn from 'classnames' -import Recorder from 'js-audio-recorder' -import { useTranslation } from 'react-i18next' -import s from './style.module.css' -import type { DisplayScene, FeedbackFunc, IChatItem } from './type' -import { TryToAskIcon, stopIcon } from './icon-component' -import Answer from './answer' -import Question from './question' -import TooltipPlus from '@/app/components/base/tooltip-plus' -import { ToastContext } from '@/app/components/base/toast' -import Button from '@/app/components/base/button' -import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' -import VoiceInput from '@/app/components/base/voice-input' -import { Microphone01 } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' -import { Microphone01 as Microphone01Solid } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' -import { XCircle } from '@/app/components/base/icons/src/vender/solid/general' -import type { DataSet } from '@/models/datasets' -import ChatImageUploader from '@/app/components/base/image-uploader/chat-image-uploader' -import ImageList from '@/app/components/base/image-uploader/image-list' -import { TransferMethod, type VisionFile, type VisionSettings } from '@/types/app' -import { useClipboardUploader, useDraggableUploader, useImageFiles } from '@/app/components/base/image-uploader/hooks' -import type { Annotation } from '@/models/log' -import type { Emoji } from '@/app/components/tools/types' - -export type IChatProps = { - appId?: string - configElem?: React.ReactNode - chatList: IChatItem[] - onChatListChange?: (chatList: IChatItem[]) => void - controlChatUpdateAllConversation?: number - /** - * Whether to display the editing area and rating status - */ - feedbackDisabled?: boolean - /** - * Whether to display the input area - */ - isHideFeedbackEdit?: boolean - isHideSendInput?: boolean - onFeedback?: FeedbackFunc - checkCanSend?: () => boolean - query?: string - onQueryChange?: (query: string) => void - onSend?: (message: string, files: VisionFile[]) => void - displayScene?: DisplayScene - useCurrentUserAvatar?: boolean - isResponding?: boolean - canStopResponding?: boolean - abortResponding?: () => void - controlClearQuery?: number - controlFocus?: number - isShowSuggestion?: boolean - suggestionList?: string[] - isShowSpeechToText?: boolean - isShowTextToSpeech?: boolean - isShowCitation?: boolean - answerIcon?: ReactNode - isShowConfigElem?: boolean - dataSets?: DataSet[] - isShowCitationHitInfo?: boolean - isShowPromptLog?: boolean - visionConfig?: VisionSettings - supportAnnotation?: boolean - allToolIcons?: Record - customDisclaimer?: string -} - -const Chat: FC = ({ - configElem, - chatList, - query = '', - onQueryChange = () => { }, - feedbackDisabled = false, - isHideFeedbackEdit = false, - isHideSendInput = false, - onFeedback, - checkCanSend, - onSend = () => { }, - displayScene, - useCurrentUserAvatar, - isResponding, - canStopResponding, - abortResponding, - controlClearQuery, - controlFocus, - isShowSuggestion, - suggestionList, - isShowSpeechToText, - isShowTextToSpeech, - isShowCitation, - answerIcon, - isShowConfigElem, - dataSets, - isShowCitationHitInfo, - isShowPromptLog, - visionConfig, - appId, - supportAnnotation, - onChatListChange, - allToolIcons, - customDisclaimer, -}) => { - const { t } = useTranslation() - const { notify } = useContext(ToastContext) - const { - files, - onUpload, - onRemove, - onReUpload, - onImageLinkLoadError, - onImageLinkLoadSuccess, - onClear, - } = useImageFiles() - const { onPaste } = useClipboardUploader({ onUpload, visionConfig, files }) - const { onDragEnter, onDragLeave, onDragOver, onDrop, isDragActive } = useDraggableUploader({ onUpload, files, visionConfig }) - const isUseInputMethod = useRef(false) - - const handleContentChange = (e: React.ChangeEvent) => { - const value = e.target.value - onQueryChange(value) - } - - const logError = (message: string) => { - notify({ type: 'error', message, duration: 3000 }) - } - - const valid = (q?: string) => { - const sendQuery = q || query - if (!sendQuery || sendQuery.trim() === '') { - logError('Message cannot be empty') - return false - } - return true - } - - useEffect(() => { - if (controlClearQuery) - onQueryChange('') - }, [controlClearQuery]) - - const handleSend = (q?: string) => { - if (!valid(q) || (checkCanSend && !checkCanSend())) - return - onSend(q || query, files.filter(file => file.progress !== -1).map(fileItem => ({ - type: 'image', - transfer_method: fileItem.type, - url: fileItem.url, - upload_file_id: fileItem.fileId, - }))) - if (!files.find(item => item.type === TransferMethod.local_file && !item.fileId)) { - if (files.length) - onClear() - if (!isResponding) - onQueryChange('') - } - } - - const handleKeyUp = (e: React.KeyboardEvent) => { - if (e.code === 'Enter') { - e.preventDefault() - // prevent send message when using input method enter - if (!e.shiftKey && !isUseInputMethod.current) - handleSend() - } - } - - const handleKeyDown = (e: React.KeyboardEvent) => { - isUseInputMethod.current = e.nativeEvent.isComposing - if (e.code === 'Enter' && !e.shiftKey) { - onQueryChange(query.replace(/\n$/, '')) - e.preventDefault() - } - } - - const media = useBreakpoints() - const isMobile = media === MediaType.mobile - const sendBtn =
handleSend()}>
- - const suggestionListRef = useRef(null) - const [hasScrollbar, setHasScrollbar] = useState(false) - useLayoutEffect(() => { - if (suggestionListRef.current) { - const listDom = suggestionListRef.current - const hasScrollbar = listDom.scrollWidth > listDom.clientWidth - setHasScrollbar(hasScrollbar) - } - }, [suggestionList]) - - const [voiceInputShow, setVoiceInputShow] = useState(false) - const handleVoiceInputShow = () => { - (Recorder as any).getPermission().then(() => { - setVoiceInputShow(true) - }, () => { - logError(t('common.voiceInput.notAllow')) - }) - } - const handleQueryChangeFromAnswer = useCallback((val: string) => { - onQueryChange(val) - handleSend(val) - }, []) - const handleAnnotationEdited = useCallback((query: string, answer: string, index: number) => { - onChatListChange?.(chatList.map((item, i) => { - if (i === index - 1) { - return { - ...item, - content: query, - } - } - if (i === index) { - return { - ...item, - annotation: { - ...item.annotation, - logAnnotation: { - ...item.annotation?.logAnnotation, - content: answer, - }, - } as any, - } - } - return item - })) - }, [chatList]) - const handleAnnotationAdded = useCallback((annotationId: string, authorName: string, query: string, answer: string, index: number) => { - onChatListChange?.(chatList.map((item, i) => { - if (i === index - 1) { - return { - ...item, - content: query, - } - } - if (i === index) { - const answerItem = { - ...item, - content: item.content, - annotation: { - id: annotationId, - authorName, - logAnnotation: { - content: answer, - account: { - id: '', - name: authorName, - email: '', - }, - }, - } as Annotation, - } - return answerItem - } - return item - })) - }, [chatList]) - const handleAnnotationRemoved = useCallback((index: number) => { - onChatListChange?.(chatList.map((item, i) => { - if (i === index) { - return { - ...item, - content: item.content, - annotation: undefined, - } - } - return item - })) - }, [chatList]) - - return ( -
- {isShowConfigElem && (configElem || null)} - {/* Chat List */} -
- {chatList.map((item, index) => { - if (item.isAnswer) { - const isLast = item.id === chatList[chatList.length - 1].id - const citation = item.citation - return - } - return ( - - ) - })} -
- {!isHideSendInput && ( -
- {/* Thinking is sync and can not be stopped */} - {(isResponding && canStopResponding && ((!!chatList[chatList.length - 1]?.content) || (chatList[chatList.length - 1]?.agent_thoughts && chatList[chatList.length - 1].agent_thoughts!.length > 0))) && ( -
- -
- )} - {isShowSuggestion && ( -
-
-
-
- {TryToAskIcon} - {t('appDebug.feature.suggestedQuestionsAfterAnswer.tryToAsk')} -
-
-
- {/* has scrollbar would hide part of first item */} -
- {suggestionList?.map((item, index) => ( -
- -
- ))} -
-
- )} -
-
- {visionConfig?.enabled && ( - <> -
- = visionConfig.number_limits} - /> -
-
-
- -
- - )} -