diff --git a/web/app/(commonLayout)/explore/chat/page.tsx b/web/app/(commonLayout)/explore/chat/page.tsx new file mode 100644 index 0000000000..9610d868ce --- /dev/null +++ b/web/app/(commonLayout)/explore/chat/page.tsx @@ -0,0 +1,13 @@ +import type { FC } from 'react' +import React from 'react' +import UniversalChat from '@/app/components/explore/universal-chat' + +const Chat: FC = () => { + return ( +
+ +
+ ) +} + +export default React.memo(Chat) diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index 581edea6a1..b1c524642e 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -5,6 +5,8 @@ import { AppContextProvider } from '@/context/app-context' import GA, { GaType } from '@/app/components/base/ga' import HeaderWrapper from '@/app/components/header/HeaderWrapper' import Header from '@/app/components/header' +import { EventEmitterContextProvider } from '@/context/event-emitter' +import { ProviderContextProvider } from '@/context/provider-context' const Layout = ({ children }: { children: ReactNode }) => { return ( @@ -12,10 +14,14 @@ const Layout = ({ children }: { children: ReactNode }) => { - -
- - {children} + + + +
+ + {children} + + diff --git a/web/app/components/app/chat/answer/index.tsx b/web/app/components/app/chat/answer/index.tsx new file mode 100644 index 0000000000..86f2ad5734 --- /dev/null +++ b/web/app/components/app/chat/answer/index.tsx @@ -0,0 +1,262 @@ +'use client' +import type { FC } from 'react' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import { UserCircleIcon } from '@heroicons/react/24/solid' +import cn from 'classnames' +import type { DisplayScene, FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc, ThoughtItem } from '../type' +import { randomString } from '../../../app-sidebar/basic' +import OperationBtn from '../operation' +import LoadingAnim from '../loading-anim' +import { EditIcon, EditIconSolid, OpeningStatementIcon, 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 type { Annotation, MessageRating } from '@/models/log' +import AppContext from '@/context/app-context' +import Tooltip from '@/app/components/base/tooltip' +import { Markdown } from '@/app/components/base/markdown' +import AutoHeightTextarea from '@/app/components/base/auto-height-textarea' +import Button from '@/app/components/base/button' +import type { DataSet } from '@/models/datasets' +const Divider: FC<{ name: string }> = ({ name }) => { + const { t } = useTranslation() + return
+ + {t('appLog.detail.annotationTip', { user: name })} + +
+
+} +const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => { + return
+ {children} +
+} +export type IAnswerProps = { + item: IChatItem + feedbackDisabled: boolean + isHideFeedbackEdit: boolean + onFeedback?: FeedbackFunc + onSubmitAnnotation?: SubmitAnnotationFunc + displayScene: DisplayScene + isResponsing?: boolean + answerIconClassName?: string + thoughts?: ThoughtItem[] + isThinking?: boolean + dataSets?: DataSet[] +} +// The component needs to maintain its own state to control whether to display input component +const Answer: FC = ({ item, feedbackDisabled = false, isHideFeedbackEdit = false, onFeedback, onSubmitAnnotation, displayScene = 'web', isResponsing, answerIconClassName, thoughts, isThinking, dataSets }) => { + const { id, content, more, feedback, adminFeedback, annotation: initAnnotation } = item + const [showEdit, setShowEdit] = useState(false) + const [loading, setLoading] = useState(false) + const [annotation, setAnnotation] = useState(initAnnotation) + const [inputValue, setInputValue] = useState(initAnnotation?.content ?? '') + const [localAdminFeedback, setLocalAdminFeedback] = useState(adminFeedback) + const { userProfile } = useContext(AppContext) + 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 + * @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
+ + {OperationBtn({ + innerContent: , + onClick: () => setShowEdit(true), + })} + + {!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()} +
+ ) + } + + return ( +
+
+
+ {isResponsing + &&
+ +
+ } +
+
+
+
+
+ {item.isOpeningStatement && ( +
+ +
{t('appDebug.openingStatement.title')}
+
+ )} + {(thoughts && thoughts.length > 0) && ( + + )} + {(isResponsing && !content) + ? ( +
+ +
+ ) + : ( +
+ +
+ )} + {!showEdit + ? (annotation?.content + && <> + + {annotation.content} + ) + : <> + + setInputValue(e.target.value)} + minHeight={58} + className={`${cn(s.textArea)} !py-2 resize-none block w-full !px-3 bg-gray-50 border border-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm text-gray-700 tracking-[0.2px]`} + /> +
+ + +
+ + } +
+
+ {!item.isOpeningStatement && ( + + )} + {!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 new file mode 100644 index 0000000000..52eb87f4d0 --- /dev/null +++ b/web/app/components/app/chat/icon-component/index.tsx @@ -0,0 +1,37 @@ +import type { FC } from 'react' +import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' + +export const stopIcon = ( + + + +) + +export const OpeningStatementIcon: FC<{ className?: string }> = ({ className }) => ( + + + +) + +export const RatingIcon: FC<{ isLike: boolean }> = ({ isLike }) => { + return isLike ? : +} + +export const EditIcon: FC<{ className?: string }> = ({ className }) => { + return + + +} + +export const EditIconSolid: FC<{ className?: string }> = ({ className }) => { + return + + + +} + +export const TryToAskIcon = ( + + + +) diff --git a/web/app/components/app/chat/index.tsx b/web/app/components/app/chat/index.tsx index 4be9b424df..8b32d56b9d 100644 --- a/web/app/components/app/chat/index.tsx +++ b/web/app/components/app/chat/index.tsx @@ -4,43 +4,25 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react' import { useContext } from 'use-context-selector' import cn from 'classnames' import Recorder from 'js-audio-recorder' -import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' -import { UserCircleIcon } from '@heroicons/react/24/solid' import { useTranslation } from 'react-i18next' -import { randomString } from '../../app-sidebar/basic' import s from './style.module.css' -import LoadingAnim from './loading-anim' -import CopyBtn from './copy-btn' +import type { DisplayScene, FeedbackFunc, IChatItem, SubmitAnnotationFunc } from './type' +import { TryToAskIcon, stopIcon } from './icon-component' +import Answer from './answer' +import Question from './question' import Tooltip from '@/app/components/base/tooltip' import { ToastContext } from '@/app/components/base/toast' import AutoHeightTextarea from '@/app/components/base/auto-height-textarea' import Button from '@/app/components/base/button' -import type { Annotation, MessageRating } from '@/models/log' -import AppContext from '@/context/app-context' -import { Markdown } from '@/app/components/base/markdown' -import { formatNumber } from '@/utils/format' 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' - -const stopIcon = ( - - - -) -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' +import type { DataSet } from '@/models/datasets' export type IChatProps = { + configElem?: React.ReactNode chatList: IChatItem[] /** * Whether to display the editing area and rating status @@ -66,352 +48,12 @@ export type IChatProps = { suggestionList?: string[] isShowSpeechToText?: boolean answerIconClassName?: string -} - -export type MessageMore = { - time: string - tokens: number - latency: number | string -} - -export type IChatItem = { - id: string - content: string - /** - * 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 -} - -const OperationBtn = ({ innerContent, onClick, className }: { innerContent: React.ReactNode; onClick?: () => void; className?: string }) => ( -
- {innerContent} -
-) - -const MoreInfo: FC<{ more: MessageMore; isQuestion: boolean }> = ({ more, isQuestion }) => { - const { t } = useTranslation() - return (
- {`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`} - {`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`} - · - {more.time} -
) -} - -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 TryToAskIcon = ( - - - -) - -const Divider: FC<{ name: string }> = ({ name }) => { - const { t } = useTranslation() - return
- - {t('appLog.detail.annotationTip', { user: name })} - -
-
-} - -const IconWrapper: FC<{ children: React.ReactNode | string }> = ({ children }) => { - return
- {children} -
-} - -type IAnswerProps = { - item: IChatItem - feedbackDisabled: boolean - isHideFeedbackEdit: boolean - onFeedback?: FeedbackFunc - onSubmitAnnotation?: SubmitAnnotationFunc - displayScene: DisplayScene - isResponsing?: boolean - answerIconClassName?: string -} - -// The component needs to maintain its own state to control whether to display input component -const Answer: FC = ({ item, feedbackDisabled = false, isHideFeedbackEdit = false, onFeedback, onSubmitAnnotation, displayScene = 'web', isResponsing, answerIconClassName }) => { - const { id, content, more, feedback, adminFeedback, annotation: initAnnotation } = item - const [showEdit, setShowEdit] = useState(false) - const [loading, setLoading] = useState(false) - const [annotation, setAnnotation] = useState(initAnnotation) - const [inputValue, setInputValue] = useState(initAnnotation?.content ?? '') - const [localAdminFeedback, setLocalAdminFeedback] = useState(adminFeedback) - const { userProfile } = useContext(AppContext) - 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 - * @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
- - {OperationBtn({ - innerContent: , - onClick: () => setShowEdit(true), - })} - - {!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()} -
- ) - } - - return ( -
-
-
- {isResponsing - &&
- -
- } -
-
-
-
-
- {item.isOpeningStatement && ( -
- -
{t('appDebug.openingStatement.title')}
-
- )} - {(isResponsing && !content) - ? ( -
- -
- ) - : ( - - )} - {!showEdit - ? (annotation?.content - && <> - - {annotation.content} - ) - : <> - - setInputValue(e.target.value)} - minHeight={58} - className={`${cn(s.textArea)} !py-2 resize-none block w-full !px-3 bg-gray-50 border border-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm text-gray-700 tracking-[0.2px]`} - /> -
- - -
- - } -
-
- {!item.isOpeningStatement && ( - - )} - {!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 && } -
-
-
-
- ) -} - -type IQuestionProps = Pick - -const Question: FC = ({ id, content, more, useCurrentUserAvatar }) => { - const { userProfile } = useContext(AppContext) - const userName = userProfile?.name - return ( -
-
-
-
- -
-
- {more && } -
- {useCurrentUserAvatar - ? ( -
- {userName?.[0].toLocaleUpperCase()} -
- ) - : ( -
- )} -
- ) + isShowConfigElem?: boolean + dataSets?: DataSet[] } const Chat: FC = ({ + configElem, chatList, feedbackDisabled = false, isHideFeedbackEdit = false, @@ -431,6 +73,8 @@ const Chat: FC = ({ suggestionList, isShowSpeechToText, answerIconClassName, + isShowConfigElem, + dataSets, }) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) @@ -509,11 +153,14 @@ const Chat: FC = ({ return (
+ {isShowConfigElem && (configElem || null)} {/* Chat List */} -
+
{chatList.map((item) => { if (item.isAnswer) { const isLast = item.id === chatList[chatList.length - 1].id + const thoughts = item.agent_thoughts?.filter(item => item.thought !== '[DONE]') + const isThinking = !item.content && item.agent_thoughts && item.agent_thoughts?.length > 0 && !item.agent_thoughts.some(item => item.thought === '[DONE]') return = ({ displayScene={displayScene ?? 'web'} isResponsing={isResponsing && isLast} answerIconClassName={answerIconClassName} + thoughts={thoughts} + isThinking={isThinking} + dataSets={dataSets} /> } return @@ -532,7 +182,8 @@ const Chat: FC = ({ { !isHideSendInput && (
- {(isResponsing && canStopResponsing) && ( + {/* Thinking is sync and can not be stopped */} + {(isResponsing && canStopResponsing && !!chatList[chatList.length - 1]?.content) && (
diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index bfc545af52..e6096b782d 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -16,10 +16,10 @@ import dayjs from 'dayjs' import { createContext, useContext } from 'use-context-selector' import classNames from 'classnames' import { useTranslation } from 'react-i18next' -import { EditIconSolid } from '../chat' import { randomString } from '../../app-sidebar/basic' import s from './style.module.css' -import type { FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc } from '@/app/components/app/chat' +import { EditIconSolid } from '@/app/components/app/chat/icon-component' +import type { FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc } from '@/app/components/app/chat/type' import type { Annotation, ChatConversationFullDetailResponse, ChatConversationGeneralDetail, ChatConversationsResponse, ChatMessage, ChatMessagesRequest, CompletionConversationFullDetailResponse, CompletionConversationGeneralDetail, CompletionConversationsResponse } from '@/models/log' import type { App } from '@/types/app' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/base/icons/assets/public/llm/anthropic.svg b/web/app/components/base/icons/assets/public/llm/anthropic.svg new file mode 100644 index 0000000000..82c93163f9 --- /dev/null +++ b/web/app/components/base/icons/assets/public/llm/anthropic.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/web/app/components/base/icons/assets/public/llm/gpt-3.svg b/web/app/components/base/icons/assets/public/llm/gpt-3.svg new file mode 100644 index 0000000000..514215fc20 --- /dev/null +++ b/web/app/components/base/icons/assets/public/llm/gpt-3.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/assets/public/llm/gpt-4.svg b/web/app/components/base/icons/assets/public/llm/gpt-4.svg new file mode 100644 index 0000000000..63abb1e768 --- /dev/null +++ b/web/app/components/base/icons/assets/public/llm/gpt-4.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/assets/public/model/checked.svg b/web/app/components/base/icons/assets/public/model/checked.svg new file mode 100644 index 0000000000..a3557035c5 --- /dev/null +++ b/web/app/components/base/icons/assets/public/model/checked.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/assets/public/plugins/google.svg b/web/app/components/base/icons/assets/public/plugins/google.svg new file mode 100644 index 0000000000..ac232b4e4a --- /dev/null +++ b/web/app/components/base/icons/assets/public/plugins/google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/web/app/components/base/icons/assets/public/plugins/web-reader.svg b/web/app/components/base/icons/assets/public/plugins/web-reader.svg new file mode 100644 index 0000000000..61fbf691bd --- /dev/null +++ b/web/app/components/base/icons/assets/public/plugins/web-reader.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/app/components/base/icons/assets/public/plugins/wikipedia.svg b/web/app/components/base/icons/assets/public/plugins/wikipedia.svg new file mode 100644 index 0000000000..a2f10d9082 --- /dev/null +++ b/web/app/components/base/icons/assets/public/plugins/wikipedia.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/assets/public/thought/data-set.svg b/web/app/components/base/icons/assets/public/thought/data-set.svg new file mode 100644 index 0000000000..bdee170b8c --- /dev/null +++ b/web/app/components/base/icons/assets/public/thought/data-set.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/app/components/base/icons/assets/public/thought/loading.svg b/web/app/components/base/icons/assets/public/thought/loading.svg new file mode 100644 index 0000000000..c666e17a63 --- /dev/null +++ b/web/app/components/base/icons/assets/public/thought/loading.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/app/components/base/icons/assets/public/thought/search.svg b/web/app/components/base/icons/assets/public/thought/search.svg new file mode 100644 index 0000000000..314b04d5ac --- /dev/null +++ b/web/app/components/base/icons/assets/public/thought/search.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/app/components/base/icons/assets/public/thought/thought-list.svg b/web/app/components/base/icons/assets/public/thought/thought-list.svg new file mode 100644 index 0000000000..b8e17f08a0 --- /dev/null +++ b/web/app/components/base/icons/assets/public/thought/thought-list.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/web/app/components/base/icons/assets/public/thought/web-reader.svg b/web/app/components/base/icons/assets/public/thought/web-reader.svg new file mode 100644 index 0000000000..1c2291eb92 --- /dev/null +++ b/web/app/components/base/icons/assets/public/thought/web-reader.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/app/components/base/icons/assets/vender/line/general/link-external-02.svg b/web/app/components/base/icons/assets/vender/line/general/link-external-02.svg new file mode 100644 index 0000000000..71936fb941 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/general/link-external-02.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/assets/vender/solid/alertsAndFeedback/alert-circle.svg b/web/app/components/base/icons/assets/vender/solid/alertsAndFeedback/alert-circle.svg new file mode 100644 index 0000000000..61d95ad6d0 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/solid/alertsAndFeedback/alert-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/assets/vender/solid/general/check-circle.svg b/web/app/components/base/icons/assets/vender/solid/general/check-circle.svg new file mode 100644 index 0000000000..33d0becea8 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/solid/general/check-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/src/public/llm/Anthropic.json b/web/app/components/base/icons/src/public/llm/Anthropic.json new file mode 100644 index 0000000000..2de0deef5c --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/Anthropic.json @@ -0,0 +1,87 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "24", + "height": "24", + "rx": "6", + "fill": "#CA9F7B" + }, + "children": [] + }, + { + "type": "element", + "name": "g", + "attributes": { + "clip-path": "url(#clip0_7672_55906)" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M15.3843 6.43457H12.9687L17.3739 17.565H19.7896L15.3843 6.43457ZM8.40522 6.43457L4 17.565H6.4633L7.36417 15.2276H11.9729L12.8737 17.565H15.337L10.9318 6.43457H8.40522ZM8.16104 13.1605L9.66852 9.24883L11.176 13.1605H8.16104Z", + "fill": "#191918" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "rect", + "attributes": { + "x": "0.5", + "y": "0.5", + "width": "23", + "height": "23", + "rx": "5.5", + "stroke": "black", + "stroke-opacity": "0.05" + }, + "children": [] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "clip0_7672_55906" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "16", + "height": "11.1304", + "fill": "white", + "transform": "translate(4 6.43457)" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Anthropic" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/llm/Anthropic.tsx b/web/app/components/base/icons/src/public/llm/Anthropic.tsx new file mode 100644 index 0000000000..89c5f2804e --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/Anthropic.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Anthropic.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/llm/Gpt3.json b/web/app/components/base/icons/src/public/llm/Gpt3.json new file mode 100644 index 0000000000..253b9a3d3f --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/Gpt3.json @@ -0,0 +1,51 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "24", + "height": "24", + "rx": "6", + "fill": "#19C37D" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M19.6451 11.6028C19.8209 11.995 19.9325 12.4142 19.9781 12.8419C20.0221 13.2696 20.0001 13.7024 19.9088 14.1234C19.8192 14.5443 19.6637 14.9484 19.4473 15.3203C19.3053 15.5688 19.1379 15.8021 18.9452 16.0168C18.7542 16.2298 18.5412 16.4225 18.3096 16.5916C18.0763 16.7606 17.8278 16.9027 17.564 17.0193C17.302 17.1343 17.0281 17.2222 16.7475 17.2796C16.6156 17.6888 16.4195 18.0759 16.1659 18.4242C15.914 18.7724 15.6081 19.0784 15.2598 19.3303C14.9115 19.5839 14.5261 19.78 14.117 19.9118C13.7079 20.0454 13.2802 20.1113 12.8491 20.1113C12.5634 20.113 12.276 20.0826 11.9953 20.0251C11.7164 19.9659 11.4425 19.8763 11.1805 19.7597C10.9184 19.643 10.6699 19.4977 10.4383 19.3286C10.2084 19.1595 9.99541 18.9651 9.80606 18.7504C9.38342 18.8417 8.95064 18.8637 8.52293 18.8197C8.09522 18.7741 7.67596 18.6625 7.28206 18.4867C6.88985 18.3126 6.52638 18.0759 6.20687 17.7868C5.88735 17.4977 5.61517 17.1596 5.40047 16.7877C5.25677 16.5392 5.13843 16.2771 5.04883 16.005C4.95924 15.7328 4.90007 15.4522 4.86964 15.1665C4.83921 14.8824 4.8409 14.595 4.87133 14.3093C4.90176 14.0253 4.96431 13.7447 5.05391 13.4725C4.76651 13.153 4.52983 12.7895 4.35402 12.3973C4.17989 12.0034 4.06662 11.5859 4.02267 11.1581C3.97702 10.7304 4.00069 10.2976 4.09029 9.8767C4.17989 9.45575 4.33542 9.05171 4.55181 8.67978C4.69382 8.43127 4.86118 8.19628 5.05222 7.98327C5.24325 7.77026 5.45795 7.57754 5.68956 7.40848C5.92116 7.23943 6.17136 7.09573 6.4334 6.98077C6.69713 6.86412 6.971 6.77791 7.25163 6.72043C7.38349 6.30962 7.5796 5.92417 7.83149 5.57592C8.08508 5.22766 8.39107 4.92167 8.73932 4.66809C9.08758 4.4162 9.47302 4.22009 9.88214 4.08654C10.2913 3.95467 10.719 3.88705 11.1501 3.88874C11.4358 3.88705 11.7232 3.91579 12.0038 3.97496C12.2844 4.03413 12.5583 4.12204 12.8203 4.23869C13.0824 4.35703 13.3309 4.50072 13.5625 4.66978C13.7941 4.84053 14.0071 5.03325 14.1964 5.24795C14.6174 5.15835 15.0502 5.13637 15.4779 5.18033C15.9056 5.22428 16.3232 5.33755 16.7171 5.51168C17.1093 5.6875 17.4727 5.92248 17.7923 6.21157C18.1118 6.49896 18.384 6.83538 18.5987 7.209C18.7423 7.45582 18.8607 7.71786 18.9503 7.99173C19.0399 8.26391 19.1007 8.54454 19.1295 8.83024C19.1599 9.11595 19.1599 9.40334 19.1278 9.68905C19.0974 9.97475 19.0348 10.2554 18.9452 10.5276C19.2343 10.8471 19.4693 11.2089 19.6451 11.6028ZM14.0122 18.8197C14.3807 18.6676 14.7154 18.4428 14.9978 18.1604C15.2801 17.8781 15.5049 17.5434 15.6571 17.1731C15.8092 16.8046 15.8887 16.409 15.8887 16.01V12.2401C15.8876 12.2367 15.8864 12.2328 15.8853 12.2283C15.8842 12.2249 15.8825 12.2215 15.8802 12.2181C15.878 12.2147 15.8752 12.2119 15.8718 12.2097C15.8684 12.2063 15.865 12.204 15.8616 12.2029L14.4974 11.4151V15.9695C14.4974 16.0151 14.4906 16.0624 14.4788 16.1064C14.4669 16.152 14.45 16.1943 14.4264 16.2349C14.4027 16.2755 14.3756 16.3126 14.3418 16.3448C14.309 16.3775 14.272 16.4059 14.2319 16.4293L11.0013 18.294C10.9742 18.3109 10.9286 18.3346 10.9049 18.3481C11.0385 18.4613 11.1839 18.5611 11.336 18.649C11.4899 18.7369 11.6488 18.8113 11.8144 18.8722C11.9801 18.9313 12.1509 18.977 12.3233 19.0074C12.4974 19.0378 12.6732 19.053 12.8491 19.053C13.248 19.053 13.6436 18.9736 14.0122 18.8197ZM6.31844 16.2602C6.51962 16.6068 6.78504 16.9077 7.10117 17.1512C7.419 17.3946 7.77908 17.5721 8.16453 17.6752C8.54998 17.7784 8.95233 17.8054 9.34792 17.753C9.74351 17.7006 10.1239 17.5721 10.4705 17.3726L13.7366 15.4877L13.7451 15.4792C13.7473 15.477 13.749 15.4736 13.7501 15.4691C13.7524 15.4657 13.7541 15.4623 13.7552 15.4589V13.8698L9.81283 16.1504C9.77225 16.174 9.72999 16.1909 9.68603 16.2045C9.64039 16.2163 9.59474 16.2214 9.54741 16.2214C9.50176 16.2214 9.45612 16.2163 9.41047 16.2045C9.36652 16.1909 9.32256 16.174 9.28199 16.1504L6.05133 14.284C6.0226 14.2671 5.98033 14.2417 5.95666 14.2265C5.92623 14.4006 5.91102 14.5764 5.91102 14.7523C5.91102 14.9281 5.92792 15.1039 5.95835 15.278C5.98878 15.4505 6.03612 15.6212 6.09529 15.7869C6.15615 15.9526 6.23053 16.1115 6.31844 16.2636V16.2602ZM5.46978 9.21062C5.2703 9.55718 5.14182 9.93925 5.08941 10.3348C5.037 10.7304 5.06405 11.1311 5.16717 11.5182C5.2703 11.9037 5.44781 12.2638 5.69125 12.5816C5.93469 12.8977 6.2373 13.1631 6.58217 13.3626L9.84664 15.2493C9.85002 15.2504 9.85396 15.2515 9.85847 15.2527H9.8703C9.87481 15.2527 9.87876 15.2515 9.88214 15.2493C9.88552 15.2482 9.8889 15.2465 9.89228 15.2442L11.2616 14.453L7.31925 12.1775C7.28037 12.1539 7.24318 12.1251 7.20937 12.093C7.17661 12.0602 7.1482 12.0232 7.12484 11.9831C7.10286 11.9426 7.08427 11.9003 7.07243 11.8547C7.0606 11.8107 7.05384 11.7651 7.05553 11.7177V7.87846C6.88985 7.93932 6.72925 8.0137 6.5771 8.10161C6.42495 8.19121 6.28125 8.29265 6.14601 8.40591C6.01245 8.51918 5.88735 8.64428 5.77408 8.77953C5.66082 8.91308 5.56107 9.05847 5.47316 9.21062H5.46978ZM16.6832 11.8208C16.7238 11.8445 16.761 11.8716 16.7948 11.9054C16.8269 11.9375 16.8557 11.9747 16.8794 12.0153C16.9013 12.0558 16.9199 12.0998 16.9318 12.1437C16.9419 12.1894 16.9487 12.235 16.947 12.2824V16.1216C17.4896 15.9221 17.963 15.5722 18.3129 15.1124C18.6646 14.6525 18.8759 14.1031 18.9249 13.5283C18.974 12.9535 18.859 12.3753 18.5919 11.8631C18.3248 11.3509 17.9174 10.9248 17.417 10.6374L14.1525 8.75079C14.1491 8.74966 14.1452 8.74853 14.1407 8.74741H14.1288C14.1254 8.74853 14.1215 8.74966 14.117 8.75079C14.1136 8.75191 14.1102 8.7536 14.1068 8.75586L12.7443 9.54366L16.6866 11.8208H16.6832ZM18.0441 9.77526H18.0425V9.77695L18.0441 9.77526ZM18.0425 9.77357C18.1405 9.20555 18.0746 8.62061 17.8514 8.08809C17.63 7.55556 17.2597 7.09742 16.7864 6.76607C16.313 6.43641 15.7551 6.24707 15.1787 6.22171C14.6005 6.19804 14.0291 6.33836 13.5287 6.62575L10.2642 8.51073C10.2608 8.51298 10.258 8.5158 10.2558 8.51918L10.249 8.52932C10.2479 8.5327 10.2467 8.53665 10.2456 8.54116C10.2445 8.54454 10.2439 8.54848 10.2439 8.55299V10.1286L14.1863 7.85141C14.2269 7.82774 14.2708 7.81084 14.3148 7.79731C14.3604 7.78548 14.4061 7.78041 14.4517 7.78041C14.499 7.78041 14.5447 7.78548 14.5903 7.79731C14.6343 7.81084 14.6766 7.82774 14.7171 7.85141L17.9478 9.71778C17.9765 9.73469 18.0188 9.75836 18.0425 9.77357ZM9.50007 8.02892C9.50007 7.98327 9.50683 7.93763 9.51867 7.89198C9.5305 7.84803 9.54741 7.80407 9.57108 7.7635C9.59474 7.72462 9.62179 7.68743 9.6556 7.65361C9.68772 7.62149 9.72492 7.59275 9.76549 7.57078L12.9961 5.70609C13.0266 5.6875 13.0688 5.66383 13.0925 5.65199C12.6496 5.28176 12.1086 5.04508 11.5355 4.97239C10.9624 4.89801 10.3809 4.9893 9.85847 5.23443C9.3344 5.47956 8.89147 5.87008 8.5821 6.35696C8.27273 6.84553 8.10874 7.41017 8.10874 7.98834V11.7583C8.10987 11.7628 8.111 11.7667 8.11212 11.7701C8.11325 11.7735 8.11494 11.7769 8.1172 11.7803C8.11945 11.7836 8.12227 11.787 8.12565 11.7904C8.1279 11.7927 8.13128 11.7949 8.13579 11.7972L9.50007 12.585V8.02892ZM10.2405 13.011L11.997 14.0253L13.7535 13.011V10.984L11.9987 9.96968L10.2422 10.984L10.2405 13.011Z", + "fill": "white" + }, + "children": [] + }, + { + "type": "element", + "name": "rect", + "attributes": { + "x": "0.5", + "y": "0.5", + "width": "23", + "height": "23", + "rx": "5.5", + "stroke": "black", + "stroke-opacity": "0.05" + }, + "children": [] + } + ] + }, + "name": "Gpt3" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/llm/Gpt3.tsx b/web/app/components/base/icons/src/public/llm/Gpt3.tsx new file mode 100644 index 0000000000..6d7eb224d0 --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/Gpt3.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Gpt3.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/llm/Gpt4.json b/web/app/components/base/icons/src/public/llm/Gpt4.json new file mode 100644 index 0000000000..0e50c5f712 --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/Gpt4.json @@ -0,0 +1,51 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "24", + "height": "24", + "rx": "6", + "fill": "#AB68FF" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M19.6451 11.6028C19.8209 11.995 19.9325 12.4142 19.9781 12.8419C20.0221 13.2696 20.0001 13.7024 19.9088 14.1234C19.8192 14.5443 19.6637 14.9484 19.4473 15.3203C19.3053 15.5688 19.1379 15.8021 18.9452 16.0168C18.7542 16.2298 18.5412 16.4225 18.3096 16.5916C18.0763 16.7606 17.8278 16.9027 17.564 17.0193C17.302 17.1343 17.0281 17.2222 16.7475 17.2796C16.6156 17.6888 16.4195 18.0759 16.1659 18.4242C15.914 18.7724 15.6081 19.0784 15.2598 19.3303C14.9115 19.5839 14.5261 19.78 14.117 19.9118C13.7079 20.0454 13.2802 20.1113 12.8491 20.1113C12.5634 20.113 12.276 20.0826 11.9953 20.0251C11.7164 19.9659 11.4425 19.8763 11.1805 19.7597C10.9184 19.643 10.6699 19.4977 10.4383 19.3286C10.2084 19.1595 9.99541 18.9651 9.80606 18.7504C9.38342 18.8417 8.95064 18.8637 8.52293 18.8197C8.09522 18.7741 7.67596 18.6625 7.28206 18.4867C6.88985 18.3126 6.52638 18.0759 6.20687 17.7868C5.88735 17.4977 5.61517 17.1596 5.40047 16.7877C5.25677 16.5392 5.13843 16.2771 5.04883 16.005C4.95924 15.7328 4.90007 15.4522 4.86964 15.1665C4.83921 14.8824 4.8409 14.595 4.87133 14.3093C4.90176 14.0253 4.96431 13.7447 5.05391 13.4725C4.76651 13.153 4.52983 12.7895 4.35402 12.3973C4.17989 12.0034 4.06662 11.5859 4.02267 11.1581C3.97702 10.7304 4.00069 10.2976 4.09029 9.8767C4.17989 9.45575 4.33542 9.05171 4.55181 8.67978C4.69382 8.43127 4.86118 8.19628 5.05222 7.98327C5.24325 7.77026 5.45795 7.57754 5.68956 7.40848C5.92116 7.23943 6.17136 7.09573 6.4334 6.98077C6.69713 6.86412 6.971 6.77791 7.25163 6.72043C7.38349 6.30962 7.5796 5.92417 7.83149 5.57592C8.08508 5.22766 8.39107 4.92167 8.73932 4.66809C9.08758 4.4162 9.47302 4.22009 9.88214 4.08654C10.2913 3.95467 10.719 3.88705 11.1501 3.88874C11.4358 3.88705 11.7232 3.91579 12.0038 3.97496C12.2844 4.03413 12.5583 4.12204 12.8203 4.23869C13.0824 4.35703 13.3309 4.50072 13.5625 4.66978C13.7941 4.84053 14.0071 5.03325 14.1964 5.24795C14.6174 5.15835 15.0502 5.13637 15.4779 5.18033C15.9056 5.22428 16.3232 5.33755 16.7171 5.51168C17.1093 5.6875 17.4727 5.92248 17.7923 6.21157C18.1118 6.49896 18.384 6.83538 18.5987 7.209C18.7423 7.45582 18.8607 7.71786 18.9503 7.99173C19.0399 8.26391 19.1007 8.54454 19.1295 8.83024C19.1599 9.11595 19.1599 9.40334 19.1278 9.68905C19.0974 9.97475 19.0348 10.2554 18.9452 10.5276C19.2343 10.8471 19.4693 11.2089 19.6451 11.6028ZM14.0122 18.8197C14.3807 18.6676 14.7154 18.4428 14.9978 18.1604C15.2801 17.8781 15.5049 17.5434 15.6571 17.1731C15.8092 16.8046 15.8887 16.409 15.8887 16.01V12.2401C15.8876 12.2367 15.8864 12.2328 15.8853 12.2283C15.8842 12.2249 15.8825 12.2215 15.8802 12.2181C15.878 12.2147 15.8752 12.2119 15.8718 12.2097C15.8684 12.2063 15.865 12.204 15.8616 12.2029L14.4974 11.4151V15.9695C14.4974 16.0151 14.4906 16.0624 14.4788 16.1064C14.4669 16.152 14.45 16.1943 14.4264 16.2349C14.4027 16.2755 14.3756 16.3126 14.3418 16.3448C14.309 16.3775 14.272 16.4059 14.2319 16.4293L11.0013 18.294C10.9742 18.3109 10.9286 18.3346 10.9049 18.3481C11.0385 18.4613 11.1839 18.5611 11.336 18.649C11.4899 18.7369 11.6488 18.8113 11.8144 18.8722C11.9801 18.9313 12.1509 18.977 12.3233 19.0074C12.4974 19.0378 12.6732 19.053 12.8491 19.053C13.248 19.053 13.6436 18.9736 14.0122 18.8197ZM6.31844 16.2602C6.51962 16.6068 6.78504 16.9077 7.10117 17.1512C7.419 17.3946 7.77908 17.5721 8.16453 17.6752C8.54998 17.7784 8.95233 17.8054 9.34792 17.753C9.74351 17.7006 10.1239 17.5721 10.4705 17.3726L13.7366 15.4877L13.7451 15.4792C13.7473 15.477 13.749 15.4736 13.7501 15.4691C13.7524 15.4657 13.7541 15.4623 13.7552 15.4589V13.8698L9.81283 16.1504C9.77225 16.174 9.72999 16.1909 9.68603 16.2045C9.64039 16.2163 9.59474 16.2214 9.54741 16.2214C9.50176 16.2214 9.45612 16.2163 9.41047 16.2045C9.36652 16.1909 9.32256 16.174 9.28199 16.1504L6.05133 14.284C6.0226 14.2671 5.98033 14.2417 5.95666 14.2265C5.92623 14.4006 5.91102 14.5764 5.91102 14.7523C5.91102 14.9281 5.92792 15.1039 5.95835 15.278C5.98878 15.4505 6.03612 15.6212 6.09529 15.7869C6.15615 15.9526 6.23053 16.1115 6.31844 16.2636V16.2602ZM5.46978 9.21062C5.2703 9.55718 5.14182 9.93925 5.08941 10.3348C5.037 10.7304 5.06405 11.1311 5.16717 11.5182C5.2703 11.9037 5.44781 12.2638 5.69125 12.5816C5.93469 12.8977 6.2373 13.1631 6.58217 13.3626L9.84664 15.2493C9.85002 15.2504 9.85396 15.2515 9.85847 15.2527H9.8703C9.87481 15.2527 9.87876 15.2515 9.88214 15.2493C9.88552 15.2482 9.8889 15.2465 9.89228 15.2442L11.2616 14.453L7.31925 12.1775C7.28037 12.1539 7.24318 12.1251 7.20937 12.093C7.17661 12.0602 7.1482 12.0232 7.12484 11.9831C7.10286 11.9426 7.08427 11.9003 7.07243 11.8547C7.0606 11.8107 7.05384 11.7651 7.05553 11.7177V7.87846C6.88985 7.93932 6.72925 8.0137 6.5771 8.10161C6.42495 8.19121 6.28125 8.29265 6.14601 8.40591C6.01245 8.51918 5.88735 8.64428 5.77408 8.77953C5.66082 8.91308 5.56107 9.05847 5.47316 9.21062H5.46978ZM16.6832 11.8208C16.7238 11.8445 16.761 11.8716 16.7948 11.9054C16.8269 11.9375 16.8557 11.9747 16.8794 12.0153C16.9013 12.0558 16.9199 12.0998 16.9318 12.1437C16.9419 12.1894 16.9487 12.235 16.947 12.2824V16.1216C17.4896 15.9221 17.963 15.5722 18.3129 15.1124C18.6646 14.6525 18.8759 14.1031 18.9249 13.5283C18.974 12.9535 18.859 12.3753 18.5919 11.8631C18.3248 11.3509 17.9174 10.9248 17.417 10.6374L14.1525 8.75079C14.1491 8.74966 14.1452 8.74853 14.1407 8.74741H14.1288C14.1254 8.74853 14.1215 8.74966 14.117 8.75079C14.1136 8.75191 14.1102 8.7536 14.1068 8.75586L12.7443 9.54366L16.6866 11.8208H16.6832ZM18.0441 9.77526H18.0425V9.77695L18.0441 9.77526ZM18.0425 9.77357C18.1405 9.20555 18.0746 8.62061 17.8514 8.08809C17.63 7.55556 17.2597 7.09742 16.7864 6.76607C16.313 6.43641 15.7551 6.24707 15.1787 6.22171C14.6005 6.19804 14.0291 6.33836 13.5287 6.62575L10.2642 8.51073C10.2608 8.51298 10.258 8.5158 10.2558 8.51918L10.249 8.52932C10.2479 8.5327 10.2467 8.53665 10.2456 8.54116C10.2445 8.54454 10.2439 8.54848 10.2439 8.55299V10.1286L14.1863 7.85141C14.2269 7.82774 14.2708 7.81084 14.3148 7.79731C14.3604 7.78548 14.4061 7.78041 14.4517 7.78041C14.499 7.78041 14.5447 7.78548 14.5903 7.79731C14.6343 7.81084 14.6766 7.82774 14.7171 7.85141L17.9478 9.71778C17.9765 9.73469 18.0188 9.75836 18.0425 9.77357ZM9.50007 8.02892C9.50007 7.98327 9.50683 7.93763 9.51867 7.89198C9.5305 7.84803 9.54741 7.80407 9.57108 7.7635C9.59474 7.72462 9.62179 7.68743 9.6556 7.65361C9.68772 7.62149 9.72492 7.59275 9.76549 7.57078L12.9961 5.70609C13.0266 5.6875 13.0688 5.66383 13.0925 5.65199C12.6496 5.28176 12.1086 5.04508 11.5355 4.97239C10.9624 4.89801 10.3809 4.9893 9.85847 5.23443C9.3344 5.47956 8.89147 5.87008 8.5821 6.35696C8.27273 6.84553 8.10874 7.41017 8.10874 7.98834V11.7583C8.10987 11.7628 8.111 11.7667 8.11212 11.7701C8.11325 11.7735 8.11494 11.7769 8.1172 11.7803C8.11945 11.7836 8.12227 11.787 8.12565 11.7904C8.1279 11.7927 8.13128 11.7949 8.13579 11.7972L9.50007 12.585V8.02892ZM10.2405 13.011L11.997 14.0253L13.7535 13.011V10.984L11.9987 9.96968L10.2422 10.984L10.2405 13.011Z", + "fill": "white" + }, + "children": [] + }, + { + "type": "element", + "name": "rect", + "attributes": { + "x": "0.5", + "y": "0.5", + "width": "23", + "height": "23", + "rx": "5.5", + "stroke": "black", + "stroke-opacity": "0.05" + }, + "children": [] + } + ] + }, + "name": "Gpt4" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/llm/Gpt4.tsx b/web/app/components/base/icons/src/public/llm/Gpt4.tsx new file mode 100644 index 0000000000..ae57fd707f --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/Gpt4.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Gpt4.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/llm/index.ts b/web/app/components/base/icons/src/public/llm/index.ts new file mode 100644 index 0000000000..235769dc29 --- /dev/null +++ b/web/app/components/base/icons/src/public/llm/index.ts @@ -0,0 +1,3 @@ +export { default as Anthropic } from './Anthropic' +export { default as Gpt3 } from './Gpt3' +export { default as Gpt4 } from './Gpt4' diff --git a/web/app/components/base/icons/src/public/model/Checked.json b/web/app/components/base/icons/src/public/model/Checked.json new file mode 100644 index 0000000000..f8ea944818 --- /dev/null +++ b/web/app/components/base/icons/src/public/model/Checked.json @@ -0,0 +1,29 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M13.3332 4L5.99984 11.3333L2.6665 8", + "stroke": "#155EEF", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + }, + "name": "Checked" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/model/Checked.tsx b/web/app/components/base/icons/src/public/model/Checked.tsx new file mode 100644 index 0000000000..a2f700d375 --- /dev/null +++ b/web/app/components/base/icons/src/public/model/Checked.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Checked.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/model/index.ts b/web/app/components/base/icons/src/public/model/index.ts new file mode 100644 index 0000000000..719a6f0309 --- /dev/null +++ b/web/app/components/base/icons/src/public/model/index.ts @@ -0,0 +1 @@ +export { default as Checked } from './Checked' diff --git a/web/app/components/base/icons/src/public/plugins/Google.json b/web/app/components/base/icons/src/public/plugins/Google.json new file mode 100644 index 0000000000..6f04dddb9b --- /dev/null +++ b/web/app/components/base/icons/src/public/plugins/Google.json @@ -0,0 +1,53 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M22.501 12.2331C22.501 11.3698 22.4296 10.7398 22.2748 10.0864H12.2153V13.983H18.12C18.001 14.9514 17.3582 16.4097 15.9296 17.3897L15.9096 17.5202L19.0902 19.9349L19.3106 19.9564C21.3343 18.1247 22.501 15.4297 22.501 12.2331Z", + "fill": "#4285F4" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M12.2147 22.5001C15.1075 22.5001 17.5361 21.5667 19.3099 19.9567L15.929 17.39C15.0242 18.0083 13.8099 18.44 12.2147 18.44C9.38142 18.44 6.97669 16.6083 6.11947 14.0767L5.99382 14.0871L2.68656 16.5955L2.64331 16.7133C4.40519 20.1433 8.02423 22.5001 12.2147 22.5001Z", + "fill": "#34A853" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M6.11997 14.0765C5.89379 13.4232 5.76289 12.7231 5.76289 11.9998C5.76289 11.2764 5.89379 10.5765 6.10807 9.92313L6.10208 9.78398L2.75337 7.23535L2.64381 7.28642C1.91765 8.70977 1.50098 10.3081 1.50098 11.9998C1.50098 13.6915 1.91765 15.2897 2.64381 16.7131L6.11997 14.0765Z", + "fill": "#FBBC05" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M12.2148 5.55997C14.2267 5.55997 15.5838 6.41163 16.3576 7.12335L19.3814 4.23C17.5243 2.53834 15.1076 1.5 12.2148 1.5C8.02426 1.5 4.4052 3.85665 2.64331 7.28662L6.10759 9.92332C6.97672 7.39166 9.38146 5.55997 12.2148 5.55997Z", + "fill": "#EB4335" + }, + "children": [] + } + ] + }, + "name": "Google" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/plugins/Google.tsx b/web/app/components/base/icons/src/public/plugins/Google.tsx new file mode 100644 index 0000000000..4cfe64c475 --- /dev/null +++ b/web/app/components/base/icons/src/public/plugins/Google.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Google.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/plugins/WebReader.json b/web/app/components/base/icons/src/public/plugins/WebReader.json new file mode 100644 index 0000000000..42ec3d9e78 --- /dev/null +++ b/web/app/components/base/icons/src/public/plugins/WebReader.json @@ -0,0 +1,39 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M9.59235 3.32566C10.3587 3.11341 11.1661 3 12 3C13.962 3 15.7773 3.62779 17.2561 4.69345C16.4693 5.21349 15.8824 5.77819 15.4756 6.38193C14.854 7.30445 14.6947 8.25844 14.8234 9.12887C14.9484 9.97416 15.3366 10.696 15.7446 11.2301C16.1402 11.7479 16.6256 12.181 17.0531 12.3946C18.1294 12.9327 19.3714 13.2022 20.2999 13.341C21.1399 13.4667 22.9206 13.8871 22.9865 12.5492C22.9955 12.3672 23 12.1841 23 12C23 5.92487 18.0751 1 12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23C12.1841 23 12.3672 22.9955 12.5492 22.9865C13.1008 22.9593 13.526 22.4902 13.4988 21.9385C13.4716 21.3869 13.0024 20.9618 12.4508 20.9889C12.3015 20.9963 12.1512 21 12 21C8.49063 21 5.45038 18.9914 3.96619 16.0611L4.93474 15.502L8.50745 16.1706C9.43309 16.3439 10.2876 15.6313 10.2834 14.6896L10.2694 11.5365L12.0952 8.41051C12.3911 7.90404 12.3646 7.27161 12.0274 6.79167L9.59235 3.32566Z", + "fill": "#444CE7" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M13.9456 12.6561C13.5777 12.5165 13.1621 12.6057 12.8839 12.884C12.6056 13.1623 12.5164 13.5778 12.656 13.9458L15.8228 22.2945C15.969 22.68 16.3367 22.9362 16.7489 22.9399C17.1611 22.9435 17.5333 22.6938 17.6863 22.3111L19.007 19.0071L22.311 17.6865C22.6937 17.5334 22.9434 17.1612 22.9397 16.749C22.9361 16.3368 22.6799 15.9691 22.2944 15.8229L13.9456 12.6561Z", + "fill": "#444CE7" + }, + "children": [] + } + ] + }, + "name": "WebReader" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/plugins/WebReader.tsx b/web/app/components/base/icons/src/public/plugins/WebReader.tsx new file mode 100644 index 0000000000..b7c9a83007 --- /dev/null +++ b/web/app/components/base/icons/src/public/plugins/WebReader.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './WebReader.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/plugins/Wikipedia.json b/web/app/components/base/icons/src/public/plugins/Wikipedia.json new file mode 100644 index 0000000000..7a16433be7 --- /dev/null +++ b/web/app/components/base/icons/src/public/plugins/Wikipedia.json @@ -0,0 +1,26 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M23.8431 5.0001H19.2179H19.0609V5.15706V5.66001V5.81696H19.2179H19.5393C19.9131 5.81696 20.2502 6.00882 20.4411 6.33021C20.632 6.65161 20.6392 7.0394 20.4603 7.36765L15.3174 16.8077L12.9751 11.2238L15.1813 7.17527C15.6379 6.33743 16.5143 5.81696 17.4684 5.81696H17.5726H17.7296V5.66001V5.15706V5.0001H17.5726H12.9474H12.7905V5.15706V5.66001V5.81696H12.9474H13.2688C13.6426 5.81696 13.9797 6.00882 14.1706 6.33021C14.3615 6.65161 14.3687 7.0394 14.1899 7.36765L12.5896 10.305L11.1634 6.9051C11.0601 6.65867 11.0856 6.38965 11.2336 6.16714C11.3816 5.94462 11.6197 5.81696 11.887 5.81696H12.2526H12.4095V5.66001V5.15706V5.0001H12.2526H6.72092H6.56396V5.15706V5.66001V5.81696H6.72092H6.79699C7.88821 5.81696 8.866 6.46719 9.28817 7.47344L11.3954 12.497L9.04698 16.8077L4.89304 6.9051C4.78966 6.65867 4.81525 6.38965 4.9632 6.16714C5.11116 5.94462 5.34932 5.81696 5.61657 5.81696H6.17832H6.33527V5.66001V5.15706V5.0001H6.17832H0.156957H0V5.15706V5.66001V5.81696H0.156957H0.52654C1.61776 5.81696 2.59561 6.46719 3.01772 7.47344L7.80628 18.889C7.89004 19.0887 8.08425 19.2177 8.30111 19.2177C8.50014 19.2177 8.67588 19.1131 8.77125 18.9381L9.39589 17.7918L11.7807 13.4155L14.0767 18.889C14.1604 19.0886 14.3547 19.2176 14.5715 19.2176C14.7705 19.2176 14.9463 19.1131 15.0417 18.938L15.6663 17.7917L21.4517 7.17517C21.9083 6.33733 22.7847 5.81686 23.7388 5.81686H23.843H24V5.6599V5.15696V5H23.8431V5.0001Z", + "fill": "#222A30" + }, + "children": [] + } + ] + }, + "name": "Wikipedia" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/plugins/Wikipedia.tsx b/web/app/components/base/icons/src/public/plugins/Wikipedia.tsx new file mode 100644 index 0000000000..7b5de1c036 --- /dev/null +++ b/web/app/components/base/icons/src/public/plugins/Wikipedia.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Wikipedia.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/plugins/index.ts b/web/app/components/base/icons/src/public/plugins/index.ts new file mode 100644 index 0000000000..38c48b5311 --- /dev/null +++ b/web/app/components/base/icons/src/public/plugins/index.ts @@ -0,0 +1,3 @@ +export { default as Google } from './Google' +export { default as WebReader } from './WebReader' +export { default as Wikipedia } from './Wikipedia' diff --git a/web/app/components/base/icons/src/public/thought/DataSet.json b/web/app/components/base/icons/src/public/thought/DataSet.json new file mode 100644 index 0000000000..55952fe9d2 --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/DataSet.json @@ -0,0 +1,64 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "12", + "height": "12", + "viewBox": "0 0 12 12", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "clip-path": "url(#clip0_7847_32895)" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M10.5 2.5C10.5 3.32843 8.48528 4 6 4C3.51472 4 1.5 3.32843 1.5 2.5M10.5 2.5C10.5 1.67157 8.48528 1 6 1C3.51472 1 1.5 1.67157 1.5 2.5M10.5 2.5V9.5C10.5 10.33 8.5 11 6 11C3.5 11 1.5 10.33 1.5 9.5V2.5M10.5 6C10.5 6.83 8.5 7.5 6 7.5C3.5 7.5 1.5 6.83 1.5 6", + "stroke": "#667085", + "stroke-width": "1.25", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "clip0_7847_32895" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "12", + "height": "12", + "fill": "white" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "DataSet" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/thought/DataSet.tsx b/web/app/components/base/icons/src/public/thought/DataSet.tsx new file mode 100644 index 0000000000..3d3046af82 --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/DataSet.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './DataSet.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/thought/Loading.json b/web/app/components/base/icons/src/public/thought/Loading.json new file mode 100644 index 0000000000..f19a3b1009 --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/Loading.json @@ -0,0 +1,64 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "12", + "height": "12", + "viewBox": "0 0 12 12", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "clip-path": "url(#clip0_7998_4025)" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M6 1.125V2.375M6 9V11M2.875 6H1.125M10.625 6H9.875M9.22855 9.22855L8.875 8.875M9.33211 2.70789L8.625 3.415M2.46079 9.53921L3.875 8.125M2.56434 2.60434L3.625 3.665", + "stroke": "#667085", + "stroke-width": "1.25", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "clip0_7998_4025" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "12", + "height": "12", + "fill": "white" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Loading" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/thought/Loading.tsx b/web/app/components/base/icons/src/public/thought/Loading.tsx new file mode 100644 index 0000000000..0dd6f18805 --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/Loading.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Loading.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/thought/Search.json b/web/app/components/base/icons/src/public/thought/Search.json new file mode 100644 index 0000000000..9213419bbc --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/Search.json @@ -0,0 +1,64 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "12", + "height": "12", + "viewBox": "0 0 12 12", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "clip-path": "url(#clip0_7847_32899)" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M10.5 10.5L8.75005 8.75M10 5.75C10 8.09721 8.09721 10 5.75 10C3.40279 10 1.5 8.09721 1.5 5.75C1.5 3.40279 3.40279 1.5 5.75 1.5C8.09721 1.5 10 3.40279 10 5.75Z", + "stroke": "#667085", + "stroke-width": "1.25", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "clip0_7847_32899" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "12", + "height": "12", + "fill": "white" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Search" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/thought/Search.tsx b/web/app/components/base/icons/src/public/thought/Search.tsx new file mode 100644 index 0000000000..120aaf8d36 --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/Search.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Search.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/thought/ThoughtList.json b/web/app/components/base/icons/src/public/thought/ThoughtList.json new file mode 100644 index 0000000000..8b97633444 --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/ThoughtList.json @@ -0,0 +1,83 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "12", + "height": "12", + "viewBox": "0 0 12 12", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M4 6C4 5.72386 4.22386 5.5 4.5 5.5L10.5 5.5C10.7761 5.5 11 5.72386 11 6C11 6.27614 10.7761 6.5 10.5 6.5L4.5 6.5C4.22386 6.5 4 6.27614 4 6Z", + "fill": "#667085" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M4 3C4 2.72386 4.22386 2.5 4.5 2.5L10.5 2.5C10.7761 2.5 11 2.72386 11 3C11 3.27614 10.7761 3.5 10.5 3.5L4.5 3.5C4.22386 3.5 4 3.27614 4 3Z", + "fill": "#667085" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M4 9C4 8.72386 4.22386 8.5 4.5 8.5L10.5 8.5C10.7761 8.5 11 8.72386 11 9C11 9.27614 10.7761 9.5 10.5 9.5L4.5 9.5C4.22386 9.5 4 9.27614 4 9Z", + "fill": "#667085" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M1 6C1 5.44772 1.44772 5 2 5C2.55228 5 3 5.44772 3 6C3 6.55228 2.55228 7 2 7C1.44772 7 1 6.55228 1 6Z", + "fill": "#667085" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M1 3C1 2.44772 1.44772 2 2 2C2.55228 2 3 2.44772 3 3C3 3.55228 2.55228 4 2 4C1.44772 4 1 3.55228 1 3Z", + "fill": "#667085" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M1 9C1 8.44772 1.44772 8 2 8C2.55228 8 3 8.44772 3 9C3 9.55228 2.55228 10 2 10C1.44772 10 1 9.55228 1 9Z", + "fill": "#667085" + }, + "children": [] + } + ] + }, + "name": "ThoughtList" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/thought/ThoughtList.tsx b/web/app/components/base/icons/src/public/thought/ThoughtList.tsx new file mode 100644 index 0000000000..8dfa9c5c80 --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/ThoughtList.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './ThoughtList.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/thought/WebReader.json b/web/app/components/base/icons/src/public/thought/WebReader.json new file mode 100644 index 0000000000..ecf85d9ec9 --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/WebReader.json @@ -0,0 +1,64 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "12", + "height": "12", + "viewBox": "0 0 12 12", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "clip-path": "url(#clip0_7847_32887)" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M4.5 1.75V1M2.53033 2.53033L2 2M2.53033 6.5L2 7.03033M6.5 2.53033L7.03033 2M1.75 4.5H1M7.93224 8.09479L6.68637 10.4085C6.54404 10.6728 6.47287 10.805 6.38725 10.8384C6.31295 10.8674 6.22926 10.8592 6.16199 10.8164C6.08447 10.767 6.04028 10.6235 5.95191 10.3366L4.22259 4.72263C4.1504 4.48825 4.1143 4.37107 4.14335 4.29192C4.16865 4.22298 4.22298 4.16865 4.29192 4.14335C4.37107 4.1143 4.48825 4.1504 4.72262 4.2226L10.3366 5.95192C10.6235 6.0403 10.767 6.08449 10.8164 6.16201C10.8592 6.22928 10.8674 6.31297 10.8384 6.38727C10.805 6.47289 10.6728 6.54406 10.4085 6.68639L8.09479 7.93224C8.05551 7.95339 8.03587 7.96396 8.01868 7.97755C8.00341 7.98961 7.98961 8.00341 7.97755 8.01868C7.96396 8.03587 7.95339 8.05551 7.93224 8.09479Z", + "stroke": "#667085", + "stroke-width": "1.25", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "clip0_7847_32887" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "12", + "height": "12", + "fill": "white" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "WebReader" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/thought/WebReader.tsx b/web/app/components/base/icons/src/public/thought/WebReader.tsx new file mode 100644 index 0000000000..b7c9a83007 --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/WebReader.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './WebReader.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/public/thought/index.ts b/web/app/components/base/icons/src/public/thought/index.ts new file mode 100644 index 0000000000..8a45489dbf --- /dev/null +++ b/web/app/components/base/icons/src/public/thought/index.ts @@ -0,0 +1,5 @@ +export { default as DataSet } from './DataSet' +export { default as Loading } from './Loading' +export { default as Search } from './Search' +export { default as ThoughtList } from './ThoughtList' +export { default as WebReader } from './WebReader' diff --git a/web/app/components/base/icons/src/vender/line/general/LinkExternal02.json b/web/app/components/base/icons/src/vender/line/general/LinkExternal02.json new file mode 100644 index 0000000000..af445595c8 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/LinkExternal02.json @@ -0,0 +1,38 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "12", + "height": "12", + "viewBox": "0 0 12 12", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "link-external-02" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Icon", + "d": "M10.5 4.5L10.5 1.5M10.5 1.5H7.49999M10.5 1.5L6 6M5 1.5H3.9C3.05992 1.5 2.63988 1.5 2.31901 1.66349C2.03677 1.8073 1.8073 2.03677 1.66349 2.31901C1.5 2.63988 1.5 3.05992 1.5 3.9V8.1C1.5 8.94008 1.5 9.36012 1.66349 9.68099C1.8073 9.96323 2.03677 10.1927 2.31901 10.3365C2.63988 10.5 3.05992 10.5 3.9 10.5H8.1C8.94008 10.5 9.36012 10.5 9.68099 10.3365C9.96323 10.1927 10.1927 9.96323 10.3365 9.68099C10.5 9.36012 10.5 8.94008 10.5 8.1V7", + "stroke": "currentColor", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + } + ] + }, + "name": "LinkExternal02" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/general/LinkExternal02.tsx b/web/app/components/base/icons/src/vender/line/general/LinkExternal02.tsx new file mode 100644 index 0000000000..895faefb5a --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/LinkExternal02.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './LinkExternal02.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/general/index.ts b/web/app/components/base/icons/src/vender/line/general/index.ts index d3d9e7e326..63cd25663f 100644 --- a/web/app/components/base/icons/src/vender/line/general/index.ts +++ b/web/app/components/base/icons/src/vender/line/general/index.ts @@ -1,4 +1,5 @@ export { default as Check } from './Check' +export { default as LinkExternal02 } from './LinkExternal02' export { default as Loading02 } from './Loading02' export { default as LogOut01 } from './LogOut01' export { default as Trash03 } from './Trash03' diff --git a/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertCircle.json b/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertCircle.json new file mode 100644 index 0000000000..9793cbb993 --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertCircle.json @@ -0,0 +1,38 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "alert-circle" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Solid", + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M8 0.666626C3.94992 0.666626 0.666672 3.94987 0.666672 7.99996C0.666672 12.05 3.94992 15.3333 8 15.3333C12.0501 15.3333 15.3333 12.05 15.3333 7.99996C15.3333 3.94987 12.0501 0.666626 8 0.666626ZM8.66667 5.33329C8.66667 4.9651 8.36819 4.66663 8 4.66663C7.63181 4.66663 7.33334 4.9651 7.33334 5.33329V7.99996C7.33334 8.36815 7.63181 8.66663 8 8.66663C8.36819 8.66663 8.66667 8.36815 8.66667 7.99996V5.33329ZM8 9.99996C7.63181 9.99996 7.33334 10.2984 7.33334 10.6666C7.33334 11.0348 7.63181 11.3333 8 11.3333H8.00667C8.37486 11.3333 8.67334 11.0348 8.67334 10.6666C8.67334 10.2984 8.37486 9.99996 8.00667 9.99996H8Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "AlertCircle" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertCircle.tsx b/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertCircle.tsx new file mode 100644 index 0000000000..0f6fb2c925 --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/AlertCircle.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './AlertCircle.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/index.ts b/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/index.ts index 6ad90c13fd..3af8739829 100644 --- a/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/index.ts +++ b/web/app/components/base/icons/src/vender/solid/alertsAndFeedback/index.ts @@ -1 +1,2 @@ +export { default as AlertCircle } from './AlertCircle' export { default as AlertTriangle } from './AlertTriangle' diff --git a/web/app/components/base/icons/src/vender/solid/general/CheckCircle.json b/web/app/components/base/icons/src/vender/solid/general/CheckCircle.json new file mode 100644 index 0000000000..1b567e859e --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/general/CheckCircle.json @@ -0,0 +1,38 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "check-circle" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Solid", + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M8 0.666626C3.94992 0.666626 0.666672 3.94987 0.666672 7.99996C0.666672 12.05 3.94992 15.3333 8 15.3333C12.0501 15.3333 15.3333 12.05 15.3333 7.99996C15.3333 3.94987 12.0501 0.666626 8 0.666626ZM11.4714 6.47136C11.7318 6.21101 11.7318 5.7889 11.4714 5.52855C11.2111 5.26821 10.7889 5.26821 10.5286 5.52855L7 9.05715L5.47141 7.52855C5.21106 7.2682 4.78895 7.2682 4.5286 7.52855C4.26825 7.7889 4.26825 8.21101 4.5286 8.47136L6.5286 10.4714C6.78895 10.7317 7.21106 10.7317 7.47141 10.4714L11.4714 6.47136Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "CheckCircle" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/solid/general/CheckCircle.tsx b/web/app/components/base/icons/src/vender/solid/general/CheckCircle.tsx new file mode 100644 index 0000000000..9664fd4785 --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/general/CheckCircle.tsx @@ -0,0 +1,14 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './CheckCircle.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/general/index.ts b/web/app/components/base/icons/src/vender/solid/general/index.ts index fda0a484e2..85aff32cd5 100644 --- a/web/app/components/base/icons/src/vender/solid/general/index.ts +++ b/web/app/components/base/icons/src/vender/solid/general/index.ts @@ -1,2 +1,3 @@ +export { default as CheckCircle } from './CheckCircle' export { default as Download02 } from './Download02' export { default as XCircle } from './XCircle' diff --git a/web/app/components/base/markdown.tsx b/web/app/components/base/markdown.tsx index ca2cc35814..b65ff9027f 100644 --- a/web/app/components/base/markdown.tsx +++ b/web/app/components/base/markdown.tsx @@ -1,16 +1,26 @@ -import ReactMarkdown from "react-markdown"; -import "katex/dist/katex.min.css"; -import RemarkMath from "remark-math"; -import RemarkBreaks from "remark-breaks"; -import RehypeKatex from "rehype-katex"; -import RemarkGfm from "remark-gfm"; +import ReactMarkdown from 'react-markdown' +import 'katex/dist/katex.min.css' +import RemarkMath from 'remark-math' +import RemarkBreaks from 'remark-breaks' +import RehypeKatex from 'rehype-katex' +import RemarkGfm from 'remark-gfm' import SyntaxHighlighter from 'react-syntax-highlighter' import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs' -import { useRef, useState, RefObject, useEffect } from "react"; +import type { RefObject } from 'react' +import { useEffect, useRef, useState } from 'react' // import { copyToClipboard } from "../utils"; +// https://txtfiddle.com/~hlshwya/extract-urls-from-text +// const urlRegex = /\b((https?|ftp|file):\/\/|(www|ftp)\.)[-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/ig +// function highlightURL(content: string) { +// return content.replace(urlRegex, (url) => { +// // fix http:// in [] will be parsed to link agin +// const res = `[${url.replace('://', '://')}](${url})` +// return res +// }) +// } export function PreCode(props: { children: any }) { - const ref = useRef(null); + const ref = useRef(null) return (
@@ -18,38 +28,37 @@ export function PreCode(props: { children: any }) {
         className="copy-code-button"
         onClick={() => {
           if (ref.current) {
-            const code = ref.current.innerText;
+            const code = ref.current.innerText
             // copyToClipboard(code);
           }
         }}
       >
       {props.children}
     
- ); + ) } const useLazyLoad = (ref: RefObject): boolean => { - const [isIntersecting, setIntersecting] = useState(false); + const [isIntersecting, setIntersecting] = useState(false) useEffect(() => { const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { - setIntersecting(true); - observer.disconnect(); + setIntersecting(true) + observer.disconnect() } - }); + }) - if (ref.current) { - observer.observe(ref.current); - } + if (ref.current) + observer.observe(ref.current) return () => { - observer.disconnect(); - }; - }, [ref]); + observer.disconnect() + } + }, [ref]) - return isIntersecting; -}; + return isIntersecting +} export function Markdown(props: { content: string }) { return ( @@ -62,26 +71,29 @@ export function Markdown(props: { content: string }) { components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || '') - return !inline && match ? ( - - ) : ( - - {children} - - ) - } + return (!inline && match) + ? ( + + ) + : ( + + {children} + + ) + }, }} - linkTarget={"_blank"} + linkTarget={'_blank'} > + {/* Markdown detect has problem. */} {props.content}
- ); + ) } diff --git a/web/app/components/base/toast/index.tsx b/web/app/components/base/toast/index.tsx index e907058ec7..a30f962ab2 100644 --- a/web/app/components/base/toast/index.tsx +++ b/web/app/components/base/toast/index.tsx @@ -9,7 +9,7 @@ import { InformationCircleIcon, XCircleIcon, } from '@heroicons/react/20/solid' -import { createContext } from 'use-context-selector' +import { createContext, useContext } from 'use-context-selector' export type IToastProps = { type?: 'success' | 'error' | 'warning' | 'info' @@ -24,6 +24,7 @@ type IToastContext = { const defaultDuring = 3000 export const ToastContext = createContext({} as IToastContext) +export const useToastContext = () => useContext(ToastContext) const Toast = ({ type = 'info', duration, @@ -31,9 +32,9 @@ const Toast = ({ children, }: IToastProps) => { // sometimes message is react node array. Not handle it. - if (typeof message !== 'string') { + if (typeof message !== 'string') return null - } + return
( - + ) const DiscoveryIcon = () => ( - + + +) + +const SelectedChatIcon = () => ( + + + +) + +const ChatIcon = () => ( + + ) @@ -33,6 +45,7 @@ const SideBar: FC<{ const segments = useSelectedLayoutSegments() const lastSegment = segments.slice(-1)[0] const isDiscoverySelected = lastSegment === 'apps' + const isChatSelected = lastSegment === 'chat' const { installedApps, setInstalledApps } = useContext(ExploreContext) const fetchInstalledAppList = async () => { @@ -81,6 +94,14 @@ const SideBar: FC<{ {isDiscoverySelected ? : }
{t('explore.sidebar.discovery')}
+ + {isChatSelected ? : } +
{t('explore.sidebar.chat')}
+
{installedApps.length > 0 && (
diff --git a/web/app/components/explore/universal-chat/config-view/detail/index.tsx b/web/app/components/explore/universal-chat/config-view/detail/index.tsx new file mode 100644 index 0000000000..916fb8a19a --- /dev/null +++ b/web/app/components/explore/universal-chat/config-view/detail/index.tsx @@ -0,0 +1,34 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import cn from 'classnames' +import { useTranslation } from 'react-i18next' +import s from './style.module.css' +import Config from '@/app/components/explore/universal-chat/config' + +type Props = { + modelId: string + plugins: Record + dataSets: any[] +} +const ConfigViewPanel: FC = ({ + modelId, + plugins, + dataSets, +}) => { + const { t } = useTranslation() + return ( +
+
+ +
{t('explore.universalChat.viewConfigDetailTip')}
+
+
+ ) +} +export default React.memo(ConfigViewPanel) diff --git a/web/app/components/explore/universal-chat/config-view/detail/style.module.css b/web/app/components/explore/universal-chat/config-view/detail/style.module.css new file mode 100644 index 0000000000..98e79c6d1a --- /dev/null +++ b/web/app/components/explore/universal-chat/config-view/detail/style.module.css @@ -0,0 +1,9 @@ +.btn { + background: url(~@/app/components/datasets/documents/assets/action.svg) center center no-repeat transparent; + background-size: 16px 16px; + /* mask-image: ; */ +} + +.panelBorder { + border: 0.5px solid rgba(0, 0, 0, .05); +} \ No newline at end of file diff --git a/web/app/components/explore/universal-chat/config-view/summary/index.tsx b/web/app/components/explore/universal-chat/config-view/summary/index.tsx new file mode 100644 index 0000000000..b098e9ed8a --- /dev/null +++ b/web/app/components/explore/universal-chat/config-view/summary/index.tsx @@ -0,0 +1,84 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import cn from 'classnames' +import { useBoolean, useClickAway } from 'ahooks' +import s from './style.module.css' +import ModelIcon from '@/app/components/app/configuration/config-model/model-icon' +import { Google, WebReader, Wikipedia } from '@/app/components/base/icons/src/public/plugins' +import ConfigDetail from '@/app/components/explore/universal-chat/config-view/detail' + +export type ISummaryProps = { + modelId: string + plugins: Record + dataSets: any[] +} + +const getColorInfo = (modelId: string) => { + if (modelId === 'gpt-4') + return s.gpt4 + + if (modelId === 'claude-2') + return s.claude + + return s.gpt3 +} + +const getPlugIcon = (pluginId: string) => { + const className = 'w-4 h-4' + switch (pluginId) { + case 'google_search': + return + case 'web_reader': + return + case 'wikipedia': + return + default: + return null + } +} + +const Summary: FC = ({ + modelId, + plugins, + dataSets, +}) => { + const pluginIds = Object.keys(plugins).filter(key => plugins[key]) + const [isShowConfig, { setFalse: hideConfig, toggle: toggleShowConfig }] = useBoolean(false) + const configContentRef = React.useRef(null) + + useClickAway(() => { + hideConfig() + }, configContentRef) + return ( +
+
+ +
{modelId}
+ { + pluginIds.length > 0 && ( +
+
+
+ {pluginIds.map(pluginId => ( +
+ {getPlugIcon(pluginId)}
+ ))} +
+
+ ) + } +
+ {isShowConfig && ( + + )} +
+ + ) +} +export default React.memo(Summary) diff --git a/web/app/components/explore/universal-chat/config-view/summary/style.module.css b/web/app/components/explore/universal-chat/config-view/summary/style.module.css new file mode 100644 index 0000000000..15b37d66c1 --- /dev/null +++ b/web/app/components/explore/universal-chat/config-view/summary/style.module.css @@ -0,0 +1,21 @@ +.border { + border: 1px solid rgba(0, 0, 0, 0.05); +} + +.gpt3 { + background: linear-gradient(0deg, #D3F8DF, #D3F8DF), +linear-gradient(0deg, #EDFCF2, #EDFCF2); + border: 1px solid rgba(211, 248, 223, 1) +} + +.gpt4 { + background: linear-gradient(0deg, #EBE9FE, #EBE9FE), + linear-gradient(0deg, #F4F3FF, #F4F3FF); + border: 1px solid rgba(235, 233, 254, 1) +} + +.claude { + background: linear-gradient(0deg, #F9EBDF, #F9EBDF), +linear-gradient(0deg, #FCF3EB, #FCF3EB); + border: 1px solid rgba(249, 235, 223, 1) +} \ No newline at end of file diff --git a/web/app/components/explore/universal-chat/config/data-config/index.tsx b/web/app/components/explore/universal-chat/config/data-config/index.tsx new file mode 100644 index 0000000000..9720227d28 --- /dev/null +++ b/web/app/components/explore/universal-chat/config/data-config/index.tsx @@ -0,0 +1,95 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { useBoolean } from 'ahooks' +import { isEqual } from 'lodash-es' +import produce from 'immer' +import FeaturePanel from '@/app/components/app/configuration/base/feature-panel' +import OperationBtn from '@/app/components/app/configuration/base/operation-btn' +import CardItem from '@/app/components/app/configuration/dataset-config/card-item' +import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset' +import type { DataSet } from '@/models/datasets' + +type Props = { + readonly?: boolean + dataSets: DataSet[] + onChange?: (data: DataSet[]) => void +} + +const DatasetConfig: FC = ({ + readonly, + dataSets, + onChange, +}) => { + const { t } = useTranslation() + + const selectedIds = dataSets.map(item => item.id) + + const hasData = dataSets.length > 0 + const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false) + const handleSelect = (data: DataSet[]) => { + if (isEqual(data.map(item => item.id), dataSets.map(item => item.id))) { + hideSelectDataSet() + return + } + + if (data.find(item => !item.name)) { // has not loaded selected dataset + const newSelected = produce(data, (draft) => { + data.forEach((item, index) => { + if (!item.name) { // not fetched database + const newItem = dataSets.find(i => i.id === item.id) + if (newItem) + draft[index] = newItem + } + }) + }) + onChange?.(newSelected) + } + else { + onChange?.(data) + } + hideSelectDataSet() + } + const onRemove = (id: string) => { + onChange?.(dataSets.filter(item => item.id !== id)) + } + + return ( + } + hasHeaderBottomBorder={!hasData} + > + {hasData + ? ( +
+ {dataSets.map(item => ( + + ))} +
+ ) + : ( +
{t('appDebug.feature.dataSet.noData')}
+ )} + + {isShowSelectDataSet && ( + + )} +
+ ) +} +export default React.memo(DatasetConfig) diff --git a/web/app/components/explore/universal-chat/config/index.tsx b/web/app/components/explore/universal-chat/config/index.tsx new file mode 100644 index 0000000000..c0932af932 --- /dev/null +++ b/web/app/components/explore/universal-chat/config/index.tsx @@ -0,0 +1,51 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import ModelConfig from './model-config' +import DataConfig from './data-config' +import PluginConfig from './plugins-config' + +export type IConfigProps = { + className?: string + readonly?: boolean + modelId: string + onModelChange?: (modelId: string) => void + plugins: Record + onPluginChange?: (key: string, value: boolean) => void + dataSets: any[] + onDataSetsChange?: (contexts: any[]) => void +} + +const Config: FC = ({ + className, + readonly, + modelId, + onModelChange, + plugins, + onPluginChange, + dataSets, + onDataSetsChange, +}) => { + return ( +
+ + + {(!readonly || (readonly && dataSets.length > 0)) && ( + + )} +
+ ) +} +export default React.memo(Config) diff --git a/web/app/components/explore/universal-chat/config/model-config/index.tsx b/web/app/components/explore/universal-chat/config/model-config/index.tsx new file mode 100644 index 0000000000..d013d861cd --- /dev/null +++ b/web/app/components/explore/universal-chat/config/model-config/index.tsx @@ -0,0 +1,61 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import cn from 'classnames' +import { useBoolean, useClickAway } from 'ahooks' +import { ChevronDownIcon } from '@heroicons/react/24/outline' +import { useTranslation } from 'react-i18next' +import ModelIcon from '@/app/components/app/configuration/config-model/model-icon' +import { UNIVERSAL_CHAT_MODEL_LIST as MODEL_LIST } from '@/config' +import { Checked as CheckedIcon } from '@/app/components/base/icons/src/public/model' +export type IModelConfigProps = { + modelId: string + onChange?: (model: string) => void + readonly?: boolean +} + +const ModelConfig: FC = ({ + modelId, + onChange, + readonly, +}) => { + const { t } = useTranslation() + + const currModel = MODEL_LIST.find(item => item.id === modelId) + const [isShowOption, { setFalse: hideOption, toggle: toogleOption }] = useBoolean(false) + const triggerRef = React.useRef(null) + useClickAway(() => { + hideOption() + }, triggerRef) + + return ( +
+
{t('explore.universalChat.model')}
+
+
!readonly && toogleOption()} + className={cn( + readonly ? 'cursor-not-allowed' : 'cursor-pointer', 'flex items-center h-9 px-3 space-x-2 rounded-lg', + isShowOption && 'bg-gray-100', + )}> + +
{currModel?.name}
+ {!readonly && } +
+ {isShowOption && ( +
+ {MODEL_LIST.map(item => ( +
onChange?.(item.id)} className="w-[232px] flex items-center h-9 px-4 rounded-lg cursor-pointer hover:bg-gray-100"> + +
{item.name}
+ {(item.id === currModel?.id) && } +
+ ))} +
+ )} +
+
+ ) +} +export default React.memo(ModelConfig) diff --git a/web/app/components/explore/universal-chat/config/plugins-config/index.tsx b/web/app/components/explore/universal-chat/config/plugins-config/index.tsx new file mode 100644 index 0000000000..e374d79fef --- /dev/null +++ b/web/app/components/explore/universal-chat/config/plugins-config/index.tsx @@ -0,0 +1,111 @@ +'use client' +import type { FC } from 'react' +import React, { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import Item from './item' +import FeaturePanel from '@/app/components/app/configuration/base/feature-panel' +import { Google, WebReader, Wikipedia } from '@/app/components/base/icons/src/public/plugins' +import { getToolProviders } from '@/service/explore' +import Loading from '@/app/components/base/loading' +import AccountSetting from '@/app/components/header/account-setting' + +export type IPluginsProps = { + readonly?: boolean + config: Record + onChange?: (key: string, value: boolean) => void +} + +const plugins = [ + { key: 'google_search', icon: }, + { key: 'web_reader', icon: }, + { key: 'wikipedia', icon: }, +] +const Plugins: FC = ({ + readonly, + config, + onChange, +}) => { + const { t } = useTranslation() + const [isLoading, setIsLoading] = React.useState(!readonly) + const [isSerpApiValid, setIsSerpApiValid] = React.useState(false) + const checkSerpApiKey = async () => { + if (readonly) + return + + const provides: any = await getToolProviders() + const isSerpApiValid = !!provides.find((v: any) => v.tool_name === 'serpapi' && v.is_enabled) + setIsSerpApiValid(isSerpApiValid) + setIsLoading(false) + } + useEffect(() => { + checkSerpApiKey() + }, []) + + const [showSetSerpAPIKeyModal, setShowSetAPIKeyModal] = React.useState(false) + + const itemConfigs = plugins.map((plugin) => { + const res: Record = { ...plugin } + const { key } = plugin + res.name = t(`explore.universalChat.plugins.${key}.name`) + if (key === 'web_reader') + res.description = t(`explore.universalChat.plugins.${key}.description`) + + if (key === 'google_search' && !isSerpApiValid && !readonly) { + res.readonly = true + res.more = ( +
+ {t('explore.universalChat.plugins.google_search.more.left')} + setShowSetAPIKeyModal(true)}>{t('explore.universalChat.plugins.google_search.more.link')} + {t('explore.universalChat.plugins.google_search.more.right')} +
+ ) + } + return res + }) + + const enabledPluginNum = Object.values(config).filter(v => v).length + + return ( + <> + +
{t('explore.universalChat.plugins.name')}
+
({enabledPluginNum}/{plugins.length})
+
} + hasHeaderBottomBorder={false} + > + {isLoading + ? ( +
+ +
+ ) + : (
+ {itemConfigs.map(item => ( + onChange?.(item.key, enabled)} + readonly={readonly || item.readonly} + /> + ))} +
)} + + { + showSetSerpAPIKeyModal && ( + { + setShowSetAPIKeyModal(false) + await checkSerpApiKey() + }} /> + ) + } + + ) +} +export default React.memo(Plugins) diff --git a/web/app/components/explore/universal-chat/config/plugins-config/item.module.css b/web/app/components/explore/universal-chat/config/plugins-config/item.module.css new file mode 100644 index 0000000000..6acd346352 --- /dev/null +++ b/web/app/components/explore/universal-chat/config/plugins-config/item.module.css @@ -0,0 +1,3 @@ +.shadow { + box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); +} \ No newline at end of file diff --git a/web/app/components/explore/universal-chat/config/plugins-config/item.tsx b/web/app/components/explore/universal-chat/config/plugins-config/item.tsx new file mode 100644 index 0000000000..27af02ea2b --- /dev/null +++ b/web/app/components/explore/universal-chat/config/plugins-config/item.tsx @@ -0,0 +1,43 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import cn from 'classnames' +import s from './item.module.css' +import Switch from '@/app/components/base/switch' + +export type IItemProps = { + icon: React.ReactNode + name: string + description?: string + more?: React.ReactNode + enabled: boolean + onChange: (enabled: boolean) => void + readonly?: boolean +} + +const Item: FC = ({ + icon, + name, + description, + more, + enabled, + onChange, + readonly, +}) => { + return ( +
+
+
+ {icon} +
+
{name}
+ {description &&
{description}
} +
+
+ +
+ {more} +
+ ) +} +export default React.memo(Item) diff --git a/web/app/components/explore/universal-chat/hooks/use-conversation.ts b/web/app/components/explore/universal-chat/hooks/use-conversation.ts new file mode 100644 index 0000000000..4e7f27cf50 --- /dev/null +++ b/web/app/components/explore/universal-chat/hooks/use-conversation.ts @@ -0,0 +1,72 @@ +import { useState } from 'react' +import produce from 'immer' +import { useGetState } from 'ahooks' +import type { ConversationItem } from '@/models/share' + +const storageConversationIdKey = 'conversationIdInfo' + +type ConversationInfoType = Omit +function useConversation() { + const [conversationList, setConversationList] = useState([]) + const [pinnedConversationList, setPinnedConversationList] = useState([]) + const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState('-1') + // when set conversation id, we do not have set appId + const setCurrConversationId = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => { + doSetCurrConversationId(id) + if (isSetToLocalStroge && id !== '-1') { + // conversationIdInfo: {[appId1]: conversationId1, [appId2]: conversationId2} + const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {} + conversationIdInfo[appId] = id + globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo)) + } + } + + const getConversationIdFromStorage = (appId: string) => { + const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey) ? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '') : {} + const id = conversationIdInfo[appId] + return id + } + + const isNewConversation = currConversationId === '-1' + // input can be updated by user + const [newConversationInputs, setNewConversationInputs] = useState | null>(null) + const resetNewConversationInputs = () => { + if (!newConversationInputs) + return + setNewConversationInputs(produce(newConversationInputs, (draft) => { + Object.keys(draft).forEach((key) => { + draft[key] = '' + }) + })) + } + const [existConversationInputs, setExistConversationInputs] = useState | null>(null) + const currInputs = isNewConversation ? newConversationInputs : existConversationInputs + const setCurrInputs = isNewConversation ? setNewConversationInputs : setExistConversationInputs + + // info is muted + const [newConversationInfo, setNewConversationInfo] = useState(null) + const [existConversationInfo, setExistConversationInfo] = useState(null) + const currConversationInfo = isNewConversation ? newConversationInfo : existConversationInfo + + return { + conversationList, + setConversationList, + pinnedConversationList, + setPinnedConversationList, + currConversationId, + getCurrConversationId, + setCurrConversationId, + getConversationIdFromStorage, + isNewConversation, + currInputs, + newConversationInputs, + existConversationInputs, + resetNewConversationInputs, + setCurrInputs, + currConversationInfo, + setNewConversationInfo, + setExistConversationInfo, + } +} + +export default useConversation diff --git a/web/app/components/explore/universal-chat/index.tsx b/web/app/components/explore/universal-chat/index.tsx new file mode 100644 index 0000000000..0b8598a9a9 --- /dev/null +++ b/web/app/components/explore/universal-chat/index.tsx @@ -0,0 +1,725 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/* eslint-disable @typescript-eslint/no-use-before-define */ +'use client' +import type { FC } from 'react' +import React, { useEffect, useRef, useState } from 'react' +import cn from 'classnames' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import produce from 'immer' +import { useBoolean, useGetState } from 'ahooks' +import AppUnavailable from '../../base/app-unavailable' +import useConversation from './hooks/use-conversation' +import s from './style.module.css' +import Init from './init' +import { ToastContext } from '@/app/components/base/toast' +import Sidebar from '@/app/components/share/chat/sidebar' +import { + delConversation, + fetchAppParams, + fetchChatList, + fetchConversations, + fetchSuggestedQuestions, + pinConversation, + sendChatMessage, + stopChatMessageResponding, + unpinConversation, + updateFeedback, +} from '@/service/universal-chat' +import type { ConversationItem, SiteInfo } from '@/models/share' +import type { PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug' +import type { Feedbacktype, IChatItem } from '@/app/components/app/chat/type' +import Chat from '@/app/components/app/chat' +import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' +import Loading from '@/app/components/base/loading' +import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel' +import { userInputsFormToPromptVariables } from '@/utils/model-config' +import Confirm from '@/app/components/base/confirm' +import type { DataSet } from '@/models/datasets' +import ConfigSummary from '@/app/components/explore/universal-chat/config-view/summary' +import { fetchDatasets } from '@/service/datasets' +import ItemOperation from '@/app/components/explore/item-operation' + +const APP_ID = 'universal-chat' +const DEFAULT_MODEL_ID = 'gpt-3.5-turbo' // gpt-4, claude-2 +const DEFAULT_PLUGIN = { + google_search: false, + web_reader: true, + wikipedia: true, +} +export type IMainProps = {} + +const Main: FC = () => { + const { t } = useTranslation() + const media = useBreakpoints() + const isMobile = media === MediaType.mobile + + /* + * app info + */ + const [appUnavailable, setAppUnavailable] = useState(false) + const [isUnknwonReason, setIsUnknwonReason] = useState(false) + const siteInfo: SiteInfo = ( + { + title: 'universal Chatbot', + icon: '', + icon_background: '', + description: '', + default_language: 'en', // TODO + prompt_public: true, + } + ) + const [promptConfig, setPromptConfig] = useState(null) + const [inited, setInited] = useState(false) + // in mobile, show sidebar by click button + const [isShowSidebar, { setTrue: showSidebar, setFalse: hideSidebar }] = useBoolean(false) + /* + * conversation info + */ + const [allConversationList, setAllConversationList] = useState([]) + const [isClearConversationList, { setTrue: clearConversationListTrue, setFalse: clearConversationListFalse }] = useBoolean(false) + const [isClearPinnedConversationList, { setTrue: clearPinnedConversationListTrue, setFalse: clearPinnedConversationListFalse }] = useBoolean(false) + const { + conversationList, + setConversationList, + pinnedConversationList, + setPinnedConversationList, + currConversationId, + getCurrConversationId, + setCurrConversationId, + getConversationIdFromStorage, + isNewConversation, + currConversationInfo, + currInputs, + newConversationInputs, + // existConversationInputs, + resetNewConversationInputs, + setCurrInputs, + setNewConversationInfo, + setExistConversationInfo, + } = useConversation() + const [hasMore, setHasMore] = useState(true) + const [hasPinnedMore, setHasPinnedMore] = useState(true) + const onMoreLoaded = ({ data: conversations, has_more }: any) => { + setHasMore(has_more) + if (isClearConversationList) { + setConversationList(conversations) + clearConversationListFalse() + } + else { + setConversationList([...conversationList, ...conversations]) + } + } + const onPinnedMoreLoaded = ({ data: conversations, has_more }: any) => { + setHasPinnedMore(has_more) + if (isClearPinnedConversationList) { + setPinnedConversationList(conversations) + clearPinnedConversationListFalse() + } + else { + setPinnedConversationList([...pinnedConversationList, ...conversations]) + } + } + const [controlUpdateConversationList, setControlUpdateConversationList] = useState(0) + const noticeUpdateList = () => { + setHasMore(true) + clearConversationListTrue() + + setHasPinnedMore(true) + clearPinnedConversationListTrue() + + setControlUpdateConversationList(Date.now()) + } + const handlePin = async (id: string) => { + await pinConversation(id) + setControlItemOpHide(Date.now()) + notify({ type: 'success', message: t('common.api.success') }) + noticeUpdateList() + } + + const handleUnpin = async (id: string) => { + await unpinConversation(id) + setControlItemOpHide(Date.now()) + notify({ type: 'success', message: t('common.api.success') }) + noticeUpdateList() + } + const [isShowConfirm, { setTrue: showConfirm, setFalse: hideConfirm }] = useBoolean(false) + const [toDeleteConversationId, setToDeleteConversationId] = useState('') + const handleDelete = (id: string) => { + setToDeleteConversationId(id) + hideSidebar() // mobile + showConfirm() + } + + const didDelete = async () => { + await delConversation(toDeleteConversationId) + setControlItemOpHide(Date.now()) + notify({ type: 'success', message: t('common.api.success') }) + hideConfirm() + if (currConversationId === toDeleteConversationId) + handleConversationIdChange('-1') + + noticeUpdateList() + } + + const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState(null) + const [speechToTextConfig, setSpeechToTextConfig] = useState(null) + + const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false) + + const conversationName = currConversationInfo?.name || t('share.chat.newChatDefaultName') as string + const conversationIntroduction = currConversationInfo?.introduction || '' + + const handleConversationSwitch = async () => { + if (!inited) + return + + // update inputs of current conversation + let notSyncToStateIntroduction = '' + let notSyncToStateInputs: Record | undefined | null = {} + // debugger + if (!isNewConversation) { + const item = allConversationList.find(item => item.id === currConversationId) as any + notSyncToStateInputs = item?.inputs || {} + // setCurrInputs(notSyncToStateInputs) + notSyncToStateIntroduction = item?.introduction || '' + setExistConversationInfo({ + name: item?.name || '', + introduction: notSyncToStateIntroduction, + }) + const modelConfig = item?.model_config + if (modelConfig) { + setModeId(modelConfig.model_id) + const pluginConfig: Record = {} + const datasetIds: string[] = [] + modelConfig.agent_mode.tools.forEach((item: any) => { + const pluginName = Object.keys(item)[0] + if (pluginName === 'dataset') + datasetIds.push(item.dataset.id) + else + pluginConfig[pluginName] = item[pluginName].enabled + }) + setPlugins(pluginConfig) + if (datasetIds.length > 0) { + const { data } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasetIds } }) + setDateSets(data) + } + else { + setDateSets([]) + } + } + else { + configSetDefaultValue() + } + } + else { + configSetDefaultValue() + notSyncToStateInputs = newConversationInputs + setCurrInputs(notSyncToStateInputs) + } + + // update chat list of current conversation + if (!isNewConversation && !conversationIdChangeBecauseOfNew) { + fetchChatList(currConversationId).then((res: any) => { + const { data } = res + const newChatList: IChatItem[] = generateNewChatListWithOpenstatement(notSyncToStateIntroduction, notSyncToStateInputs) + + data.forEach((item: any) => { + newChatList.push({ + id: `question-${item.id}`, + content: item.query, + isAnswer: false, + }) + newChatList.push({ + ...item, + id: item.id, + content: item.answer, + feedback: item.feedback, + isAnswer: true, + }) + }) + setChatList(newChatList) + setErrorHappened(false) + }) + } + + if (isNewConversation) { + setChatList(generateNewChatListWithOpenstatement()) + setErrorHappened(false) + } + + setControlFocus(Date.now()) + } + + useEffect(() => { + handleConversationSwitch() + }, [currConversationId, inited]) + + const handleConversationIdChange = (id: string) => { + if (id === '-1') { + createNewChat() + setConversationIdChangeBecauseOfNew(true) + } + else { + setConversationIdChangeBecauseOfNew(false) + } + // trigger handleConversationSwitch + setCurrConversationId(id, APP_ID) + setIsShowSuggestion(false) + hideSidebar() + } + + /* + * chat info. chat is under conversation. + */ + const [chatList, setChatList, getChatList] = useGetState([]) + const chatListDomRef = useRef(null) + useEffect(() => { + // scroll to bottom + if (chatListDomRef.current) + chatListDomRef.current.scrollTop = chatListDomRef.current.scrollHeight + }, [chatList, currConversationId]) + + // user can not edit inputs if user had send message + const createNewChat = async () => { + // if new chat is already exist, do not create new chat + abortController?.abort() + setResponsingFalse() + if (conversationList.some(item => item.id === '-1')) + return + + setConversationList(produce(conversationList, (draft) => { + draft.unshift({ + id: '-1', + name: t('share.chat.newChatDefaultName'), + inputs: newConversationInputs, + introduction: conversationIntroduction, + }) + })) + configSetDefaultValue() + } + + // sometime introduction is not applied to state + const generateNewChatListWithOpenstatement = (introduction?: string, inputs?: Record | null) => { + let caculatedIntroduction = introduction || conversationIntroduction || '' + const caculatedPromptVariables = inputs || currInputs || null + if (caculatedIntroduction && caculatedPromptVariables) + caculatedIntroduction = replaceStringWithValues(caculatedIntroduction, promptConfig?.prompt_variables || [], caculatedPromptVariables) + + const openstatement = { + id: `${Date.now()}`, + content: caculatedIntroduction, + isAnswer: true, + feedbackDisabled: true, + isOpeningStatement: true, + } + if (caculatedIntroduction) + return [openstatement] + + return [] + } + + const fetchAllConversations = () => { + return fetchConversations(undefined, undefined, 100) + } + + const fetchInitData = async () => { + return Promise.all([fetchAllConversations(), fetchAppParams()]) + } + + // init + useEffect(() => { + (async () => { + try { + const [conversationData, appParams]: any = await fetchInitData() + const prompt_template = '' + // handle current conversation id + const { data: allConversations } = conversationData as { data: ConversationItem[]; has_more: boolean } + const _conversationId = getConversationIdFromStorage(APP_ID) + const isNotNewConversation = allConversations.some(item => item.id === _conversationId) + setAllConversationList(allConversations) + // fetch new conversation info + const { user_input_form, opening_statement: introduction, suggested_questions_after_answer, speech_to_text }: any = appParams + const prompt_variables = userInputsFormToPromptVariables(user_input_form) + + setNewConversationInfo({ + name: t('share.chat.newChatDefaultName'), + introduction, + }) + setPromptConfig({ + prompt_template, + prompt_variables, + } as PromptConfig) + setSuggestedQuestionsAfterAnswerConfig(suggested_questions_after_answer) + setSpeechToTextConfig(speech_to_text) + + if (isNotNewConversation) + setCurrConversationId(_conversationId, APP_ID, false) + + setInited(true) + } + catch (e: any) { + if (e.status === 404) { + setAppUnavailable(true) + } + else { + setIsUnknwonReason(true) + setAppUnavailable(true) + } + } + })() + }, []) + + const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) + const [abortController, setAbortController] = useState(null) + const { notify } = useContext(ToastContext) + const logError = (message: string) => { + notify({ type: 'error', message }) + } + + const checkCanSend = () => { + if (currConversationId !== '-1') + return true + + const prompt_variables = promptConfig?.prompt_variables + const inputs = currInputs + if (!inputs || !prompt_variables || prompt_variables?.length === 0) + return true + + let hasEmptyInput = false + const requiredVars = prompt_variables?.filter(({ key, name, required }) => { + const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) + return res + }) || [] // compatible with old version + requiredVars.forEach(({ key }) => { + if (hasEmptyInput) + return + + if (!inputs?.[key]) + hasEmptyInput = true + }) + + if (hasEmptyInput) { + logError(t('appDebug.errorMessage.valueOfVarRequired')) + return false + } + return !hasEmptyInput + } + + const [controlFocus, setControlFocus] = useState(0) + const [isShowSuggestion, setIsShowSuggestion] = useState(false) + const doShowSuggestion = isShowSuggestion && !isResponsing + const [suggestQuestions, setSuggestQuestions] = useState([]) + const [messageTaskId, setMessageTaskId] = useState('') + const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false) + const [errorHappened, setErrorHappened] = useState(false) + const [isResponsingConIsCurrCon, setIsResponsingConCurrCon, getIsResponsingConIsCurrCon] = useGetState(true) + const handleSend = async (message: string) => { + if (isResponsing) { + notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) + return + } + const formattedPlugins = Object.keys(plugins).map(key => ({ + [key]: { + enabled: plugins[key], + }, + })) + const formattedDataSets = dataSets.map(({ id }) => { + return { + dataset: { + enabled: true, + id, + }, + } + }) + const data = { + query: message, + conversation_id: isNewConversation ? null : currConversationId, + model: modelId, + tools: [...formattedPlugins, ...formattedDataSets], + } + + // qustion + const questionId = `question-${Date.now()}` + const questionItem = { + id: questionId, + content: message, + agent_thoughts: [], + isAnswer: false, + } + + const placeholderAnswerId = `answer-placeholder-${Date.now()}` + const placeholderAnswerItem = { + id: placeholderAnswerId, + content: '', + isAnswer: true, + } + + const newList = [...getChatList(), questionItem, placeholderAnswerItem] + setChatList(newList) + + // answer + const responseItem: IChatItem = { + id: `${Date.now()}`, + content: '', + agent_thoughts: [], + isAnswer: true, + } + + const prevTempNewConversationId = getCurrConversationId() || '-1' + let tempNewConversationId = prevTempNewConversationId + + setHasStopResponded(false) + setResponsingTrue() + setErrorHappened(false) + setIsShowSuggestion(false) + setIsResponsingConCurrCon(true) + + sendChatMessage(data, { + getAbortController: (abortController) => { + setAbortController(abortController) + }, + onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { + responseItem.content = responseItem.content + message + responseItem.id = messageId + if (isFirstMessage && newConversationId) + tempNewConversationId = newConversationId + + setMessageTaskId(taskId) + // has switched to other conversation + if (prevTempNewConversationId !== getCurrConversationId()) { + setIsResponsingConCurrCon(false) + return + } + + // closesure new list is outdated. + const newListWithAnswer = produce( + getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), + (draft) => { + if (!draft.find(item => item.id === questionId)) + draft.push({ ...questionItem } as any) + + draft.push({ ...responseItem }) + }) + + setChatList(newListWithAnswer) + }, + async onCompleted(hasError?: boolean) { + if (hasError) { + setResponsingFalse() + return + } + + if (getConversationIdChangeBecauseOfNew()) { + const { data: allConversations }: any = await fetchAllConversations() + setAllConversationList(allConversations) + noticeUpdateList() + } + setConversationIdChangeBecauseOfNew(false) + resetNewConversationInputs() + setCurrConversationId(tempNewConversationId, APP_ID, true) + if (getIsResponsingConIsCurrCon() && suggestedQuestionsAfterAnswerConfig?.enabled && !getHasStopResponded()) { + const { data }: any = await fetchSuggestedQuestions(responseItem.id) + setSuggestQuestions(data) + setIsShowSuggestion(true) + } + setResponsingFalse() + }, + onThought(thought) { + // thought finished then start to return message. Warning: use push agent_thoughts.push would caused problem when the thought is more then 2 + responseItem.id = thought.message_id; + (responseItem as any).agent_thoughts = [...(responseItem as any).agent_thoughts, thought] // .push(thought) + // has switched to other conversation + + if (prevTempNewConversationId !== getCurrConversationId()) { + setIsResponsingConCurrCon(false) + return + } + const newListWithAnswer = produce( + getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), + (draft) => { + if (!draft.find(item => item.id === questionId)) + draft.push({ ...questionItem }) + draft.push({ ...responseItem }) + }) + setChatList(newListWithAnswer) + }, + onError() { + setErrorHappened(true) + // role back placeholder answer + setChatList(produce(getChatList(), (draft) => { + draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1) + })) + setResponsingFalse() + }, + }) + } + + const handleFeedback = async (messageId: string, feedback: Feedbacktype) => { + await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }) + const newChatList = chatList.map((item) => { + if (item.id === messageId) { + return { + ...item, + feedback, + } + } + return item + }) + setChatList(newChatList) + notify({ type: 'success', message: t('common.api.success') }) + } + + const renderSidebar = () => { + if (!APP_ID || !promptConfig) + return null + return ( + + ) + } + + const [modelId, setModeId] = useState(DEFAULT_MODEL_ID) + // const currModel = MODEL_LIST.find(item => item.id === modelId) + + const [plugins, setPlugins] = useState>(DEFAULT_PLUGIN) + const handlePluginsChange = (key: string, value: boolean) => { + setPlugins({ + ...plugins, + [key]: value, + }) + } + const [dataSets, setDateSets] = useState([]) + const configSetDefaultValue = () => { + setModeId(DEFAULT_MODEL_ID) + setPlugins(DEFAULT_PLUGIN) + setDateSets([]) + } + const isCurrConversationPinned = !!pinnedConversationList.find(item => item.id === currConversationId) + const [controlItemOpHide, setControlItemOpHide] = useState(0) + if (appUnavailable) + return + + if (!promptConfig) + return + + return ( +
+
+ {/* sidebar */} + {!isMobile && renderSidebar()} + {isMobile && isShowSidebar && ( +
+
e.stopPropagation()}> + {renderSidebar()} +
+
+ )} + {/* main */} +
+ {(!isNewConversation || isResponsing || errorHappened) && ( +
+
+
{conversationName}
+
+ +
e.stopPropagation()}> + isCurrConversationPinned ? handleUnpin(currConversationId) : handlePin(currConversationId)} + isShowDelete + onDelete={() => handleDelete(currConversationId)} + /> +
+
+
+
+ )} +
+
+ } + chatList={chatList} + onSend={handleSend} + isHideFeedbackEdit + onFeedback={handleFeedback} + isResponsing={isResponsing} + canStopResponsing={!!messageTaskId && isResponsingConIsCurrCon} + abortResponsing={async () => { + await stopChatMessageResponding(messageTaskId) + setHasStopResponded(true) + setResponsingFalse() + }} + checkCanSend={checkCanSend} + controlFocus={controlFocus} + isShowSuggestion={doShowSuggestion} + suggestionList={suggestQuestions} + isShowSpeechToText={speechToTextConfig?.enabled} + dataSets={dataSets} + /> +
+
+ + {isShowConfirm && ( + + )} +
+
+
+ ) +} +export default React.memo(Main) diff --git a/web/app/components/explore/universal-chat/init/index.tsx b/web/app/components/explore/universal-chat/init/index.tsx new file mode 100644 index 0000000000..b91f2caf09 --- /dev/null +++ b/web/app/components/explore/universal-chat/init/index.tsx @@ -0,0 +1,43 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import cn from 'classnames' +import type { IConfigProps } from '../config' +import Config from '../config' +import s from './style.module.css' + +const Line = ( + + + + + + + + + + +) + +const Init: FC = ({ + ...configProps +}) => { + const { t } = useTranslation() + + return ( +
+
+
+
{t('explore.universalChat.welcome')}
+
{t('explore.universalChat.welcomeDescribe')}
+
+
+ {Line} +
+ +
+
+ ) +} +export default React.memo(Init) diff --git a/web/app/components/explore/universal-chat/init/style.module.css b/web/app/components/explore/universal-chat/init/style.module.css new file mode 100644 index 0000000000..7cbce70868 --- /dev/null +++ b/web/app/components/explore/universal-chat/init/style.module.css @@ -0,0 +1,9 @@ +.textGradient { + background: linear-gradient(to right, rgba(16, 74, 225, 1) 0, rgba(0, 152, 238, 1) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-fill-color: transparent; +} + + diff --git a/web/app/components/explore/universal-chat/style.module.css b/web/app/components/explore/universal-chat/style.module.css new file mode 100644 index 0000000000..895860c21a --- /dev/null +++ b/web/app/components/explore/universal-chat/style.module.css @@ -0,0 +1,3 @@ +.installedApp { + height: calc(100vh - 74px); +} \ No newline at end of file diff --git a/web/app/components/header/account-setting/index.tsx b/web/app/components/header/account-setting/index.tsx index e9504aa0c2..d53e5e6479 100644 --- a/web/app/components/header/account-setting/index.tsx +++ b/web/app/components/header/account-setting/index.tsx @@ -9,11 +9,12 @@ import MembersPage from './members-page' import IntegrationsPage from './Integrations-page' import LanguagePage from './language-page' import ProviderPage from './provider-page' +import PluginPage from './plugin-page' import DataSourcePage from './data-source-page' import s from './index.module.css' import Modal from '@/app/components/base/modal' -import { Database03 } from '@/app/components/base/icons/src/vender/line/development' -import { Database03 as Database03Solid } from '@/app/components/base/icons/src/vender/solid/development' +import { Database03, PuzzlePiece01 } from '@/app/components/base/icons/src/vender/line/development' +import { Database03 as Database03Solid, PuzzlePiece01 as PuzzlePiece01Solid } from '@/app/components/base/icons/src/vender/solid/development' const iconClassName = ` w-4 h-4 ml-3 mr-2 @@ -80,6 +81,12 @@ export default function AccountSetting({ icon: , activeIcon: , }, + { + key: 'plugin', + name: t('common.settings.plugin'), + icon: , + activeIcon: , + }, ], }, ] @@ -148,6 +155,7 @@ export default function AccountSetting({ {activeMenu === 'language' && } {activeMenu === 'provider' && } {activeMenu === 'data-source' && } + {activeMenu === 'plugin' && }
diff --git a/web/app/components/header/account-setting/key-validator/KeyInput.tsx b/web/app/components/header/account-setting/key-validator/KeyInput.tsx new file mode 100644 index 0000000000..6647dde756 --- /dev/null +++ b/web/app/components/header/account-setting/key-validator/KeyInput.tsx @@ -0,0 +1,77 @@ +import type { ChangeEvent } from 'react' +import { + ValidatedErrorIcon, + ValidatedErrorMessage, + ValidatedSuccessIcon, + ValidatingTip, +} from './ValidateStatus' +import { ValidatedStatus } from './declarations' +import type { ValidatedStatusState } from './declarations' + +type KeyInputProps = { + value?: string + name: string + placeholder: string + className?: string + onChange: (v: string) => void + onFocus?: () => void + validating: boolean + validatedStatusState: ValidatedStatusState +} + +const KeyInput = ({ + value, + name, + placeholder, + className, + onChange, + onFocus, + validating, + validatedStatusState, +}: KeyInputProps) => { + const handleChange = (e: ChangeEvent) => { + const inputValue = e.target.value + onChange(inputValue) + } + + const getValidatedIcon = () => { + if (validatedStatusState.status === ValidatedStatus.Error || validatedStatusState.status === ValidatedStatus.Exceed) + return + + if (validatedStatusState.status === ValidatedStatus.Success) + return + } + const getValidatedTip = () => { + if (validating) + return + + if (validatedStatusState.status === ValidatedStatus.Error) + return + } + + return ( +
+
{name}
+
+ + {getValidatedIcon()} +
+ {getValidatedTip()} +
+ ) +} + +export default KeyInput diff --git a/web/app/components/header/account-setting/key-validator/Operate.tsx b/web/app/components/header/account-setting/key-validator/Operate.tsx new file mode 100644 index 0000000000..285fd7a75c --- /dev/null +++ b/web/app/components/header/account-setting/key-validator/Operate.tsx @@ -0,0 +1,85 @@ +import { useTranslation } from 'react-i18next' +import Indicator from '../../indicator' +import type { Status } from './declarations' + +type OperateProps = { + isOpen: boolean + status: Status + onCancel: () => void + onSave: () => void + onAdd: () => void + onEdit: () => void +} + +const Operate = ({ + isOpen, + status, + onCancel, + onSave, + onAdd, + onEdit, +}: OperateProps) => { + const { t } = useTranslation() + + if (isOpen) { + return ( +
+
+ {t('common.operation.cancel')} +
+
+ {t('common.operation.save')} +
+
+ ) + } + + if (status === 'add') { + return ( +
+ {t('common.provider.addKey')} +
+ ) + } + + if (status === 'fail' || status === 'success') { + return ( +
+ { + status === 'fail' && ( +
+
{t('common.provider.invalidApiKey')}
+ +
+ ) + } + { + status === 'success' && ( + + ) + } +
+ {t('common.provider.editKey')} +
+
+ ) + } + + return null +} + +export default Operate diff --git a/web/app/components/header/account-setting/key-validator/ValidateStatus.tsx b/web/app/components/header/account-setting/key-validator/ValidateStatus.tsx new file mode 100644 index 0000000000..1af9193b0f --- /dev/null +++ b/web/app/components/header/account-setting/key-validator/ValidateStatus.tsx @@ -0,0 +1,30 @@ +import { useTranslation } from 'react-i18next' +import { AlertCircle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' +import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' + +export const ValidatedErrorIcon = () => { + return +} + +export const ValidatedSuccessIcon = () => { + return +} + +export const ValidatingTip = () => { + const { t } = useTranslation() + return ( +
+ {t('common.provider.validating')} +
+ ) +} + +export const ValidatedErrorMessage = ({ errorMessage }: { errorMessage: string }) => { + const { t } = useTranslation() + + return ( +
+ {t('common.provider.validatedError')}{errorMessage} +
+ ) +} diff --git a/web/app/components/header/account-setting/key-validator/declarations.ts b/web/app/components/header/account-setting/key-validator/declarations.ts new file mode 100644 index 0000000000..23522c733b --- /dev/null +++ b/web/app/components/header/account-setting/key-validator/declarations.ts @@ -0,0 +1,43 @@ +import type { Dispatch, SetStateAction } from 'react' + +export enum ValidatedStatus { + Success = 'success', + Error = 'error', + Exceed = 'exceed', +} + +export type ValidatedStatusState = { + status?: ValidatedStatus + message?: string +} + +export type Status = 'add' | 'fail' | 'success' + +export type ValidateValue = Record + +export type ValidateCallback = { + before: (v?: ValidateValue) => boolean | undefined + run?: (v?: ValidateValue) => Promise +} + +export type Form = { + key: string + title: string + placeholder: string + value?: string + validate?: ValidateCallback + handleFocus?: (v: ValidateValue, dispatch: Dispatch>) => void +} + +export type KeyFrom = { + text: string + link: string +} + +export type KeyValidatorProps = { + type: string + title: React.ReactNode + status: Status + forms: Form[] + keyFrom: KeyFrom +} diff --git a/web/app/components/header/account-setting/key-validator/hooks.ts b/web/app/components/header/account-setting/key-validator/hooks.ts new file mode 100644 index 0000000000..45532f640a --- /dev/null +++ b/web/app/components/header/account-setting/key-validator/hooks.ts @@ -0,0 +1,32 @@ +import { useState } from 'react' +import { useDebounceFn } from 'ahooks' +import type { DebouncedFunc } from 'lodash-es' +import { ValidatedStatus } from './declarations' +import type { ValidateCallback, ValidateValue, ValidatedStatusState } from './declarations' + +export const useValidate: (value: ValidateValue) => [DebouncedFunc<(validateCallback: ValidateCallback) => Promise>, boolean, ValidatedStatusState] = (value) => { + const [validating, setValidating] = useState(false) + const [validatedStatus, setValidatedStatus] = useState({}) + + const { run } = useDebounceFn(async (validateCallback: ValidateCallback) => { + if (!validateCallback.before(value)) { + setValidating(false) + setValidatedStatus({}) + return + } + + setValidating(true) + + if (validateCallback.run) { + const res = await validateCallback?.run(value) + setValidatedStatus( + res.status === 'success' + ? { status: ValidatedStatus.Success } + : { status: ValidatedStatus.Error, message: res.message }) + + setValidating(false) + } + }, { wait: 500 }) + + return [run, validating, validatedStatus] +} diff --git a/web/app/components/header/account-setting/key-validator/index.tsx b/web/app/components/header/account-setting/key-validator/index.tsx new file mode 100644 index 0000000000..713ec81fbe --- /dev/null +++ b/web/app/components/header/account-setting/key-validator/index.tsx @@ -0,0 +1,119 @@ +import { useState } from 'react' +import Operate from './Operate' +import KeyInput from './KeyInput' +import { useValidate } from './hooks' +import type { Form, KeyFrom, Status, ValidateValue } from './declarations' +import { useEventEmitterContextContext } from '@/context/event-emitter' +import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' + +export type KeyValidatorProps = { + type: string + title: React.ReactNode + status: Status + forms: Form[] + keyFrom: KeyFrom + onSave: (v: ValidateValue) => Promise +} + +const KeyValidator = ({ + type, + title, + status, + forms, + keyFrom, + onSave, +}: KeyValidatorProps) => { + const triggerKey = `plugins/${type}` + const { eventEmitter } = useEventEmitterContextContext() + const [isOpen, setIsOpen] = useState(false) + const prevValue = forms.reduce((prev: ValidateValue, next: Form) => { + prev[next.key] = next.value + return prev + }, {}) + const [value, setValue] = useState(prevValue) + const [validate, validating, validatedStatusState] = useValidate(value) + + eventEmitter?.useSubscription((v) => { + if (v !== triggerKey) { + setIsOpen(false) + setValue(prevValue) + validate({ before: () => false }) + } + }) + + const handleCancel = () => { + eventEmitter?.emit('') + } + + const handleSave = async () => { + if (await onSave(value)) + eventEmitter?.emit('') + } + + const handleAdd = () => { + setIsOpen(true) + eventEmitter?.emit(triggerKey) + } + + const handleEdit = () => { + setIsOpen(true) + eventEmitter?.emit(triggerKey) + } + + const handleChange = (form: Form, val: string) => { + setValue({ ...value, [form.key]: val }) + + if (form.validate) + validate(form.validate) + } + + const handleFocus = (form: Form) => { + if (form.handleFocus) + form.handleFocus(value, setValue) + } + + return ( +
+
+ {title} + +
+ { + isOpen && ( +
+ { + forms.map(form => ( + handleChange(form, v)} + onFocus={() => handleFocus(form)} + validating={validating} + validatedStatusState={validatedStatusState} + /> + )) + } + + {keyFrom.text} + + +
+ ) + } +
+ ) +} + +export default KeyValidator diff --git a/web/app/components/header/account-setting/plugin-page/SerpapiPlugin.tsx b/web/app/components/header/account-setting/plugin-page/SerpapiPlugin.tsx new file mode 100644 index 0000000000..75a4276042 --- /dev/null +++ b/web/app/components/header/account-setting/plugin-page/SerpapiPlugin.tsx @@ -0,0 +1,77 @@ +import { useTranslation } from 'react-i18next' +import Image from 'next/image' +import SerpapiLogo from '../../assets/serpapi.png' +import KeyValidator from '../key-validator' +import type { Form, ValidateValue } from '../key-validator/declarations' +import { updatePluginKey, validatePluginKey } from './utils' +import { useToastContext } from '@/app/components/base/toast' +import type { PluginProvider } from '@/models/common' + +type SerpapiPluginProps = { + plugin: PluginProvider + onUpdate: () => void +} +const SerpapiPlugin = ({ + plugin, + onUpdate, +}: SerpapiPluginProps) => { + const { t } = useTranslation() + const { notify } = useToastContext() + + const forms: Form[] = [{ + key: 'api_key', + title: t('common.plugin.serpapi.apiKey'), + placeholder: t('common.plugin.serpapi.apiKeyPlaceholder'), + value: plugin.credentials?.api_key, + validate: { + before: (v) => { + if (v?.api_key) + return true + }, + run: async (v) => { + return validatePluginKey('serpapi', { + credentials: { + api_key: v?.api_key, + }, + }) + }, + }, + handleFocus: (v, dispatch) => { + if (v.api_key === plugin.credentials?.api_key) + dispatch({ ...v, api_key: '' }) + }, + }] + + const handleSave = async (v: ValidateValue) => { + if (!v?.api_key || v?.api_key === plugin.credentials?.api_key) + return + + const res = await updatePluginKey('serpapi', { + credentials: { + api_key: v?.api_key, + }, + }) + + if (res.status === 'success') { + notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) + onUpdate() + return true + } + } + + return ( + } + status={plugin.credentials?.api_key ? 'success' : 'add'} + forms={forms} + keyFrom={{ + text: t('common.plugin.serpapi.keyFrom'), + link: 'https://serpapi.com/manage-api-key', + }} + onSave={handleSave} + /> + ) +} + +export default SerpapiPlugin diff --git a/web/app/components/header/account-setting/plugin-page/index.tsx b/web/app/components/header/account-setting/plugin-page/index.tsx new file mode 100644 index 0000000000..4b8c11e6ab --- /dev/null +++ b/web/app/components/header/account-setting/plugin-page/index.tsx @@ -0,0 +1,38 @@ +import useSWR from 'swr' +import { LockClosedIcon } from '@heroicons/react/24/solid' +import { useTranslation } from 'react-i18next' +import Link from 'next/link' +import SerpapiPlugin from './SerpapiPlugin' +import { fetchPluginProviders } from '@/service/common' +import type { PluginProvider } from '@/models/common' + +const PluginPage = () => { + const { t } = useTranslation() + const { data: plugins, mutate } = useSWR('/workspaces/current/tool-providers', fetchPluginProviders) + + const Plugin_MAP: Record = { + serpapi: (plugin: PluginProvider) => mutate()} />, + } + + return ( +
+
+ {plugins?.map(plugin => Plugin_MAP[plugin.tool_name](plugin))} +
+
+ + {t('common.provider.encrypted.front')} + + PKCS1_OAEP + + {t('common.provider.encrypted.back')} +
+
+ ) +} + +export default PluginPage diff --git a/web/app/components/header/account-setting/plugin-page/utils.ts b/web/app/components/header/account-setting/plugin-page/utils.ts new file mode 100644 index 0000000000..cb9e25a093 --- /dev/null +++ b/web/app/components/header/account-setting/plugin-page/utils.ts @@ -0,0 +1,34 @@ +import { ValidatedStatus } from '../key-validator/declarations' +import { updatePluginProviderAIKey, validatePluginProviderKey } from '@/service/common' + +export const validatePluginKey = async (pluginType: string, body: any) => { + try { + const res = await validatePluginProviderKey({ + url: `/workspaces/current/tool-providers/${pluginType}/credentials-validate`, + body, + }) + if (res.result === 'success') + return Promise.resolve({ status: ValidatedStatus.Success }) + else + return Promise.resolve({ status: ValidatedStatus.Error, message: res.error }) + } + catch (e: any) { + return Promise.resolve({ status: ValidatedStatus.Error, message: e.message }) + } +} + +export const updatePluginKey = async (pluginType: string, body: any) => { + try { + const res = await updatePluginProviderAIKey({ + url: `/workspaces/current/tool-providers/${pluginType}/credentials`, + body, + }) + if (res.result === 'success') + return Promise.resolve({ status: ValidatedStatus.Success }) + else + return Promise.resolve({ status: ValidatedStatus.Error, message: res.error }) + } + catch (e: any) { + return Promise.resolve({ status: ValidatedStatus.Error, message: e.message }) + } +} diff --git a/web/app/components/header/assets/serpapi.png b/web/app/components/header/assets/serpapi.png new file mode 100644 index 0000000000..9650453ed5 Binary files /dev/null and b/web/app/components/header/assets/serpapi.png differ diff --git a/web/app/components/share/chat/hooks/use-conversation.ts b/web/app/components/share/chat/hooks/use-conversation.ts index 88a702f029..4e7f27cf50 100644 --- a/web/app/components/share/chat/hooks/use-conversation.ts +++ b/web/app/components/share/chat/hooks/use-conversation.ts @@ -1,5 +1,6 @@ import { useState } from 'react' import produce from 'immer' +import { useGetState } from 'ahooks' import type { ConversationItem } from '@/models/share' const storageConversationIdKey = 'conversationIdInfo' @@ -8,7 +9,7 @@ type ConversationInfoType = Omit function useConversation() { const [conversationList, setConversationList] = useState([]) const [pinnedConversationList, setPinnedConversationList] = useState([]) - const [currConversationId, doSetCurrConversationId] = useState('-1') + const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState('-1') // when set conversation id, we do not have set appId const setCurrConversationId = (id: string, appId: string, isSetToLocalStroge = true, newConversationName = '') => { doSetCurrConversationId(id) @@ -53,6 +54,7 @@ function useConversation() { pinnedConversationList, setPinnedConversationList, currConversationId, + getCurrConversationId, setCurrConversationId, getConversationIdFromStorage, isNewConversation, diff --git a/web/app/components/share/chat/index.tsx b/web/app/components/share/chat/index.tsx index 3e05c871d5..53d8ece250 100644 --- a/web/app/components/share/chat/index.tsx +++ b/web/app/components/share/chat/index.tsx @@ -43,6 +43,8 @@ import Confirm from '@/app/components/base/confirm' export type IMainProps = { isInstalledApp?: boolean installedAppInfo?: InstalledApp + isSupportPlugin?: boolean + isUniversalChat?: boolean } const Main: FC = ({ @@ -88,6 +90,7 @@ const Main: FC = ({ pinnedConversationList, setPinnedConversationList, currConversationId, + getCurrConversationId, setCurrConversationId, getConversationIdFromStorage, isNewConversation, @@ -212,7 +215,7 @@ const Main: FC = ({ } // update chat list of current conversation - if (!isNewConversation && !conversationIdChangeBecauseOfNew && !isResponsing) { + if (!isNewConversation && !conversationIdChangeBecauseOfNew) { fetchChatList(currConversationId, isInstalledApp, installedAppInfo?.id).then((res: any) => { const { data } = res const newChatList: IChatItem[] = generateNewChatListWithOpenstatement(notSyncToStateIntroduction, notSyncToStateInputs) @@ -421,6 +424,7 @@ const Main: FC = ({ const [suggestQuestions, setSuggestQuestions] = useState([]) const [messageTaskId, setMessageTaskId] = useState('') const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false) + const [isResponsingConIsCurrCon, setIsResponsingConCurrCon, getIsResponsingConIsCurrCon] = useGetState(true) const handleSend = async (message: string) => { if (isResponsing) { @@ -457,12 +461,13 @@ const Main: FC = ({ content: '', isAnswer: true, } - - let tempNewConversationId = '' + const prevTempNewConversationId = getCurrConversationId() || '-1' + let tempNewConversationId = prevTempNewConversationId setHasStopResponded(false) setResponsingTrue() setIsShowSuggestion(false) + setIsResponsingConCurrCon(true) sendChatMessage(data, { getAbortController: (abortController) => { setAbortController(abortController) @@ -474,6 +479,11 @@ const Main: FC = ({ tempNewConversationId = newConversationId setMessageTaskId(taskId) + // has switched to other conversation + if (prevTempNewConversationId !== getCurrConversationId()) { + setIsResponsingConCurrCon(false) + return + } // closesure new list is outdated. const newListWithAnswer = produce( getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId), @@ -499,7 +509,7 @@ const Main: FC = ({ resetNewConversationInputs() setChatNotStarted() setCurrConversationId(tempNewConversationId, appId, true) - if (suggestedQuestionsAfterAnswerConfig?.enabled && !getHasStopResponded()) { + if (getIsResponsingConIsCurrCon() && suggestedQuestionsAfterAnswerConfig?.enabled && !getHasStopResponded()) { const { data }: any = await fetchSuggestedQuestions(responseItem.id, isInstalledApp, installedAppInfo?.id) setSuggestQuestions(data) setIsShowSuggestion(true) @@ -628,7 +638,7 @@ const Main: FC = ({ isHideFeedbackEdit onFeedback={handleFeedback} isResponsing={isResponsing} - canStopResponsing={!!messageTaskId} + canStopResponsing={!!messageTaskId && isResponsingConIsCurrCon} abortResponsing={async () => { await stopChatMessageResponding(appId, messageTaskId, isInstalledApp, installedAppInfo?.id) setHasStopResponded(true) diff --git a/web/app/components/share/chat/sidebar/index.tsx b/web/app/components/share/chat/sidebar/index.tsx index cfb8e86fbf..f80ee6a950 100644 --- a/web/app/components/share/chat/sidebar/index.tsx +++ b/web/app/components/share/chat/sidebar/index.tsx @@ -11,6 +11,7 @@ import AppInfo from '@/app/components/share/chat/sidebar/app-info' // import Card from './card' import type { ConversationItem, SiteInfo } from '@/models/share' import { fetchConversations } from '@/service/share' +import { fetchConversations as fetchUniversalConversations } from '@/service/universal-chat' export type ISidebarProps = { copyRight: string @@ -22,6 +23,7 @@ export type ISidebarProps = { isClearPinnedConversationList: boolean isInstalledApp: boolean installedAppId?: string + isUniversalChat?: boolean siteInfo: SiteInfo onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void onPinnedMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void @@ -43,6 +45,7 @@ const Sidebar: FC = ({ isClearPinnedConversationList, isInstalledApp, installedAppId, + isUniversalChat, siteInfo, onMoreLoaded, onPinnedMoreLoaded, @@ -57,8 +60,14 @@ const Sidebar: FC = ({ const [hasPinned, setHasPinned] = useState(false) const checkHasPinned = async () => { - const { data }: any = await fetchConversations(isInstalledApp, installedAppId, undefined, true) - setHasPinned(data.length > 0) + let res: any + if (isUniversalChat) + res = await fetchUniversalConversations(undefined, true) + + else + res = await fetchConversations(isInstalledApp, installedAppId, undefined, true) + + setHasPinned(res.data.length > 0) } useEffect(() => { @@ -70,13 +79,13 @@ const Sidebar: FC = ({ checkHasPinned() }, [controlUpdateList]) - const maxListHeight = isInstalledApp ? 'max-h-[30vh]' : 'max-h-[40vh]' + const maxListHeight = (isInstalledApp || isUniversalChat) ? 'max-h-[30vh]' : 'max-h-[40vh]' return (
= ({ isClearConversationList={isClearPinnedConversationList} isInstalledApp={isInstalledApp} installedAppId={installedAppId} + isUniversalChat={isUniversalChat} onMoreLoaded={onPinnedMoreLoaded} isNoMore={isPinnedNoMore} isPinned={true} @@ -119,18 +129,19 @@ const Sidebar: FC = ({
)} {/* unpinned list */} -
+
{(hasPinned && list.length > 0) && (
{t('share.chat.unpinnedTitle')}
)} = ({
-
-
© {copyRight} {(new Date()).getFullYear()}
-
+ {!isUniversalChat && ( +
+
© {copyRight} {(new Date()).getFullYear()}
+
+ )} ) } diff --git a/web/app/components/share/chat/sidebar/list/index.tsx b/web/app/components/share/chat/sidebar/list/index.tsx index 76cb30ec91..3d470874d4 100644 --- a/web/app/components/share/chat/sidebar/list/index.tsx +++ b/web/app/components/share/chat/sidebar/list/index.tsx @@ -10,6 +10,7 @@ import cn from 'classnames' import s from './style.module.css' import type { ConversationItem } from '@/models/share' import { fetchConversations } from '@/service/share' +import { fetchConversations as fetchUniversalConversations } from '@/service/universal-chat' import ItemOperation from '@/app/components/explore/item-operation' export type IListProps = { @@ -19,6 +20,7 @@ export type IListProps = { list: ConversationItem[] isClearConversationList: boolean isInstalledApp: boolean + isUniversalChat?: boolean installedAppId?: string onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void isNoMore: boolean @@ -35,6 +37,7 @@ const List: FC = ({ list, isClearConversationList, isInstalledApp, + isUniversalChat, installedAppId, onMoreLoaded, isNoMore, @@ -51,8 +54,12 @@ const List: FC = ({ let lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined if (lastId === '-1') lastId = undefined - - const { data: conversations, has_more }: any = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned) + let res: any + if (isUniversalChat) + res = await fetchUniversalConversations(lastId, isPinned) + else + res = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned) + const { data: conversations, has_more }: any = res onMoreLoaded({ data: conversations, has_more }) } return { list: [] } @@ -68,7 +75,7 @@ const List: FC = ({ return (