From 4a75e3774aa34fb417003dd6b33888ace3a4fdaa Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 26 Jan 2024 16:41:16 +0800 Subject: [PATCH] chore: split question and and answer to components --- app/api/parameters/route.ts | 5 +- app/components/chat/answer/index.tsx | 164 +++++++++++++++++++++ app/components/chat/index.tsx | 196 +------------------------ app/components/chat/question/index.tsx | 43 ++++++ app/components/chat/type.ts | 134 +++++++++++++++++ 5 files changed, 348 insertions(+), 194 deletions(-) create mode 100644 app/components/chat/answer/index.tsx create mode 100644 app/components/chat/question/index.tsx create mode 100644 app/components/chat/type.ts diff --git a/app/api/parameters/route.ts b/app/api/parameters/route.ts index b8139d6..f133da8 100644 --- a/app/api/parameters/route.ts +++ b/app/api/parameters/route.ts @@ -9,7 +9,8 @@ export async function GET(request: NextRequest) { return NextResponse.json(data as object, { headers: setSession(sessionId), }) - } catch (error) { - return NextResponse.json([]); + } + catch (error) { + return NextResponse.json([]) } } diff --git a/app/components/chat/answer/index.tsx b/app/components/chat/answer/index.tsx new file mode 100644 index 0000000..cc0c463 --- /dev/null +++ b/app/components/chat/answer/index.tsx @@ -0,0 +1,164 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' +import { useTranslation } from 'react-i18next' +import LoadingAnim from '../loading-anim' +import type { FeedbackFunc, IChatItem } from '../type' +import s from '../style.module.css' +import { randomString } from '@/utils/string' +import type { MessageRating } from '@/types/app' +import Tooltip from '@/app/components/base/tooltip' +import { Markdown } from '@/app/components/base/markdown' + +const OperationBtn = ({ innerContent, onClick, className }: { innerContent: React.ReactNode; onClick?: () => void; className?: string }) => ( +
+ {innerContent} +
+) + +const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => ( + + + +) + +const RatingIcon: FC<{ isLike: boolean }> = ({ isLike }) => { + return isLike ? : +} + +const EditIcon: FC<{ className?: string }> = ({ className }) => { + return + + +} + +export const EditIconSolid: FC<{ className?: string }> = ({ className }) => { + return + + + +} + +const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => { + return
+ {children} +
+} + +type IAnswerProps = { + item: IChatItem + feedbackDisabled: boolean + onFeedback?: FeedbackFunc + isResponsing?: boolean +} + +// The component needs to maintain its own state to control whether to display input component +const Answer: FC = ({ item, feedbackDisabled = false, onFeedback, isResponsing }) => { + const { id, content, feedback } = item + const { t } = useTranslation() + + /** + * 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 + * @returns comp + */ + const renderFeedbackRating = (rating: MessageRating | undefined) => { + 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' + // The tooltip is always displayed, but the content is different for different scenarios. + return ( + +
{ + await onFeedback?.(id, { rating: null }) + }} + > +
+ +
+
+
+ ) + } + + /** + * Different scenarios have different operation items. + * @returns comp + */ + const renderItemOperation = () => { + const userOperation = () => { + return feedback?.rating + ? null + :
+ + {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'like' }) })} + + + {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'dislike' }) })} + +
+ } + + return ( +
+ {userOperation()} +
+ ) + } + + return ( +
+
+
+ {isResponsing + &&
+ +
+ } +
+
+
+
+ {item.isOpeningStatement && ( +
+ +
{t('app.chat.openingStatementTitle')}
+
+ )} + {(isResponsing && !content) + ? ( +
+ +
+ ) + : ( + + )} +
+
+ {!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()} + {/* User feedback must be displayed */} + {!feedbackDisabled && renderFeedbackRating(feedback?.rating)} +
+
+
+
+
+ ) +} +export default React.memo(Answer) diff --git a/app/components/chat/index.tsx b/app/components/chat/index.tsx index da57740..ace2ec4 100644 --- a/app/components/chat/index.tsx +++ b/app/components/chat/index.tsx @@ -2,23 +2,19 @@ import type { FC } from 'react' import React, { useEffect, useRef } from 'react' import cn from 'classnames' -import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' import Textarea from 'rc-textarea' import s from './style.module.css' -import LoadingAnim from './loading-anim' -import { randomString } from '@/utils/string' -import type { Feedbacktype, MessageRating, VisionFile, VisionSettings } from '@/types/app' +import Answer from './answer' +import Question from './question' +import type { FeedbackFunc, Feedbacktype } from './type' +import type { VisionFile, VisionSettings } from '@/types/app' import { TransferMethod } from '@/types/app' import Tooltip from '@/app/components/base/tooltip' import Toast from '@/app/components/base/toast' -import { Markdown } from '@/app/components/base/markdown' import ChatImageUploader from '@/app/components/base/image-uploader/chat-image-uploader' import ImageList from '@/app/components/base/image-uploader/image-list' import { useImageFiles } from '@/app/components/base/image-uploader/hooks' -import ImageGallery from '@/app/components/base/image-gallery' - -export type FeedbackFunc = (messageId: string, feedback: Feedbacktype) => Promise export type IChatProps = { chatList: IChatItem[] @@ -60,190 +56,6 @@ export type IChatItem = { message_files?: VisionFile[] } -const OperationBtn = ({ innerContent, onClick, className }: { innerContent: React.ReactNode; onClick?: () => void; className?: string }) => ( -
- {innerContent} -
-) - -const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => ( - - - -) - -const RatingIcon: FC<{ isLike: boolean }> = ({ isLike }) => { - return isLike ? : -} - -const EditIcon: FC<{ className?: string }> = ({ className }) => { - return - - -} - -export const EditIconSolid: FC<{ className?: string }> = ({ className }) => { - return - - - -} - -const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => { - return
- {children} -
-} - -type IAnswerProps = { - item: IChatItem - feedbackDisabled: boolean - onFeedback?: FeedbackFunc - isResponsing?: boolean -} - -// The component needs to maintain its own state to control whether to display input component -const Answer: FC = ({ item, feedbackDisabled = false, onFeedback, isResponsing }) => { - const { id, content, feedback } = item - const { t } = useTranslation() - - /** - * 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 - * @returns comp - */ - const renderFeedbackRating = (rating: MessageRating | undefined) => { - 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' - // The tooltip is always displayed, but the content is different for different scenarios. - return ( - -
{ - await onFeedback?.(id, { rating: null }) - }} - > -
- -
-
-
- ) - } - - /** - * Different scenarios have different operation items. - * @returns comp - */ - const renderItemOperation = () => { - const userOperation = () => { - return feedback?.rating - ? null - :
- - {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'like' }) })} - - - {OperationBtn({ innerContent: , onClick: () => onFeedback?.(id, { rating: 'dislike' }) })} - -
- } - - return ( -
- {userOperation()} -
- ) - } - - return ( -
-
-
- {isResponsing - &&
- -
- } -
-
-
-
- {item.isOpeningStatement && ( -
- -
{t('app.chat.openingStatementTitle')}
-
- )} - {(isResponsing && !content) - ? ( -
- -
- ) - : ( - - )} -
-
- {!feedbackDisabled && !item.feedbackDisabled && renderItemOperation()} - {/* User feedback must be displayed */} - {!feedbackDisabled && renderFeedbackRating(feedback?.rating)} -
-
-
-
-
- ) -} - -type IQuestionProps = Pick & { - imgSrcs?: string[] -} - -const Question: FC = ({ id, content, useCurrentUserAvatar, imgSrcs }) => { - const userName = '' - return ( -
-
-
-
- {imgSrcs && imgSrcs.length > 0 && ( - - )} - -
-
-
- {useCurrentUserAvatar - ? ( -
- {userName?.[0].toLocaleUpperCase()} -
- ) - : ( -
- )} -
- ) -} - const Chat: FC = ({ chatList, feedbackDisabled = false, diff --git a/app/components/chat/question/index.tsx b/app/components/chat/question/index.tsx new file mode 100644 index 0000000..75b5391 --- /dev/null +++ b/app/components/chat/question/index.tsx @@ -0,0 +1,43 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import type { IChatItem } from '../type' +import s from '../style.module.css' + +import { Markdown } from '@/app/components/base/markdown' +import ImageGallery from '@/app/components/base/image-gallery' + +type IQuestionProps = Pick & { + imgSrcs?: string[] +} + +const Question: FC = ({ id, content, useCurrentUserAvatar, imgSrcs }) => { + const userName = '' + return ( +
+
+
+
+ {imgSrcs && imgSrcs.length > 0 && ( + + )} + +
+
+
+ {useCurrentUserAvatar + ? ( +
+ {userName?.[0].toLocaleUpperCase()} +
+ ) + : ( +
+ )} +
+ ) +} + +export default React.memo(Question) diff --git a/app/components/chat/type.ts b/app/components/chat/type.ts new file mode 100644 index 0000000..7bbc72b --- /dev/null +++ b/app/components/chat/type.ts @@ -0,0 +1,134 @@ +import type { VisionFile } from '@/types/app' + +export type LogAnnotation = { + content: string + account: { + id: string + name: string + email: string + } + created_at: number +} + +export type Annotation = { + id: string + authorName: string + logAnnotation?: LogAnnotation + created_at?: number +} + +export const MessageRatings = ['like', 'dislike', null] as const +export type MessageRating = typeof MessageRatings[number] + +export type MessageMore = { + time: string + tokens: number + latency: number | string +} + +export type Feedbacktype = { + rating: MessageRating + content?: string | null +} + +export type FeedbackFunc = (messageId: string, feedback: Feedbacktype) => Promise +export type SubmitAnnotationFunc = (messageId: string, content: string) => Promise + +export type DisplayScene = 'web' | 'console' + +export type ToolInfoInThought = { + name: string + input: string + output: string + isFinished: boolean +} + +export type ThoughtItem = { + id: string + tool: string // plugin or dataset. May has multi. + thought: string + tool_input: string + message_id: string + observation: string + position: number + files?: string[] + message_files?: VisionFile[] +} + +export type CitationItem = { + content: string + data_source_type: string + dataset_name: string + dataset_id: string + document_id: string + document_name: string + hit_count: number + index_node_hash: string + segment_id: string + segment_position: number + score: number + word_count: number +} + +export type IChatItem = { + id: string + content: string + citation?: CitationItem[] + /** + * Specific message type + */ + isAnswer: boolean + /** + * The user feedback result of this message + */ + feedback?: Feedbacktype + /** + * The admin feedback result of this message + */ + adminFeedback?: Feedbacktype + /** + * Whether to hide the feedback area + */ + feedbackDisabled?: boolean + /** + * More information about this message + */ + more?: MessageMore + annotation?: Annotation + useCurrentUserAvatar?: boolean + isOpeningStatement?: boolean + suggestedQuestions?: string[] + log?: { role: string; text: string }[] + agent_thoughts?: ThoughtItem[] + message_files?: VisionFile[] +} + +export type MessageEnd = { + id: string + metadata: { + retriever_resources?: CitationItem[] + annotation_reply: { + id: string + account: { + id: string + name: string + } + } + } +} + +export type MessageReplace = { + id: string + task_id: string + answer: string + conversation_id: string +} + +export type AnnotationReply = { + id: string + task_id: string + answer: string + conversation_id: string + annotation_id: string + annotation_author_name: string +}